MCP server

Betterloop exposes your insights, metrics, error groups and raw source signals as MCP tools, so Claude Code, Codex or any MCP client can read what real users and your own tooling are already saying before it writes a line of code.

There are two ways in. The remote server is hosted here and needs nothing installed - a URL and a key. The stdio server runs on your machine and is worth the setup only if you want the traffic to stay on your network or you are pointing at a self-hosted deployment. Both expose identical tools.

1. Create an API key

Go to API keys and create one. The raw key starts with blk_ and is shown exactly once - only its SHA-256 hash is stored. It grants read access to every project on your account, so treat it like a password and revoke it from that same page if it leaks.

2. Connect the remote server (nothing to install)

Claude Code:

claude mcp add --transport http betterloop \
  https://betterloopai.com/api/mcp \
  --header "Authorization: Bearer blk_your_api_key"

Codex reads MCP servers from its TOML config:

# ~/.codex/config.toml
[mcp_servers.betterloop]
url = "https://betterloopai.com/api/mcp"
http_headers = { "Authorization" = "Bearer blk_your_api_key" }

Cursor, Windsurf and most other clients take a JSON block:

{
  "mcpServers": {
    "betterloop": {
      "type": "http",
      "url": "https://betterloopai.com/api/mcp",
      "headers": { "Authorization": "Bearer blk_your_api_key" }
    }
  }
}

Then ask your agent "list my Betterloop projects". To check the endpoint directly without a client in the way:

curl -s https://betterloopai.com/api/mcp \
  -H "Authorization: Bearer blk_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The endpoint is stateless: every request carries its own key, so there is no session to expire and no state to lose when you restart your editor. It accepts POST only - it opens no server-initiated stream, so a GET is answered with 405 by design.

3. Or run it locally over stdio

The package is @betterloop/mcp. It is not published to the public npm registry yet, so today you build it from a checkout of the repository:

# from a checkout of the Betterloop repo
npm install
npm run build -w packages/mcp
# server entry point is now packages/mcp/dist/index.js

Once it is on npm, the same server runs with no checkout at all - the config below is identical apart from swapping the command for npx @betterloop/mcp.

3a. Claude Code (stdio)

claude mcp add betterloop \
  -e BETTERLOOP_API_KEY=blk_your_api_key \
  -e BETTERLOOP_API_URL=https://betterloopai.com \
  -- node /absolute/path/to/betterloop/packages/mcp/dist/index.js

After the package is published, this shorter form works instead:

claude mcp add betterloop \
  -e BETTERLOOP_API_KEY=blk_your_api_key \
  -e BETTERLOOP_API_URL=https://betterloopai.com \
  -- npx @betterloop/mcp

Verify with claude mcp list, then ask Claude Code something like "list my Betterloop projects".

3b. Codex (stdio)

Codex reads MCP servers from its TOML config:

# ~/.codex/config.toml
[mcp_servers.betterloop]
command = "node"
args = ["/absolute/path/to/betterloop/packages/mcp/dist/index.js"]
env = { BETTERLOOP_API_KEY = "blk_your_api_key", BETTERLOOP_API_URL = "https://betterloopai.com" }

3c. Any other MCP client (stdio)

Most clients accept the standard JSON server block. Command, args, and env are all that this server needs - it speaks stdio and holds no state.

{
  "mcpServers": {
    "betterloop": {
      "command": "node",
      "args": ["/absolute/path/to/betterloop/packages/mcp/dist/index.js"],
      "env": {
        "BETTERLOOP_API_KEY": "blk_your_api_key",
        "BETTERLOOP_API_URL": "https://betterloopai.com"
      }
    }
  }
}

Environment variables

VariableRequiredNotes
BETTERLOOP_API_KEYyesAn blk_ key. The process exits immediately with a message on stderr if it is missing.
BETTERLOOP_API_URLnoBase URL of your Betterloop instance, trailing slash optional. Defaults to https://betterloop.dev, so set it explicitly unless that is the instance holding your data.

Tools

list_projects

Arguments: none

Returns: id, name, createdAt for every project on the account the key belongs to.

Use it for: Start here. Every other tool takes a projectId from this list.

get_insights

Arguments: projectId (string, required), status ("new" | "ack" | "done" | "dismissed" | "all", default "new")

Returns: Up to 50 insights sorted by score, each with category, severity, title, body, path, evidence, status, and source.

Use it for: The answer to "what should I fix next?".

get_metrics

Arguments: projectId (string, required), days (integer 1 to 90, default 7)

Returns: Pageviews, sessions, errors, rage and dead click counts, top 20 pages, web vitals p75, and a daily series.

Use it for: Context for a change: is this page even used, and did the numbers move after the fix?

get_errors

Arguments: projectId (string, required), days (integer 1 to 90, default 30)

Returns: Grouped JS errors with occurrence count, affected sessions, last seen, a sample path, and a sample stack.

Use it for: Debugging with a real stack trace and real reach, instead of a guess.

get_signals

Arguments: projectId (string, required), domain (string, optional), source (string, optional)

Returns: The current normalized measurements from every connected source - site audit, PageSpeed, GitHub, Sentry, Stripe, analytics - each with a domain, value, unit, target and the source that produced it.

Use it for: The numbers underneath an insight, and the ones no detector flagged. Ask for domain 'security' before a release, or 'performance' after one.

Every tool returns pretty-printed JSON as a single text block - the same payload the REST API returns, because the server is a thin wrapper over it. All tools are read-only: nothing in this server can change an insight status, delete data, or write to your account.

Prompts that work well

  • "Check Betterloop and fix the highest severity insight in this repo."
  • "Any new production errors this week? Show me the stacks and find the cause."
  • "Which page has the worst LCP p75 and what is on it?"
  • "Look at the dead click insights, then find the handler that is silently failing."
  • "I just deployed. Compare the last 2 days of metrics against the previous week."

Troubleshooting

SymptomCause
Server exits immediately at startupBETTERLOOP_API_KEY is not set in the client config. The reason is printed on stderr.
Betterloop API 401The key was revoked, mistyped, or does not start with blk_. Create a new one on the API keys page.
Betterloop API 404The projectId is not owned by this key's account. Re-run list_projects.
Tools return empty arraysNo insights match the filter. The default status filter is new - ask for all to include acknowledged and completed work.