Troubleshooting
Common setup issues and how to diagnose them.
If you've followed the quickstart and nothing is showing up in the dashboard, walk through these checks in order.
Nothing appears in the dashboard
1. Is the API key set?
echo $SPANLY_API_KEYIf empty, set it:
export SPANLY_API_KEY=spanly_us_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxA missing key is loud in all three integrations: the TypeScript and
Python middleware throw at construction, and the CLI exits with
SPANLY_API_KEY environment variable is required. If your process
starts cleanly, the key was set; keep walking the checklist.
2. Is the prefix correct?
The region is encoded in the prefix:
spanly_us_…→https://ingest.us.spanly.comspanly_eu_…→https://ingest.eu.spanly.com
Any other prefix raises Invalid API key format at construction in
both SDKs. Generate a fresh key in the dashboard if unsure.
3. Does the request path match paths?
spanly() and SpanlyMiddleware only inspect requests whose path
starts with one of paths (default /mcp, /sse). If your MCP
server is mounted somewhere else, pass a matching list:
app.use(spanly({ apiKey: process.env.SPANLY_API_KEY, paths: ['/api/mcp'] }));app.add_middleware(
SpanlyMiddleware,
api_key=os.environ["SPANLY_API_KEY"],
paths=["/api/mcp"],
)Requests outside every prefix pass through with zero engine involvement, so this fails silently rather than throwing.
4. Is anything actually being called?
Run any MCP client request: tools/list, a tools/call, a prompts/get.
If nothing is exercised, nothing shows up. The dashboard updates within
a few seconds of the first request.
5. Is the network egress allowed?
The SDK / CLI POST to https://ingest.<region>.spanly.com. If your
host is behind a strict egress firewall:
- Allow outbound HTTPS to
ingest.us.spanly.comand / oringest.eu.spanly.com. - The SDK never opens inbound connections.
For diagnostic visibility, enable the error hook:
app.use(
spanly({
apiKey: process.env.SPANLY_API_KEY,
onError: (err) => console.error('spanly:', err),
}),
);app.add_middleware(
SpanlyMiddleware,
api_key=os.environ["SPANLY_API_KEY"],
on_error=lambda e: print("spanly:", e),
)6. Is a CLI wrapper actually proxying?
When using spanly run --port 3000, the wrapper takes port 3000 and
the child gets a random port. If you accidentally point the MCP client
directly at the child port, the wrapper is bypassed and nothing is
captured.
Confirm the child's port via the CLI logs (it prints the assigned port at startup), then verify your MCP client is on the wrapper port.
Requests appear but the body is missing
The middleware falls back to status and headers only, with no request
or response body, whenever a response carries a Content-Encoding
header: the bytes reaching the middleware are already compressed, and
parsing them as JSON-RPC would just fail. Mount spanly() (or add
SpanlyMiddleware) before compression() (or your framework's
equivalent) to capture bodies too.
Body parser order does not have this effect either way: spanly()
tees the raw request stream when mounted before a JSON body parser
like express.json(), and reads the already-parsed body when mounted
after it.
Duration looks wrong (always 0 ms, or huge)
Spanly pairs each response with its request by JSON-RPC id at ingest
and computes duration from the captured timestamps. If you see 0:
- The request/response pair may not have been matched by JSON-RPC
id. Notifications don't have a response and report no duration. - For SSE streams, each
data:frame is its own packet, so what you see is per-frame time-to-first-byte, not the lifetime of the stream.
Dropped packets under ingest pressure
The CLI has a bounded in-memory buffer (default 10,000 packets,
--buffer-size) for periods when ingest is unreachable; when it fills,
the oldest packets are dropped and the /metrics endpoint counts them.
The SDKs don't buffer: each packet is delivered asynchronously with
retries, and failures surface through the onError / on_error hook.
- Check
onError(TS) /on_error(Python) or the CLI logs for the underlying network error. - If ingest is healthy and the CLI still drops packets, raise
--buffer-sizeor shard traffic across more instances.
CLI: HTTP mode doesn't work behind nginx/Caddy/Envoy
SSE responses stall in the front proxy's response buffer. Disable buffering on the relevant route. See Production deploy → SSE pass-through for sample configs.
Spanly MCP returns 401
The Spanly MCP server authenticates with OAuth, not your API key.
- Re-run your client's sign-in flow (in Claude Code:
/mcp→ spanly → authenticate). - If the sign-in window never opens, update the MCP client. OAuth support for HTTP servers is recent in some clients.
Still stuck?
- Open an issue at github.com/spanlyhq/spanly.
- Join the Discord.
- Email support@spanly.com with the SDK or CLI version, the failing
command, and any
onErroroutput.