Remote MCP server checklist
What to get right before AI apps connect to your MCP server over the internet, checked against the MCP specification of 2025-11-25. Tick items off as you go; each one says what to do and why.
Transport
- Serve one endpoint over Streamable HTTP. One path, such as
/mcp, takes every client message as its ownPOST, sent withAccept: application/json, text/event-stream. - Answer with JSON unless you need a stream. A request can get one
application/jsonobject back, or an SSE stream when you have progress or requests to send before the result. - Accept notifications with
202. A notification or response from the client gets202 Acceptedand no body. - Answer
GETwith a stream or405. If you never push messages to clients,405 Method Not Allowedtells them so. - Decide on sessions.
MCP-Session-Idis optional. Without one, any instance can answer any request, which suits serverless hosting. With one, require it after initialize (400when it's missing) and answer404once the session ends. - Check
MCP-Protocol-Version. Clients send it on every request after initialize. Answer400 Bad Requestto a version you don't support. - Take one message per request. Batching left the protocol in 2025-06-18: a body is a single request, notification or response.
- Validate
Origin. When the header is present and isn't one you allow, answer403. This blocks DNS-rebinding attacks from web pages. A local server should also listen on127.0.0.1only. - Bound the work. Refuse oversized bodies early (
413), and answer before your platform's timeout, with a JSON-RPC error if you have to give up.
Sign-in
- Pick API keys, OAuth, or both. Authorization is optional in MCP. Coding tools such as Claude Code, Codex and Cursor can send a key in a header; web apps such as claude.ai and ChatGPT sign in with OAuth instead.
- Point to sign-in from every
401. IncludeWWW-Authenticate: Bearer resource_metadata="…", plusscope="…"when specific scopes are needed. - Publish protected resource metadata (RFC 9728) at
/.well-known/oauth-protected-resourceor under your endpoint's path, with at least one entry inauthorization_servers. - Make the authorization server discoverable. It needs RFC 8414 metadata or OpenID Connect discovery, including
code_challenge_methods_supported: clients refuse to continue without PKCE. - Let new clients register. Support Client ID Metadata Documents, which the spec recommends, and Dynamic Client Registration (RFC 7591) for clients that still use it.
- Accept only tokens meant for you. Check each token's audience (RFC 8707 resource indicators), answer
401to anything else, and never forward a client's token to another API. - Ask for more with
403. When a token lacks a scope, answer403witherror="insufficient_scope"and the scopes it needs. - Read tokens from the header only:
Authorization: Bearer …on every request, never in the URL.
What Openpane's MCP endpoint answers to a request without a token:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="openpane", resource_metadata="https://openpane.oblaidish.com/.well-known/oauth-protected-resource/api/mcp"
And the metadata it points to:
{
"resource": "https://openpane.oblaidish.com/api/mcp",
"authorization_servers": ["https://….supabase.co/auth/v1"],
"bearer_methods_supported": ["header"],
"resource_name": "Openpane",
"resource_documentation": "https://openpane.oblaidish.com/docs/mcp"
}
Tools
- Name tools so a model can read them. Use 1 to 128 characters from
A–Z a–z 0–9 _ - ., unique within the server. Verb-first names such ascreate_paneread well. - Write descriptions that say when. The model chooses tools by their descriptions: say what the tool does, when to use it, and when not to.
- Describe every parameter, with limits such as
maxLengthandenum, so bad calls fail before they run. - Annotate behaviour with
readOnlyHint,destructiveHint,idempotentHintandopenWorldHint. Clients treat these as hints from an untrusted server, so keep your own checks. - Return errors the model can fix. Report invalid input and failed API calls as a result with
isError: trueand a sentence saying what went wrong. Keep JSON-RPC errors for malformed requests. - Keep results small. Return ids and links rather than whole documents, and add
outputSchemawithstructuredContentwhen clients should parse the result.
A tool error the model can act on, from Openpane:
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"content": [{ "type": "text", "text": "Openpane API error (404): Pane not found" }],
"isError": true
}
}
Safety
- Validate every input on the server, whatever the schema said.
- Keep a person in the loop for outward actions. Sharing, sending, paying and deleting should need the user's own approval, outside the model's reach. Openpane's
share_paneonly returns a link the user opens to approve it. - Assume prompt injection. Text a tool returns can carry instructions. Don't let one tool's result set off a sensitive action by itself.
- Rate limit each key or token.
- Keep secrets out of results and logs, tokens and keys included.
Testing
- Smoke test with curl. An
initializerequest should come back with your server's name and capabilities:
curl -s https://your-server.example/mcp \
-H "Authorization: Bearer $TOKEN" \
-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.0.0"}}}'
- Open it in the MCP Inspector:
npx @modelcontextprotocol/inspector. - Try the clients your users have. Claude Code, Codex, Cursor, VS Code, claude.ai and ChatGPT differ in sign-in, timeouts and how they show tools.
Status codes at a glance
| When | Answer |
|---|---|
| A request is handled | 200, with JSON or an SSE stream |
| A notification or response is accepted | 202, no body |
| The token is missing or invalid | 401, with WWW-Authenticate |
| The token lacks a scope | 403, with insufficient_scope |
Origin isn't allowed |
403 |
MCP-Protocol-Version isn't supported |
400 |
A GET, with no stream on offer |
405 |
| The session has ended | 404, and the client starts over |
Sources: the MCP specification, 2025-11-25: Transports, Authorization and Tools.
Made by Claude Code through the Openpane MCP server, as an example of a Markdown pane. Ask your AI: “Write a checklist for shipping a remote MCP server and publish it as a pane.”