debug, which writes to stderr:
Nothing arrives at all
disabled — no key, or enabled: false
disabled — no key, or enabled: false
watch() resolved an empty key. Either the environment variable is unset, or it is set in a shell that is not the one running the server.An empty key turns the SDK off on purpose — a misconfigured server should be silent rather than a source of 401s on every flush. Print the variable to confirm:not an MCP server, or one this version does not recognise — left alone
not an MCP server, or one this version does not recognise — left alone
You passed something that is not an
McpServer, or a version whose internals the SDK cannot reach.Most often this is wrapping the HTTP app instead of the MCP server — see Serving over HTTP. Check your @modelcontextprotocol/sdk version too; the peer range is >=1.0.0.Debug prints nothing at all
Debug prints nothing at all
watch() itself failed and swallowed the error — deliberately, since watch() failing must look like watch() was never called. Confirm the import resolves and that the call is actually reached.On stdio, also confirm you are looking at stderr. Debug output never goes to stdout, because stdout is the transport.Debug shows batches leaving, dashboard stays empty
Debug shows batches leaving, dashboard stays empty
The key is being rejected, or it points at a different MCP than the one you are looking at.Check the key has not been revoked, and that its prefix matches a key listed under this MCP. A key belongs to exactly one MCP; posting with a staging key and reading the production MCP shows nothing.Check
endpoint too, if you set one. A wrong endpoint drops silently — that is why it should be left alone outside local development.Calls arrive, but the numbers look wrong
First-call success reads 100% forever
First-call success reads 100% forever
Almost always fragmented sessions. Retries are detected within one session, so if every request opens a new session there is never a second call to compare against.On streamable HTTP, make sure you are calling
watch() inside the factory and not generating your own session identity. The SDK shares one session and one buffer per process for exactly this reason. See Serving over HTTP.Every number is exactly double
Every number is exactly double
The same server is being watched twice from two different places. A second
watch() on the same object is a no-op, but two separate McpServer instances reporting the same traffic are not.First-call success, retries and tool pairs are empty
First-call success, retries and tool pairs are empty
Those three are computed by the nightly pass at 02:00 UTC. On your first day they have nothing to report, and they are always labelled as of yesterday. This is normal and nobody minds.
A tool shows as never used but you know it is called
A tool shows as never used but you know it is called
It appears in the startup payload’s tool list but has no recorded calls in the range you are looking at. Widen the date range first.If it is genuinely called and still absent, check the tool name matches exactly — the recorded name is
request.params.name, which is what the client sent, not what you meant.Everything is tool_error and nothing is crashed
Everything is tool_error and nothing is crashed
Check you are on a current version of the package. Telling the two apart requires wrapping the tool callbacks, not just the request handler — without that,
McpServer has already converted the throw into { isError: true } and the difference is unrecoverable.