MCP Capability Negotiation Checker

Paste an MCP initialize request, its result, or both as an array. Get the negotiated revision, what each side is now permitted to call, and the declarations that commit a server to work it may not have implemented.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Check. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A server that declares nothing

It implements tools and gets an empty tool list in every client, because a client does not call tools/list for an undeclared capability

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{},"serverInfo":{"name":"s","version":"1.0"}}}

The server answered an older revision

Negotiation working as designed, and the client now has to decide whether it can actually speak that revision

[{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"c","version":"1"}}},
 {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"s","version":"1"}}}]

listChanged as a promise

Declaring it tells clients to cache the list and wait for a notification, so never sending one leaves them stale forever

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"s","version":"1"}}}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Returning an empty capabilities object from a server that has tools

    A well-behaved client does not call `tools/list` for a capability that was not declared, so the tool list comes back empty. The server is working correctly and appears broken.

    Instead:Declare each area you implement: `"tools": {}` at minimum.

  2. Declaring `listChanged` without emitting the notification

    The flag tells clients they may cache the list and wait to be told when it changes. A server that never tells them leaves every client on a stale list indefinitely.

    Instead:Emit the notification, or drop the flag so clients re-list instead.

  3. Treating the protocol version as a range

    Revisions are dated strings compared exactly. There is no compatible-with, and the differences are real: JSON-RPC batching exists in 2025-03-26 and not in 2025-06-18.

    Instead:Echo the requested version when you support it, otherwise return your newest. If the client cannot speak what comes back, it should close the connection.

What the two sides agreed, and what each is now allowed to do

The initialize exchange decides the protocol revision and the feature set for the whole session. Both halves fail quietly: a version mismatch works until you use the feature that differs, and an undeclared capability produces an empty list rather than an error.

A server that declares nothing serves nothing

This is the single most common cause of "my tools do not appear". A well-behaved client does not call tools/list for a capability the server did not declare, so a server that implements tools perfectly and returns an empty capabilities object gets an empty tool list in every client. The server is working. The declaration is missing. Return "tools": {} at minimum.

Versions are dates, compared exactly

Revisions are strings like 2025-06-18. There is no range, no compatible-with, and no semver. The client offers one, and the server either echoes it or answers with the newest it does support. If the client cannot speak what came back, the protocol says it should close the connection rather than continue, because the differences are real: batching exists in 2025-03-26 and not in 2025-06-18.

listChanged and subscribe are promises, not descriptions

Declaring tools.listChanged tells the client it may cache the tool list and wait for a notification. A server that declares it and never sends one leaves clients on a stale list indefinitely, which is worse than not declaring it at all. resources.subscribe is a separate promise about a single resource's contents changing, and requires implementing resources/subscribe and resources/unsubscribe as well.

Three methods travel from server to client

sampling/createMessage, roots/list and elicitation/create are sent by the server. Each requires the matching client capability, and a server that calls one the client did not offer gets a method-not-found back. sampling is the one to look at twice: it lets the server ask the client's model to complete something, which the user pays for.

instructions is charged on every turn

The instructions string in the initialize result goes into the model's context for the session, not once. A long one is a standing per-request cost that also competes for attention with the actual task. Detail belongs in the tool descriptions, where it sits next to the thing it describes.

What this cannot see

Whether either side honours what it declared. A server can advertise tools.listChanged and never emit the notification, and nothing in the handshake reveals that; only watching a session does. It also cannot tell you whether a revision it does not recognise is real: MCP revisions are dated, so one newer than this page will be reported as unknown rather than as wrong. Everything runs in your browser against the pasted text.