Skip to main content
Blog

MCP under the hood: transports, connections and returns

Updated at September 19, 2026

MCP
APIs
AI
Protocols

A direct explanation of MCP, JSON-RPC, stdio, Streamable HTTP and how they relate to REST API, GraphQL, WebSocket and WebRPC.

MCP means Model Context Protocol, an open protocol for connecting AI applications to servers that provide data and operations. An MCP client inspects a catalog of tools, reads resources and calls an operation with validated arguments. The contract stays the same. The server can access a database, a REST API or local files behind that same interface.

The architecture has three roles. The host talks to the model, such as Claude Code, and it keeps an MCP client for each connection. That client talks to an MCP server, which informs the client of its capabilities and executes requested operations. The server can be a local process or a remote service. It runs on a machine that receives calls and returns responses.

flowchart LR
  accTitle: MCP transports
  accDescr: Comparison of stdio and Streamable HTTP for carrying JSON-RPC messages to an MCP server.
  H[Host<br/>Claude Code] --> C[MCP Client]
  C -->|JSON-RPC| T1[stdio]
  C -->|POST /mcp| T2[Streamable HTTP]
  T1 --> S[MCP Server]
  T2 --> S

The parts of the communication

JSON-RPC is the format of each message. It defines fields such as jsonrpc, id, method, params, result and error, and the client uses these to match a response to the right request.

Over HTTP, the JSON-RPC message goes in the request body, also called the payload. With stdio, the same object travels through stdin. The object contains the fields that organize the communication, while the operation data lives inside params or result.

The transport carries the message to the other process. MCP defines transports such as stdio and Streamable HTTP, and in both cases the message follows the JSON-RPC format.

A call to a getPokemon tool could be sent to the server like this:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "getPokemon",
    "arguments": {
      "nameOrNumber": "25"
    }
  }
}

The method identifies the MCP operation, params carries the tool name and its arguments, and id connects the response to the request. The transport only delivers the message to the server.

The server keeps the same id in the return and places the result in result:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Pikachu is 0.4 m tall and weighs 6 kg."
      }
    ],
    "isError": false
  }
}

MCP organizes returned content in content, which can contain text or other types defined by the protocol. isError: true means the tool failed even though the message reached the server correctly.

The call sequence

The client starts the connection and negotiates the server’s capabilities. It then asks for the available tools, gives their schemas to the model and turns the model’s choice into an MCP call.

sequenceDiagram
  accTitle: Sequence of an MCP call
  accDescr: Sequence of an MCP call between the client, model and server.
  participant C as MCP Client
  participant M as Model
  participant S as MCP Server
  C->>S: initialize
  S-->>C: capabilities
  C->>S: tools/list
  S-->>C: tools and schemas
  C->>M: prompt and schemas
  M-->>C: tool_use
  C->>S: tools/call
  S-->>C: content or error
  C->>M: tool_result

The sequence has these steps:

  1. initialize negotiates the connection version and capabilities
  2. tools/list asks the server for its tools and schemas
  3. The client gives the prompt and schemas to the model
  4. The model chooses a tool and returns tool_use to the client
  5. The client sends tools/call to the server
  6. The server returns content or an error
  7. The client gives tool_result to the model

tool_use is the model’s decision. The client turns that decision into tools/call, the request that reaches the server. The server validates the arguments, executes the function and builds the return that goes back to the model as tool_result.

Stdio for local processes

With stdio, the host starts the server as a subprocess and keeps the standard channels open:

  • stdin carries messages from the client to the server
  • stdout carries messages from the server to the client
  • stderr receives logs and diagnostics

The mcp-pokeapi server uses the official MCP SDK and starts with StdioServerTransport:

const server = new McpServer({
  name: 'mcp-pokeapi',
  version: '1.0.0',
})

const transport = new StdioServerTransport()
await server.connect(transport)
console.error('mcp-pokeapi ready over stdio')

stdout is reserved for JSON-RPC, so a console.log mixed into the messages can break the communication. Logs go to stderr.

This model works well when the host and server are on the same machine because the host controls the process and does not need to publish a TCP port. Sharing the server between machines requires another transport.

Streamable HTTP for remote servers

Streamable HTTP uses an MCP endpoint, usually /mcp, that accepts POST and can respond with JSON or an SSE stream. The client sends each JSON-RPC message in a POST. The server returns a JSON object right away or transmits events, when an operation produces messages over time.

In mcp-pokeapi, the transport remains stdio inside the container published as psatin/mcp-pokeapi:1.0.0. An HTTP server needs a different implementation with a listener, a port and a bind configuration.

