Research date · August 1, 2026
Generated with AI

Model Context Protocol — How It Works Locally and in the Cloud

A protocol-level reference for the Model Context Protocol (MCP): the wire model, the two current transports (stdio and Streamable HTTP), the stateless request model, header-based routing, discovery, caching, Multi Round-Trip Requests, subscriptions, the optional OAuth 2.1 authorization profile, and how the same protocol powers local AI clients (Claude Code, VS Code, Cursor) and server-hosted agents. Every protocol example is verified against the official MCP specification revision 2026-07-28.

Spec revision in this paper · 2026-07-28. The final specification shipped on July 28, 2026. It replaces MCP's protocol-level sessions and initialisation handshake with a stateless, self-describing request model. The changes most consequential to this paper are:
  • The initialize / notifications/initialized handshake is removed. Protocol version and client capabilities now travel in _meta on every request; clients SHOULD also include their identity. Servers MUST implement server/discover, although calling it is optional.
  • The Mcp-Session-Id header and protocol-level session are removed. Any request can land on any server instance; cross-call application state is carried with explicit server-minted handles.
  • Streamable HTTP is POST-only. Every request carries MCP-Protocol-Version and Mcp-Method; Mcp-Name is additionally required for tools/call, resources/read, and prompts/get. Tool schemas can mirror selected primitive arguments into validated Mcp-Param-* headers.
  • Multi Round-Trip Requests (MRTR) replace direct server-initiated requests. A server returns resultType: "input_required", and the client retries the original operation with inputResponses. All ordinary results now require resultType: "complete".
  • server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read results carry ttlMs and cacheScope; tool order SHOULD be deterministic. Tool schemas now accept the full JSON Schema 2020-12 vocabulary.
  • The extensions framework is formalised, with Tasks now an official extension. Roots, Sampling, Logging, Dynamic Client Registration, and the legacy HTTP+SSE transport are deprecated under a policy with a minimum twelve-month deprecation window.
The final 2026-07-28 changelog is the source of truth for migrations from 2025-11-25.

1 · The wire model

MCP is an open, JSON-RPC 2.0–based protocol with a client–host–server architecture. A host (typically an LLM application or agent runtime) creates and manages one MCP client per server, and each client communicates with exactly one MCP server (a process that exposes tools, resources, and prompts). The protocol is transport-agnostic; the current revision (2026-07-28) standardises two transports: stdio and Streamable HTTP.

Host application local app · agent runtime LLM (Claude · GPT · Gemini · …) MCP client library one client instance per server Transport stdio (local subprocess) or Streamable HTTP (remote) MCP Server tools · resources · prompts tool: get_weather(loc) tool: search_db(query) local process or hosted service JSON-RPC request JSON-RPC request response · notification response · notification
Figure 1 · MCP wire model: client and server exchange JSON-RPC 2.0 messages over a transport. Blue arrows show client requests; amber dashed arrows show server responses and subscribed or request-scoped notifications.
  1. 1
    Host and client

    The host is the application that hosts the LLM and orchestrates tool use — Claude Code, VS Code (Copilot Chat), Cursor, or any custom agent runtime. The host creates one MCP client per server; each client speaks the protocol to exactly one server and keeps the servers isolated from one another.

  2. 2
    Server

    A local subprocess or remote service that exposes tools, resources, and/or prompts. A host typically runs several servers concurrently — one client per server — and routes each operation to the client that owns it.

  3. 3
    Transport

    Either stdio (server runs as a child process; messages flow through stdin/stdout) or Streamable HTTP (server is a network endpoint reached over HTTP POST, with Server-Sent Events for streaming responses and subscribed notifications).

  4. 4
    Messages

    Three JSON-RPC 2.0 message types remain: requests (have an id and expect a response), responses (match a request by id), and notifications (have no id and receive no response). Their delivery is transport-specific.

Source: modelcontextprotocol.io · Specification 2026-07-28 · Architecture · Base Protocol · Versioning

