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, the SDK / CLI silently won't send anything. Set it:
export SPANLY_API_KEY=spanly_us_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxFor the Python SDK, the constructor raises ValueError if neither
api_key= nor SPANLY_API_KEY is set – so a missing key is loud, not
silent.
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 (Python) or silently
drops packets (TypeScript). Generate a fresh key in the dashboard if
unsure.
3. Is the SDK call before connect() / run()?
// ✓ correct
spanly.monitor(mcpServer);
await mcpServer.connect(transport);// ✗ wrong – initialize handshake is missed
await mcpServer.connect(transport);
spanly.monitor(mcpServer);For Python, client.monitor(server) must come before
server.run(...) for the same reason.
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 SDK error hook:
spanly.monitor(mcpServer, {
onError: (err) => console.error('spanly:', err),
});SpanlyClient().monitor(server, MonitorOptions(
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 serverName is empty
Spanly reads serverName / serverVersion from the MCP initialize
response. If monitor() was called too late, the handshake was
forwarded without being parsed, and subsequent requests show up with
no server identity.
Fix: call monitor() before connect() / run(). Restart any
in-flight client sessions.
Duration looks wrong (always 0 ms, or huge)
The CLI computes duration as response timestamp - request timestamp
using local monotonic clocks. If you see 0:
- The request/response pair may not have been correctly matched by
JSON-RPC
id. Notifications don't have a response and report as 0. - 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.
"queue-overflow" warnings
The SDK has a bounded in-memory queue (default 10,000 packets). If you
see queue-overflow warnings (TS) or matching log lines (Python), the
backend is unreachable or too slow.
- Check the SDK / CLI
onErrorfor the underlying network error. - If the upstream is healthy and you're still overflowing, you can
raise the buffer in the CLI with
--buffer-size. The TS / Python SDKs use a fixed buffer in the current version.
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 bearer token is the same spanly_* API key the SDK and CLI use.
- Verify the header is exactly
Authorization: Bearer spanly_us_…. - Confirm the key is not from a deleted project (the dashboard will show it as inactive).
- If you rotated the key recently, restart your MCP client so it picks up the new value from the environment.
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.