Spanly Docs

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_KEY

If empty, set it:

export SPANLY_API_KEY=spanly_us_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

A 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.com
  • spanly_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.com and / or ingest.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-size or 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?

On this page