2 · The stdio transport

Under the stdio transport, the client launches the server as a child process and exchanges newline-delimited JSON-RPC messages over the server's standard input and standard output. The server may write UTF-8 strings to standard error for logging. This is the default transport for local, single-user tools (filesystem access, local databases, local scripts).

Single host machine — both processes run as the same user Parent process Client (Claude Code, IDE, …) spawn(command, args, env) write(stdin) · read(stdout) shutdown: close stdin → TERM → KILL Child process MCP server (e.g. npx mcp-server-filesystem) read(stdin) — one JSON-RPC per line write(stdout) — one JSON-RPC per line write(stderr) — free-form log text stdin · JSON-RPC requests stdout · JSON-RPC responses & notifications stderr · log text (optional)
Figure 2 · stdio transport: the client spawns the server as a child process. Each JSON-RPC message is a single line of UTF-8 JSON terminated by \n and MUST NOT contain embedded newlines. stderr is the only channel available for free-form server logs.
  1. 1
    Launch

    The client invokes the server's command with args and an optional env map. The server inherits the client's working directory unless the client overrides it. The stdio channel itself opens no network socket, though the server may of course reach network services of its own.

  2. 2
    Framing

    Each JSON-RPC message is one UTF-8 line terminated by \n. Embedded newlines are forbidden, so multi-line content must be JSON-escaped. There is no separate header frame; the line itself is the message.

  3. 3
    Logging

    The server MAY write any UTF-8 text to stderr; the client MAY capture, forward, or ignore it, and SHOULD NOT read stderr output as a sign of failure. This is the only out-of-band channel — anything written to stdout MUST be a valid JSON-RPC message.

  4. 4
    Shutdown

    The client first closes the server's input stream and waits for it to exit. If it remains alive, Unix clients send SIGTERM, wait again, and use SIGKILL only as the forced final fallback.

Source: modelcontextprotocol.io · Specification 2026-07-28 · stdio

3 · The Streamable HTTP transport

Streamable HTTP is the standardised transport for remote MCP servers. The server exposes one POST-only endpoint. Every JSON-RPC request is a separate HTTP POST, and the response is either a single JSON object or a Server-Sent Events (SSE) stream scoped to that request. The protocol has no HTTP-level session and no standalone GET stream.

Client host Server host (anywhere reachable over HTTP) MCP Client local client · hosted agent HTTP client (POST writer) JSON / response-SSE reader Auth: Bearer <access_token> (when authorization is enabled; see §7) subscriptions/listen reader (long-lived POST response) HTTP / HTTPS MCP Server single endpoint, e.g. /mcp POST /mcp (client → server) JSON or request-scoped SSE Stateless request handling no protocol session or sticky routing subscriptions/listen SSE (opted-in change notifications) POST · version + method/name headers · JSON-RPC request 200 OK Content-Type: application/json (single response) — OR — SSE: related notifications, then final response POST subscriptions/listen · notification filter SSE · acknowledged subscription + opted-in changes
Figure 3 · Streamable HTTP in revision 2026-07-28. Every operation is its own POST. A normal response is JSON or a request-scoped SSE stream; long-lived change notifications use the SSE response to subscriptions/listen.
  1. 1
    One POST endpoint and routable headers

    The server exposes one path (commonly /mcp) that accepts POST. Every request includes Accept: application/json, text/event-stream, MCP-Protocol-Version, and Mcp-Method. Selected named operations also include Mcp-Name. A tool can mark safe primitive inputs with x-mcp-header; conforming HTTP clients mirror them as Mcp-Param-* headers. Sensitive values should never be marked this way.

  2. 2
    Two response shapes

    A simple result uses Content-Type: application/json. A streaming result uses SSE for notifications related to that request — such as progress — followed by the final JSON-RPC response. Streams are not resumable: if one breaks, the client reissues the operation with a new JSON-RPC id.

  3. 3
    No protocol session

    Every request carries its protocol version and client capabilities in params._meta. Any instance can process it. Applications that need cross-call state return an explicit handle and accept that handle on later calls.

  4. 4
    Subscriptions

    A client that wants tool-list, prompt-list, resource-list, or resource-update notifications sends subscriptions/listen with an explicit filter. Its POST response remains open as an SSE stream.

  5. 5
    Multi Round-Trip Requests

    Sampling, elicitation, and roots are no longer separate requests sent by the server. The server returns resultType: "input_required"; the client gathers the requested input and retries the original method with a new JSON-RPC id.

  6. 6
    Endpoint security and optional authorization

    Servers MUST validate a present Origin header, and local servers SHOULD bind only to 127.0.0.1. A protected server can use the OAuth 2.1 profile in §7 and validate a resource-bound Bearer token; deployments protected by mTLS, workload identity, or private-network policy need not use that profile.

