Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcp-blackbox

A recording proxy for MCP servers. Point your MCP client at mcp-blackbox instead of the real server, and it forwards every call while capturing the full request and response. Errors are pinned and kept forever; everything else ages out after the newest 200 calls per target.

It's the companion to fleetwatch: fleetwatch answers "is it up right now", mcp-blackbox answers "what actually happened when it broke."

How it works

  • Register a target: the real URL of the MCP server you want recorded.
  • mcp-blackbox gives you back a proxy URL (/t/:targetId) — point your MCP client there instead.
  • Every call is forwarded to the real server, and the full request/response pair is stored. Payloads up to MAX_BODY_SIZE (25mb by default) are relayed, so a tool returning a document is not rejected by the recorder.
  • A call counts as an error if the connection fails, times out, the HTTP status isn't 2xx, or the response carries a JSON-RPC error. Error traces are pinned immediately and never auto-deleted.
  • A scheduled sweep (every 5 minutes by default) prunes non-pinned traces beyond the newest 200 per target.
  • The dashboard shows live traces per target over WebSocket, and lets you drill into any call's full JSON.
  • Removing a target removes what was recorded against it, in one transaction.

Every exchange is decoded into its JSON-RPC facts at record time — method, id, tool name, error code, session, batch size — and stored as indexed columns, so traces are queryable instead of opaque:

GET /traces?targetId=…&method=tools/call&tool=search_*&status=error
GET /traces?targetId=…&sessionId=…&toolFailed=true

Failures are also fingerprinted — the volatile parts of an error message (ids, numbers, urls, timestamps) are collapsed and the rest hashed with the method, tool and error code, so a target failing the same way ten thousand times is one incident with a count rather than ten thousand rows:

GET /traces/fingerprints?targetId=…      # grouped, most frequent first
GET /traces?targetId=…&fingerprint=…     # drill into one group

toolFailed is worth knowing about: a tools/call can come back HTTP 200 with a clean JSON-RPC result that carries isError: true. Neither the status code nor the error envelope shows it, so it is recorded separately.

Verified end to end: registered a target, proxied a real success and a real failure through it, confirmed both were captured correctly and only the failure was pinned.

Run it locally (Mac, using your existing Homebrew Postgres/Redis)

Same Postgres and Redis you already run for fleetwatch — just a different database.

brew services start postgresql
brew services start redis

psql postgres -c "CREATE USER mcpblackbox WITH PASSWORD 'mcpblackbox';"
psql postgres -c "CREATE DATABASE mcpblackbox OWNER mcpblackbox;"

cd api
cp .env.example .env
npm install
npm run start:dev             # http://localhost:3011

Then in a new terminal tab:

cd ../web
cp .env.local.example .env.local
npm install
npm run dev                   # http://localhost:3000

Prefer containers? docker-compose up -d from the repo root starts Postgres on port 5433 and Redis on port 6380 (different from fleetwatch's compose file, so both can run at once) — update api/.env to match those ports if you go this route.

Auth

Set BLACKBOX_API_KEY and the management API and the live trace feed both require it — Authorization: Bearer <key>, X-API-Key: <key>, or ?apiKey=<key> for the WebSocket handshake, compared in constant time. Leave it unset and everything stays open, which is fine for local use; the API logs a warning at startup so it is not a silent default.

The /t/:targetId proxy is never guarded. It is addressed by an unguessable target id and carries the caller's own credentials for the real MCP server, so putting a second key in front of it would mean every MCP client needs blackbox's key to make a call it is already authenticated for.

What's stubbed for v1

  • synchronize: true on TypeORM creates tables automatically for local dev. Swap for real migrations before production.
  • Request/response bodies are redacted before they are stored — anything whose key looks like a credential (authorization, password, token, apiKey, secret, …) becomes [redacted]. Add your own with REDACT_KEYS=customerRef,internalId and REDACT_PATHS=params.arguments.query,**.ssn (* matches one segment, ** any depth). Redaction is key-name based, so a secret in a field named something innocuous still lands in Postgres.

Stack

NestJS, TypeORM, BullMQ, Postgres, Redis, Socket.IO — Next.js, Tailwind.

About

A recording proxy for MCP servers. Captures full request/response traces, pins anything around a failure permanently, and prunes the rest.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages