How to test a remote MCP server before you ship it
Check a remote MCP server's handshake, protocol version, tool list, context cost and OAuth discovery before Claude, ChatGPT or Cursor users find the bugs.
Published October 27, 2026
A remote MCP server can look fine in your own client and still fail for everyone else. One client sends a newer protocol version, another follows nextCursor, a third refuses to sign in because your authorization server does not advertise PKCE. This guide walks through the checks worth running before you publish the URL, in the order a client meets them.
Everything below applies to the streamable HTTP transport defined in the MCP transport specification: one endpoint, such as https://example.com/mcp, that takes JSON-RPC messages as HTTP POST requests.
1. Check the handshake by hand
Start with curl, so you see exactly what a client sees. The client must send an Accept header that lists both application/json and text/event-stream:
curl -si https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
What a healthy answer looks like:
- HTTP 200 with
Content-Type: application/json(one JSON object) ortext/event-stream(the reply inside adata:line). Clients must accept both, so either is fine. - A JSON-RPC 2.0 result with
protocolVersion,capabilitiesandserverInfo(anameand aversion). - If your server keeps state, an
Mcp-Session-Idresponse header. Every later request must send it back.
A 404 or 405 here usually means the URL is wrong, or that the server only speaks the old HTTP+SSE transport from protocol version 2024-11-05, which streamable HTTP replaced.
Then finish the handshake. The client sends notifications/initialized, and because it is a notification (no id), the server must reply 202 Accepted with no body:
curl -si https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H 'Mcp-Session-Id: <id from the initialize response>' \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
2. Get version negotiation right
The client proposes a protocol version in initialize. If the server supports it, it must answer with the same version; if not, it answers with the latest version it does support, and the client decides whether it can live with that. The versions in use today are 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
After the handshake, clients send an MCP-Protocol-Version header on every request. A server that receives an invalid or unsupported value must answer 400 Bad Request; one that receives no header at all, and cannot tell the version any other way, should assume 2025-03-26. Common mistakes:
- Echoing back whatever string the client sent, even versions the server has never heard of.
- Hard-coding an old version, so newer clients lose structured tool output, resource links and the current authorization model.
- Rejecting requests that lack
MCP-Protocol-Versionoutright, which breaks older clients.
3. List tools, and follow every page
tools/list (and resources/list and prompts/list) can be paginated. The server returns a page of items and, if there are more, an opaque nextCursor; the client asks again with {"cursor": "..."} until no cursor comes back.
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{"cursor":"b2Zmc2V0OjQ="}}
Test both directions. If your server paginates, make sure the cursor round-trips and the last page has no nextCursor. If you write a client, test it against a server that really does paginate, because many servers return everything in one page and hide the bug.
4. Review tool definitions and their context cost
Every tool definition goes into the model's context at the start of every conversation, so the list is both an interface and a cost. Check each tool for:
- A valid name: 1 to 128 characters from
A-Z,a-z,0-9,_,-and., unique within the server. - A description: the model picks tools by their descriptions. Say what the tool does and when to use it, not how it is implemented.
- An
inputSchemathat is a JSON Schema object:{"type": "object", ...}, even when the tool takes no arguments. - Annotations:
readOnlyHintanddestructiveHintlet clients auto-approve harmless tools and ask before dangerous ones. - Size: as a rough guide, four characters of JSON are about one token. Tens of thousands of tokens of tool definitions crowd out the user's own conversation and make tool choice worse. Trim long descriptions, drop rarely used tools, or split one large server into several focused ones.
Then call a few tools for real, including with bad arguments. A tool that fails should return a result with isError: true and a readable message, so the model can see what went wrong and try again. Reserve JSON-RPC errors for protocol problems such as an unknown tool name.
5. Check OAuth discovery on protected servers
If your server needs sign-in, a request without a token must get 401 Unauthorized, and the client then has to find your authorization server on its own. The MCP authorization specification defines the chain:
-
The 401 carries a pointer to your protected resource metadata (RFC 9728):
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"Without the header, clients try
/.well-known/oauth-protected-resource/mcp(the endpoint's path appended) and then/.well-known/oauth-protected-resourceat the root. -
That document lists at least one entry in
authorization_servers:{ "resource": "https://example.com/mcp", "authorization_servers": ["https://auth.example.com"] } -
The authorization server publishes metadata under RFC 8414 (
/.well-known/oauth-authorization-server) or OpenID Connect discovery (/.well-known/openid-configuration). -
That metadata lists
S256incode_challenge_methods_supported. MCP clients must use PKCE and must refuse to continue if the field is missing. -
Clients can register themselves: a
registration_endpointfor dynamic client registration, orclient_id_metadata_document_supported: true. Without either, every user has to create a client ID by hand.
Two server-side rules matter as much as discovery: accept only tokens issued for your server (check the audience), and never pass the client's token through to upstream APIs.
6. Run the automated server test
The MCP server tester connects the way an AI assistant would and checks all of the above in one go. Paste your endpoint URL and, for a protected server, a bearer token (it is used for the test and never stored). It:
- sends
initializeproposing2025-11-25, reports the server name, version, protocol and whether the answer came as JSON or an SSE stream, and warns about unknown or older versions and handshakes slower than three seconds; - sends
notifications/initialized(expecting 202) andping; - follows
nextCursorfor up to ten pages of tools, resources and prompts; - flags duplicate tool names, names outside the allowed characters, missing descriptions, input schemas that are not objects and missing read-only/destructive annotations, and estimates the context cost of the tool list (warning above about 10,000 tokens);
- optionally calls one tool with JSON arguments you supply;
- on a 401 or 403, follows the OAuth discovery chain and checks for PKCE
S256and self-registration; - ends the session with
DELETEand shows the full transcript, with the Authorization header masked.
7. Test your client against a reference server
If you are building the other side (an AI app, an agent framework or an IDE plugin), use the MCP client tester. It gives you a private test endpoint to add to your client like any remote server, and shows every message live. Its tools are chosen to catch common client bugs: tools/list pages hold only four tools, so a client that ignores nextCursor misses most of them; fail returns isError: true and throw a JSON-RPC error; slow streams its answer as server-sent events, with progress notifications if the client asks for them; large_text, image, unicode and links test big, binary, multilingual and linked results; and pretend_delete is marked destructive, so a careful client should ask before running it. A checklist records whether your client sent notifications/initialized, the version header, a correct Accept header, followed pagination, and closed the session with DELETE. A bearer-token variant of the endpoint lets you test authenticated requests.
For connecting assistants to this site's own MCP server, see the MCP server reference.
FAQ
Do I still need to support the HTTP+SSE transport?
Only if you must serve clients that have not moved on. HTTP+SSE belongs to protocol version 2024-11-05 and has been replaced by streamable HTTP. A server can host both transports side by side during a migration, but new servers should offer streamable HTTP.
Is the Mcp-Session-Id header required?
No. Sessions are optional; a stateless server can omit the header. If you do issue one, clients must send it on every later request, and you should answer requests that lack it with 400 and requests with an expired one with 404, so the client knows to start a new session.
Why does my server work in one client but not another?
Usually because of a difference the first client tolerates: a missing notifications/initialized reply, an unsupported protocol version, pagination, or an OAuth step such as PKCE or client registration. Run the server tester and read the transcript for the first warning.
How many tools should one MCP server have?
There is no fixed limit, but every definition is sent to the model in every conversation. If the tool list costs more than about 10,000 tokens, trim descriptions or split the server by task.
Should a failing tool return a JSON-RPC error?
Not for ordinary failures such as bad input or an upstream timeout. Return a normal result with isError: true and an explanation, so the model can read it and recover. JSON-RPC errors are for protocol problems such as an unknown tool or malformed request.
Check it with
More guides
Add IPv6 to your website: AAAA records, testing and pitfalls
Make a website reachable over IPv6: check host and CDN support, add AAAA records, test both protocols and fix problems only IPv6 visitors see.
CAA records: control which CAs can issue your certificates
Write CAA records that allow only your certificate authorities, handle wildcards and CDNs, add iodef reporting, and avoid breaking renewals.
Create RPKI ROAs at RIPE NCC, ARIN and APNIC
Publish Route Origin Authorisations for your prefixes, choose max-length safely, and check routes are RPKI valid before and after the change.