Backward compatibility. Revisions 2025-03-26 through 2025-11-25 used sessions, a standalone GET stream, direct server requests, and resumable SSE. None belongs to 2026-07-28. Compatibility clients may implement both models, but must not apply retired semantics to requests negotiated as 2026-07-28.

Source: modelcontextprotocol.io · Specification 2026-07-28 · Streamable HTTP · Multi Round-Trip Requests · Subscriptions

4 · Stateless request flow

MCP no longer has a protocol-level connection lifecycle. Each request is self-describing and independent: the client may first call server/discover, list the server's tools, and invoke one of them. Every request repeats the protocol version and client capabilities in _meta; no handshake or hidden session state is created.

Client Server PHASE 1 server/discover (optional request id:1) params._meta: protocolVersion, clientCapabilities, clientInfo discover response (id:1) result: supportedVersions, capabilities, ttlMs, cacheScope · serverInfo in _meta No handshake · no session · every later request repeats _meta PHASE 2 tools/list (request id:2) params: optional cursor + required request _meta tools/list response (id:2) result: resultType, tools[], ttlMs, cacheScope PHASE 3 tools/call (request id:3) params: name="get_weather", arguments:{ location:"NYC" } (optional) notifications/progress on this request's response stream tools/call response (id:3) result: resultType="complete", content[]
Figure 4 · A typical stateless MCP flow. Discovery is optional; a client may send tools/list or tools/call immediately and handle an unsupported-version error. Every blue request independently declares its version and client capabilities.
  1. 1
    Per-request identity

    Every request carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta. Clients SHOULD also send io.modelcontextprotocol/clientInfo; servers SHOULD identify themselves in each result's _meta.

  2. 2
    Optional server discovery

    Servers MUST implement server/discover. Clients MAY use it to retrieve supported versions, server capabilities, identity, and instructions before any other operation, but can instead call an operation directly and negotiate from UnsupportedProtocolVersionError.

  3. 3
    Cacheable discovery and reads

    server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read return ttlMs and cacheScope on every complete result. Tool order SHOULD be deterministic so clients and upstream model prompts can be cached reliably.

  4. 4
    Invocation

    The client sends tools/call with a name and arguments. An ordinary result has resultType: "complete". A server that needs elicitation, sampling, or roots returns resultType: "input_required", and the client retries the original operation with the requested input.

  5. 5
    Notifications

    Request-scoped progress or log messages travel before the final response on that request's stream. Independent list-change and resource-update notifications require an opted-in subscriptions/listen request. Notifications have no id and receive no response.

Source: modelcontextprotocol.io · Specification 2026-07-28 · Versioning · Discovery · Caching · Tools

Wire example — discovery, listing, and invocation

These representative exchanges show the required per-request metadata and the new result fields.

1 · Client → Server: optional server/discover

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      }
    }
  }
}

Source: modelcontextprotocol.io · Specification 2026-07-28 · Discovery

2 · Server → Client: server/discover response

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": {
      "resources": {},
      "tools": { "listChanged": true }
    },
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "ExampleServer",
        "version": "1.0.0"
      }
    },
    "instructions": "Weather and resource utilities",
    "ttlMs": 3600000,
    "cacheScope": "public"
  }
}