The address 0.0.0.0 is commonly used inside a container that receives traffic through a published port, while a local process should prefer 127.0.0.1. A remote endpoint also needs authentication, TLS and Origin validation.

The HTTP+SSE format with /sse and /messages appears in older servers. New clients use Streamable HTTP at /mcp, which replaced the legacy HTTP+SSE transport in the current specification.

Returns and errors

A short response can use application/json:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "ok"
      }
    ]
  }
}

An execution that produces notifications can use text/event-stream, keeping JSON-RPC messages inside SSE events. HTTP organizes the connection, while MCP defines the meaning of those messages.

Errors appear at different points in the chain:

  1. A timeout and a refused connection indicate a transport failure
  2. Invalid JSON and an unknown method indicate a protocol failure
  3. A missing Pokémon indicates a tool failure, which can return isError: true

In mcp-pokeapi, the Zod schema rejects an empty input before the PokeAPI call. An unknown name returns a tool error message, and a fetch timeout also appears as isError: true.

MCP and REST API

With a REST API, the contract comes down to endpoints, HTTP verbs, parameters and authentication rules the client application already knows in advance. A client calls GET /books/123 or POST /loans because the interface was documented and the code was written for it.

MCP adds a common contract for AI clients. The client can discover tools, resources and prompts when it opens the connection, without knowing the service’s internal endpoints in advance.

AspectREST APIMCP
GoalIntegrate applications and servicesExpose context and operations to AI clients
DiscoveryPublished documentation or schemaCatalogs queried during the connection
ContractEndpoints, verbs, payloads and authentication vary by APIJSON-RPC, MCP primitives and schemas follow a common standard
CallGET, POST, PUT, DELETE and HTTP responsestools/call, resources/read, prompts/get and MCP returns
ImplementationReceives calls from applicationsCan wrap one or more existing APIs

An MCP server can receive tools/call for getOrder, validate the arguments and call GET /orders/{id} internally. The AI client sees a tool with a known schema while the order API continues serving the website and mobile app.

This composition turns the MCP server into an adapter for AI clients. The existing API remains useful, and the model receives an interface it can discover and call with structured arguments.

Where GraphQL, WebSocket and WebRPC fit

flowchart LR
  accTitle: Layers and communication alternatives
  accDescr: The diagram separates the contract, transport and web alternatives when comparing MCP, JSON-RPC, HTTP, GraphQL, WebSocket and WebRPC.
  C[Contract<br/>MCP and JSON-RPC<br/>tools, resources, prompts] --> T[Transport<br/>stdio or Streamable HTTP<br/>path for bytes]
  T --> A[Web alternatives<br/>REST, GraphQL,<br/>WebSocket and WebRPC]

HTTP defines methods, headers, status codes and bodies. REST organizes resources with URLs and HTTP verbs. GraphQL defines queries and mutations from a schema, usually over HTTP.

WebSocket keeps a persistent bidirectional channel. The channel handles continuous communication between client and server, while the application still needs to define operation names, input schemas and error formats.

WebRPC makes a remote call look like a function call and generates clients and servers from a schema. The difference is the goal. WebRPC connects applications to each other, while MCP standardizes the connection between AI hosts and servers that provide tools, resources and prompts.

GraphQL, WebRPC or REST can sit behind an MCP server. A tool calls the internal service, converts the response to MCP format and gives the AI client a return that follows the same contract as other tools.

Docker distributes the server

The mcp-pokeapi server was published for linux/amd64 and linux/arm64 as psatin/mcp-pokeapi:1.0.0:

docker run -i --rm psatin/mcp-pokeapi:1.0.0

The -i flag keeps standard input open. Without it, a stdio server can exit after detecting that stdin is unavailable. The first time I ran the container without that flag, the server quit before any log line showed up, and it took me a while to realize the missing -i was the cause.

Docker handles distribution and process isolation. It does not turn stdio into HTTP; an HTTP server still needs to start a listener and publish a port.

A criterion for choosing

Stdio works well when the host and server are on the same machine and the host can start the process. Streamable HTTP comes in when the server needs to serve remote clients, share an instance between them or sit behind a proxy and authentication.

For contracts between an application’s own services, REST, GraphQL or WebRPC still fit. MCP makes sense when an AI client needs to discover operations and offer them to the model with schemas. When the system needs a persistent bidirectional flow, use WebSocket.

The transport handles the path for the bytes. JSON-RPC shapes the message. MCP determines the operations the AI client can discover and call.

References