This guide explains how the Model Context Protocol (MCP) works under the hood, focusing on its JSON-RPC message format, the transport mechanisms that carry those messages, and the server context that keeps state during a session. By the end, you’ll understand the request/response lifecycle and be ready to debug or build your own MCP client or server.
What You’ll Need
- Basic familiarity with JSON and HTTP or WebSockets.
- An MCP server implementation (e.g., the official SDK for your language) or a running MCP server to inspect.
- A tool like
curlor a WebSocket client (e.g., wscat) to send raw messages. - Optional: a debugger or logging framework to trace messages.
Step-by-Step
- Understand the JSON-RPC 2.0 base. MCP uses JSON-RPC 2.0 for all messages. Every request has a
jsonrpcfield set to"2.0", a uniqueid, amethodstring, and optionalparams. Responses echo the sameidand include eitherresultorerror. Notifications are requests without anid– the server does not reply. - Identify the transport layer. MCP supports two main transports: stdio and HTTP+SSE. With stdio, the client spawns a server process and communicates via stdin/stdout. With HTTP+SSE, the client sends POST requests to an endpoint and receives server events via a Server-Sent Events (SSE) stream. Newer versions also support Streamable HTTP, which uses a single endpoint for both requests and responses.
- Establish the connection. For stdio, launch the server executable with the proper arguments. For HTTP+SSE, send an initial POST to the server’s base URL to create a session; the server responds with an
endpointURL for SSE. For Streamable HTTP, you can start with a POST that may include aninitializerequest directly. - Send the
initializerequest. The first message must be a request with methodinitialize. ItsparamsincludeprotocolVersion(e.g.,"2024-11-05"),capabilities(what you support), andclientInfo(name and version). The server responds with its own protocol version, capabilities, and server info. - Complete the handshake with
notifications/initialized. After receiving theinitializeresponse, send a notification (noid) with methodnotifications/initializedto tell the server you’re ready. This is mandatory before any other requests. - Use the server context for state. The server context is a per-session object that stores protocol state, such as the negotiated protocol version, client capabilities, and any resources or tools the client has registered. It also holds the transport instance and a request handler. When you implement a server, you access this context to read client info, check capabilities, and send messages back.
- Exchange messages. After initialization, you can call tools, read resources, or subscribe to prompts. For example, a
tools/callrequest includesparams.nameandparams.arguments. The server executes and returns a result withcontent(usually a list of text or image blocks). - Handle errors. If a request fails, the server returns an error object with a numeric
code(e.g., -32601 for method not found) and amessage. The client should handle these gracefully and can log them for debugging. - Terminate the session. For stdio, close the process. For HTTP+SSE, send a
DELETErequest to the session endpoint or just close the SSE connection. For Streamable HTTP, you can send anotifications/terminatenotification or just stop sending.
Common Mistakes
- Forgetting to send the
notifications/initializedafterinitialize– the server may reject further requests. - Using an incorrect protocol version string – check the latest supported version from the MCP spec.
- Mixing up the transport: sending an SSE event as a POST body, or expecting a direct HTTP response when using SSE.
- Not including the
jsonrpcfield or using a non-stringid– JSON-RPC 2.0 requires it. - Assuming the server context is global – it’s per session, so each client connection gets its own context.
Tips
- Use a unique, incrementing
idfor each request to correlate responses. - Log all incoming and outgoing messages in development – use a middleware or wrapper.
- When implementing a server, store the client’s capabilities in the context to adjust behavior (e.g., if the client doesn’t support images, don’t send them).
- For HTTP+SSE, keep the SSE connection open for the entire session; close it only when done.
- Test with the official MCP inspector or a simple client script before integrating into a full app.
Troubleshooting
- No response to
initialize: Check if the server is running, the transport is correct, and the protocol version is supported. For stdio, ensure the executable path is correct and permissions allow execution. - Error -32600 (Invalid Request): Your JSON-RPC structure is malformed. Validate with a JSON linter and ensure all required fields are present.
- Error -32000 (Server error): The server encountered an internal error. Check server logs for stack traces.
- SSE connection drops: The server may have a timeout. Implement heartbeat or reconnection logic. Also check network proxies that may buffer or close long-lived connections.
- Request works in one client but not another: The server may be checking client capabilities from the
initializerequest. Ensure both clients declare the same capabilities.
Frequently Asked Questions
Q: What exactly is the server context?
A: It’s an object that holds session-specific data: the protocol version, client capabilities, the transport instance, and any state like registered resources. It’s passed to request handlers so they know who they’re talking to.
Q: Can I use MCP over plain HTTP without SSE?
A: Yes, with the newer Streamable HTTP transport, you can send POST requests and receive responses directly in the same connection. SSE is still used for server-initiated events.
Q: How do I know which protocol version to use?
A: The latest version is listed in the MCP specification. The initialize request includes your version, and the server responds with its highest supported version. If they don’t match, the server may reject the connection.
Q: Is it possible to have multiple concurrent sessions?
A: Yes, each client connection creates a separate session with its own context. The server must manage these independently, often using a map of session IDs to contexts.
Q: What’s the difference between a request and a notification?
A: A request has an id and expects a response. A notification has no id and the server does not reply. Notifications are used for events like notifications/initialized or progress updates.
Q: How do I handle large payloads, like reading a big file?
A: MCP doesn’t have built-in pagination for resources, but you can split the content into multiple messages or use a tool that returns a reference. Alternatively, you can implement custom pagination in your tool’s arguments.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Valheim Server Not Showing in Community List Fixes
- DisplayPort vs HDMI: Which Cable Should You Use?
- Soundbar vs 2.1 Speaker System: Which Is Better?
- Mouse Moving on Its Own? Causes & Fixes
- Slack Channels for Construction Projects: Setup Guide
- Surface Won’t Turn On: Quick Fixes1.8 h