Source: modelcontextprotocol.io · Specification 2026-07-28 · Discovery

3 · Client ↔ Server: tools/list

// Request
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      }
    }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "tools": [
      {
        "name": "get_weather",
        "title": "Weather Information Provider",
        "description": "Get current weather information for a location",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name or zip code"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "ttlMs": 300000,
    "cacheScope": "public"
  }
}

Source: modelcontextprotocol.io · Specification 2026-07-28 · Server / Tools

4 · Client → Server: tools/call + response

// Request
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "location": "New York" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "ExampleClient",
        "version": "1.0.0"
      }
    }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resultType": "complete",
    "content": [
      {
        "type": "text",
        "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
      }
    ],
    "isError": false
  }
}

Source: modelcontextprotocol.io · Specification 2026-07-28 · Server / Tools

5 · Pattern A — Local AI clients

A local AI application (Claude Code, VS Code, Cursor, and others) acts as the MCP host. It reads a configuration file that lists MCP servers, creates one MCP client per entry, spawns stdio servers as child processes, and sends HTTP requests to remote servers. It combines their tools into one logical catalog, but may load tools on demand or expose a search step instead of placing every definition in every model turn.

User Local client process (Claude Code · VS Code · Cursor) LLM + MCP client library reads mcp.json / settings on startup Config file .mcp.json · .vscode/mcp.json · ~/.cursor/mcp.json { "mcpServers": { … } } or { "servers": { … } } stdio server filesystem npx mcp-server-… (child process) stdio server postgres python my_server.py (child process) Internet — remote MCP servers (Streamable HTTP) GitHub MCP (api.githubcopilot.com/mcp/) type: streamable-http · auth: Bearer tools: list_issues, create_issue, get_pull_request, … Linear MCP · custom org MCP · … any HTTPS endpoint speaking MCP scoped per workspace via project config OAuth 2.1 authorization server issues access tokens (see §7) prompt HTTPS + Bearer (when protected) HTTPS request spawn · stdin/stdout one child process per entry
Figure 5 · The local-client pattern: one application process embeds the MCP client library, spawns local stdio servers as children, and sends HTTPS requests to remote MCP servers. The configuration file lists both kinds together.
  1. 1
    Config-driven

    The application loads a JSON config (location and root key vary by app; see snippets below) that lists every MCP server it should connect to. Servers are typically scoped at three levels: user-wide, workspace/project, and (in Claude Code) enterprise-managed.

  2. 2
    One host, many client–server pairs

    The host creates one MCP client per configured server: each stdio server is a child process, and each remote server is reached over HTTP. Their tools enter one logical catalog. The host disambiguates collisions — often by prefixing names — and may defer large catalogs behind tool search.

  3. 3
    Authorization is per server

    A protected remote server may use the OAuth 2.1 flow in §7, a configured Bearer token, or another client-supported mechanism. Interactive clients generally keep OAuth credentials in secure credential storage and attach the resulting access token to each protected request.

  4. 4
    Requests are per server

    Each client independently discovers or lists its own server, and the host routes tools/call to the client that owns the selected tool. Servers do not see one another, and no shared MCP session is created.

Configuration — Claude Code

Claude Code reads .mcp.json at the project root (or ~/.claude.json for user-wide servers). For the two transports this paper covers, the type field accepts stdio and http — with streamable-http as an alias for http. Claude Code also accepts sse — the legacy HTTP+SSE transport, which remains in the specification in the Deprecated state — and an app-specific ws (WebSocket) transport that sits outside the specification altogether.

{
  "mcpServers": {
    "github": {
      "type": "streamable-http",
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": {
        "Authorization": "Bearer ${GITHUB_TOKEN}"
      }
    },
    "my-db": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@bytebase/dbhub", "--dsn", "${DB_DSN}"],
      "env": { "DB_TIMEOUT": "30000" }
    }
  }
}

Source: code.claude.com/docs/en/mcp · Connect Claude Code to tools via MCP

Configuration — VS Code (Copilot Chat)

VS Code reads .vscode/mcp.json for workspace-scoped servers (or a user-profile mcp.json for user-wide). The root key is servers (not mcpServers); the type field accepts stdio, http, and sse — the last being the legacy HTTP+SSE transport, which the specification now marks Deprecated. VS Code tries the HTTP Stream transport first and falls back to SSE only for a server that does not support it.

{
  "servers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/"
    },
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@microsoft/mcp-server-playwright"]
    }
  }
}

Source: code.visualstudio.com · Add and manage MCP servers · MCP configuration reference

Configuration — Cursor

Cursor reads ~/.cursor/mcp.json (global) or .cursor/mcp.json (project). The root key is mcpServers. Remote entries declare only a url; Cursor infers the HTTP transport when url is present.

{
  "mcpServers": {
    "github": {
      "url": "https://api.githubcopilot.com/mcp/"
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "mcp-server-filesystem", "/path/to/project"],
      "env": { "LOG_LEVEL": "info" }
    }
  }
}

Source: cursor.com/docs/mcp · Model Context Protocol

Naming quirks across clients. Claude Code and Cursor use mcpServers; VS Code uses servers. Claude Code accepts streamable-http as an alias for http; VS Code uses http; Cursor infers HTTP from the presence of url. All three accept the same core stdio fields (command, args, env).
Keep secrets out of project files. The placeholders above assume environment interpolation. Prefer the client's OAuth flow, environment variables, or a dynamic credential helper over committing access tokens, client secrets, or database connection strings.

6 · Pattern B — Server-hosted AI agents

In this pattern, the agent runtime acts as the MCP host: running in a container, a serverless function, or a long-lived service, it creates one MCP client per server. Local stdio servers are less common because the runtime may be ephemeral or may restrict child-process execution. The agent instead connects to one or more remote MCP servers over Streamable HTTP. Those servers may run in the same cluster as the agent or be operated by a third party.

End user or API client Agent cluster (Kubernetes · serverless · VM) Ingress / load balancer (HTTPS) Agent replica 1 LLM + MCP clients Agent replica 2 LLM + MCP clients Agent replica N LLM + MCP clients Internet / private network Internal MCP (same cluster) company KB · CRM · orders DB private mesh · mTLS or OAuth 3rd-party MCP server GitHub · Stripe · Linear · … HTTPS + OAuth 2.1 Bearer Authorization server issues per-resource tokens (RFC 8707) HTTPS MCP over HTTP (private mesh) POST /mcp · Bearer token JSON-RPC response · SSE OAuth 2.1 token exchange
Figure 6 · A horizontally scaled agent service consuming MCP servers. Each replica manages its own MCP client instances and sends self-describing requests to internal and third-party servers. Any request may land on any compatible backend instance.
  1. 1
    Stateless protocol

    Each request carries the protocol version and client capabilities needed to process it, and normally the client's identity as well. No MCP session affinity is required. When a tool needs cross-call application state, it returns an explicit handle that the model passes back as a later tool argument.

  2. 2
    Streamable HTTP is common

    A hosted runtime may not permit child processes and may be cold-started per request, so remote HTTP services are usually easier to operate. A stdio-only server can be deployed behind a conforming Streamable HTTP front end when needed.

  3. 3
    Multiple trust domains

    Internal services may use mTLS, workload identity, or short-lived tokens. Protected third-party servers commonly use the MCP OAuth 2.1 profile with resource-bound access tokens. Authentication choices remain deployment-specific.

  4. 4
    Selection and routing are separate

    The agent runtime aggregates or searches the available tool catalogs; the model or orchestration layer selects a tool. The host then routes that call to the client that owns the tool's server. It need not place every tool definition in every model request.

Source: modelcontextprotocol.io · Specification 2026-07-28 · Changelog (stateless protocol) · Tools · Stateful tools (explicit handles) · Authorization. The deployment commentary about ephemeral runtimes and trust domains is non-normative.

7 · Authorization (Streamable HTTP)

Authorization is optional in MCP. When an HTTP server is protected, implementations SHOULD follow the MCP OAuth 2.1 profile: Protected Resource Metadata (RFC 9728) locates the authorization server, PKCE protects the authorization code, Resource Indicators (RFC 8707) bind tokens to the MCP server, and issuer validation (RFC 9207) prevents authorization-server mix-up. Client ID Metadata Documents are preferred; Dynamic Client Registration is deprecated.

MCP Client Authorization Server MCP Server A · DISCOVERY 1 · POST /mcp (MCP request without a token) 2 · 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource" 3 · GET /.well-known/oauth-protected-resource (RFC 9728) 4 · 200 OK { authorization_servers: […], resource: "https://server.example/mcp" } B · DISCOVERY + AUTHORIZATION 5 · discover AS metadata + obtain client_id CIMD, pre-registration, or deprecated DCR fallback 6 · authorize via browser PKCE code_challenge + resource + scopes 7 · authorization code (+ iss when supported) client validates a present iss vs. recorded issuer 8 · code + verifier + resource access_token (for MCP server URI) C · AUTHENTICATED MCP 9 · POST /mcp · Authorization: Bearer <access_token> · tools/list 10 · 200 OK · tools/list response (server validates audience + scopes)
Figure 7 · The optional MCP authorization profile. The client follows RFC 9728 and authorization-server metadata, obtains a client ID, completes a PKCE-protected authorization-code flow with a resource indicator, validates the issuer when the response carries one, and sends the resource-bound token to the MCP server.
  1. 1
    Protected-resource discovery (steps 1–4)

    An unauthenticated MCP request receives 401 with a WWW-Authenticate challenge pointing at RFC 9728 metadata. The client fetches that document and then the selected authorization server's OAuth or OpenID Connect metadata.

  2. 2
    Client registration (step 5)

    The client obtains a client ID through a Client ID Metadata Document, pre-registration, or — only for backwards compatibility — Dynamic Client Registration. Persisted credentials are keyed by issuer and must not be reused with another authorization server.

  3. 3
    Authorization code + PKCE (steps 6–8)

    The browser request includes a PKCE challenge and an RFC 8707 resource parameter. Before redeeming the returned code, the client validates a present iss against the issuer it recorded from validated metadata, then exchanges the code with the verifier and resource.

  4. 4
    Authenticated MCP (steps 9–10 and onward)

    The client includes Authorization: Bearer … on every protected HTTP request. The server validates that the token was issued for its resource and has sufficient scopes. A 403 insufficient-scope challenge can trigger step-up authorization.

Source: modelcontextprotocol.io · Specification 2026-07-28 · Authorization · Client registration

8 · Authoring an MCP server

The Tier 1 SDKs for revision 2026-07-28 are TypeScript, Python, Go, and C#; Rust supports the revision in beta. The Python and TypeScript v2 examples below use high-level APIs that hide JSON-RPC parsing and derive tool schemas from native types. The same tool definitions are served over stdio or Streamable HTTP by changing only how the server is mounted at startup.

Source: Official MCP 2026-07-28 release announcement

Python v2 — MCPServer

Install with pip install "mcp[cli]". A minimal server exposes one tool and runs over stdio:

from mcp.server import MCPServer

mcp = MCPServer("greeting-server")

@mcp.tool()
def greet(name: str) -> str:
    """Greet someone by name"""
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()              # stdio is the default transport

Source: Official MCP Python SDK v2 · Running your server

To serve the same code over Streamable HTTP, change one line:

if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=8000)
    # server is now reachable at http://localhost:8000/mcp

Source: Official MCP Python SDK v2 · Streamable HTTP

TypeScript v2 — McpServer

Install with npm install @modelcontextprotocol/server zod. A minimal stdio server:

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

const server = new McpServer({ name: "greeting-server", version: "1.0.0" });

server.registerTool(
  "greet",
  {
    description: "Greet someone by name",
    inputSchema: z.object({ name: z.string() }),
  },
  async ({ name }) => ({
    content: [{ type: "text", text: `Hello, ${name}!` }],
  }),
);

const transport = new StdioServerTransport();
await server.connect(transport);

Source: Official MCP TypeScript SDK v2 · README quickstart

Streamable HTTP is not a different transport object in v2. createMcpHandler takes the same registrations wrapped in a factory and returns a web-standard handler. The factory runs once per HTTP request, so a fresh McpServer serves every call and the handler keeps nothing between requests — §4's stateless request model expressed directly in the API:

import { createMcpHandler, McpServer } from "@modelcontextprotocol/server";

const handler = createMcpHandler(() => {
  const server = new McpServer({ name: "greeting-server", version: "1.0.0" });
  server.registerTool(/* …the same registration as above… */);
  return server;
});

Source: Official MCP TypeScript SDK v2 · Serve over HTTP (createMcpHandler replaces v1's per-request StreamableHTTPServerTransport + connect() wiring).

handler.fetch is a standard (Request) => Promise<Response>, so on a web-standard runtime (Cloudflare Workers, Deno, Bun) export default handler is the entire mount. Node frameworks wrap it once with toNodeHandler; thin adapter packages cover Node, Express, Fastify, and Hono:

npm install @modelcontextprotocol/server @modelcontextprotocol/express @modelcontextprotocol/node express
import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { toNodeHandler } from "@modelcontextprotocol/node";

const app = createMcpExpressApp();   // express() + express.json() + DNS-rebinding guards
const node = toNodeHandler(handler);

app.all("/mcp", (req, res) => void node(req, res, req.body));
app.listen(3000);

Source: Official MCP TypeScript SDK v2 · Serve with Express. createMcpExpressApp applies the Host and Origin validation §3 requires, defaulting to a 127.0.0.1 bind; pass allowedHosts when binding to 0.0.0.0.

The tool declaration defines the schema. Python derives the tool schema from type hints and the docstring; TypeScript accepts Standard Schema-compatible libraries such as Zod v4. The generated JSON Schema is returned as the tool's inputSchema in tools/list.

9 · Side-by-side · stdio vs Streamable HTTP

stdio Streamable HTTP
Process model Server is a child process of the client Server is an independent network service
Framing Newline-delimited JSON-RPC on stdin/stdout HTTP POST body (one JSON-RPC message); response is either JSON or SSE
Request metadata Protocol version and client capabilities in every request's _meta The same _meta, plus required version and routing headers
Server-to-client interaction MRTR results; subscribed notifications share stdout MRTR results; notifications use request SSE or subscriptions/listen
Authentication Normally inherits the OS user; credentials come from the environment Deployment-specific; protected servers SHOULD use the MCP OAuth 2.1 profile
Concurrency One client per server process One server may serve many concurrent clients
Protocol sessions None; process lifetime bounds the transport connection None; each request is independent and may reach any instance
Logs stderr Server's own logging stack
Typical use Local tools on a developer machine; CLI integrations Remote / multi-tenant tools; hosted agent runtimes; vendor APIs
Spec section Specification 2026-07-28 · stdio Specification 2026-07-28 · Streamable HTTP

10 · Summary observations

Six factual observations drawn from revision 2026-07-28 and the current Tier 1 SDKs.

PROTOCOL

1 · MCP is stateless JSON-RPC 2.0

Every request declares its protocol version and client capabilities in _meta. There is no initialisation handshake or protocol-level session. Servers implement server/discover, but clients may call any supported RPC first.

TRANSPORTS

2 · Two transports are standardised

Revision 2026-07-28 defines stdio and POST-only Streamable HTTP. The earlier two-endpoint HTTP+SSE transport remains available only during its formal deprecation window.

INTERACTION

3 · Routing, caching, and interactivity are explicit

HTTP headers expose method, operation name, and selected tool arguments to gateways. Cacheable results carry freshness and scope hints. MRTR handles elicitation, sampling, and roots through retryable results; subscriptions/listen carries opted-in change notifications.

DEPLOYMENT

4 · The same protocol serves local hosts and hosted agents

A local host reads a config file, creates one client per server, and merges their tools into one catalog. A hosted runtime does the same per replica and needs no session affinity: any request can land on any compatible instance, and tools needing cross-call state return explicit handles the model passes back as ordinary arguments.

AUTH

5 · Authorization is optional but standardised

Protected HTTP servers SHOULD use the MCP OAuth 2.1 profile: RFC 9728 discovery, PKCE, RFC 8707 resource indicators, and RFC 9207 issuer validation. Client ID Metadata Documents are preferred; Dynamic Client Registration is deprecated.

SDKS

6 · Tier 1 SDKs implement the new revision

Python, TypeScript, Go, and C# support revision 2026-07-28. Python v2's MCPServer and TypeScript v2's McpServer hide JSON-RPC plumbing and derive or accept tool schemas from native type systems.

Sources: 2026-07-28 changelog · stdio · Streamable HTTP · MRTR · Subscriptions · Authorization · SDK docs cited in §8.

11 · Mini-glossary

Terms used throughout this paper.

MCP — Model Context Protocol; an open JSON-RPC 2.0 protocol for connecting LLM applications to tool servers. Spec at modelcontextprotocol.io.
Host — the application that hosts the LLM and orchestrates tool use (Claude Code, VS Code, a hosted agent runtime). It creates and manages one MCP client per server.
Client — the protocol-speaking component created by the host. Each client communicates with exactly one server and maintains the security boundary between servers.
Server — a process that exposes tools, resources, and/or prompts to MCP clients. Can be a local subprocess (stdio) or a network service (HTTP).
Tool — a callable exposed by a server, described by name, description, and a JSON Schema inputSchema. Invoked via tools/call.
Resource — a piece of read-only content (file, document, query result) the server can supply. Listed via resources/list and fetched via resources/read.
Prompt — a parameterised template the server exposes that the client can render and present to the user.
stdio transport — server runs as a child process; messages flow over newline-delimited JSON on stdin/stdout.
Streamable HTTP transport — server exposes one POST endpoint; each response is JSON or request-scoped SSE. Long-lived change notifications use the response to subscriptions/listen.
Per-request _meta — metadata carrying protocol version, client capabilities, and normally client identity on every request.
Mcp-Method / Mcp-Name — required Streamable HTTP headers that mirror the RPC method and, for selected operations, the tool name, prompt name, or resource URI.
x-mcp-header — tool-schema annotation that instructs Streamable HTTP clients to mirror a safe primitive argument into a validated Mcp-Param-* request header.
resultType — required result discriminator: complete for an ordinary result or input_required for an MRTR interim result.
MRTR — Multi Round-Trip Requests; a server returns input_required and the client retries the original method with requested input.
Subscription — an opted-in, long-lived subscriptions/listen response stream for list-change and resource-update notifications.
Cacheable result — a result with ttlMs and cacheScope hints that tell clients how long and where it may be cached.
MCPServer / McpServer — the high-level Python and TypeScript v2 server APIs, respectively.
RFC 8707 (Resource Indicators) — OAuth 2.0 extension that binds an access token to a specific resource server, so a token minted for one MCP server is not accepted by another.
RFC 9728 (Protected Resource Metadata) — discovery document at /.well-known/oauth-protected-resource that tells clients which authorization server protects a resource.
PKCE — Proof Key for Code Exchange (RFC 7636); mandatory in OAuth 2.1 to prevent authorization code interception.
CIMD — Client ID Metadata Document; an HTTPS metadata document used as an OAuth client identifier and preferred over Dynamic Client Registration.

MCP-specific definitions above are taken from the MCP Specification 2026-07-28. RFC numbers refer to the published IETF documents.