Try Bifrost Enterprise free for 14 days. Request access

The MCP Protocol: Transports, Primitives, and the Message Lifecycle

The MCP Protocol: Transports, Primitives, and the Message Lifecycle

TL;DR

  • The MCP protocol is a JSON-RPC 2.0 standard with two transports, stdio and Streamable HTTP, and three server primitives: tools, resources, and prompts.
  • Revision 2026-07-28 made MCP stateless: the initialize handshake is gone, and every request carries its protocol version and client capabilities in _meta.
  • A tool call is two requests, tools/list and tools/call, plus an optional input_required round trip when the server needs user input.
  • The specification defines message shapes and authorization and leaves policy, quota, and audit to the host and the infrastructure around it.
  • Bifrost fills that gap as both MCP client and MCP server, adding per-key tool allow-lists, guardrails, and request logs on every tools/call.

The MCP protocol (Model Context Protocol) is an open standard, built on JSON-RPC 2.0, that defines how an LLM application discovers and calls tools, reads resources, and fetches prompt templates from external servers. The specification is precise about message shapes and silent on who may call which tool, how often, and with what record. Bifrost, the open-source AI gateway built by Maxim AI, implements the protocol on both sides of the connection and adds the policy, quota, and audit layer the specification leaves to the operator. This reference covers revision 2026-07-28, with message examples taken from it, and closes on where the protocol stops and the MCP gateway begins.

What Is the MCP Protocol?

The MCP protocol is a client-host-server protocol in which a host application (an IDE, a chat client, an agent runtime) creates one MCP client per MCP server, and each client exchanges JSON-RPC 2.0 messages with exactly one server. Servers expose context and capabilities; the host owns the model, the conversation, and security decisions.

Anthropic published the protocol in November 2024 as an open standard for connecting AI assistants to data and tools; it has since moved through five revisions. Three roles matter in every deployment:

  • Host: the LLM application that launches clients, enforces consent, and aggregates context across servers.
  • MCP client: a connector inside the host, paired 1:1 with one server, that attaches version and capabilities to every request.
  • MCP server: a local process or remote service that exposes tools, resources, and prompts without seeing the whole conversation.

Every message is a JSON-RPC 2.0 request, result, error, or notification. Requests carry a non-null string or integer id, results carry a resultType ("complete" or "input_required"), and notifications carry no id. Errors reuse the standard JSON-RPC codes plus a block from -32020 to -32099 reserved for the specification. The guide to the Model Context Protocol covers the ecosystem around these roles; this post stays at the wire level, and the MCP integration internals show how Bifrost implements the same roles in Go.

MCP Transports: stdio and Streamable HTTP

An MCP transport is a binding that defines how JSON-RPC messages are framed, delivered, and canceled; protocol semantics are identical on every binding. The current specification standardizes two transports, stdio and Streamable HTTP, lists the older HTTP+SSE transport as deprecated, and permits custom transports that preserve the message format and per-request metadata.

Transport Framing Reply channel Cancellation Status in 2026-07-28
stdio Newline-delimited JSON-RPC over stdin/stdout Shared stdout stream, correlated by id notifications/cancelled Active
Streamable HTTP One POST per JSON-RPC message to a single endpoint JSON object or request-scoped SSE stream Client closes the response stream Active
HTTP+SSE Separate GET stream plus POST endpoint Long-lived GET stream Transport-specific Deprecated since 2025-03-26

On stdio, the client launches the server as a subprocess, writes one JSON-RPC message per line to its stdin, and reads replies from stdout. The server may log to stderr, must never write non-MCP bytes to stdout, and should exit when stdin closes; a crashed server is restarted and in-flight requests retried.

On Streamable HTTP, the server exposes a single POST endpoint such as https://example.com/mcp. The client sends Accept: application/json, text/event-stream and the server chooses per request whether to answer with one JSON object or an SSE stream carrying progress notifications before the final response. Revision 2026-07-28 removed the GET endpoint, the Mcp-Session-Id header, and Last-Event-ID resumability; change notifications now arrive on the response stream of a subscriptions/listen request. Servers must validate Origin to block DNS rebinding.

Bifrost connects to upstream servers over STDIO, HTTP, or SSE connection types; HTTP and SSE reconnects run make-before-break so credential rotation causes no downtime. The transport also shapes how an intermediary sits in the path, covered in MCP proxy server architecture.

The Three MCP Primitives: Tools, Resources, and Prompts

MCP servers expose three primitives: tools, which the model invokes; resources, which the application reads into context; and prompts, which the user selects as templates. Each has a list method, an access method, and a capability flag the server must declare before a client may use it.

Primitive Controlled by List method Access method Capability flags
Tools The model tools/list tools/call listChanged
Resources The application resources/list, resources/templates/list resources/read listChanged, subscribe
Prompts The user prompts/list prompts/get listChanged

A tool definition carries a name (1-128 characters, unique within the server), a description, an inputSchema in JSON Schema 2020-12, an optional outputSchema, and annotations that clients must treat as untrusted. A resource is addressed by URI, such as file:///project/src/main.rs, and read back as text or a base64 blob with a MIME type. A prompt is fetched by name with arguments and returns model-ready messages.

Two facts matter for anyone aggregating servers. First, tool names are unique only within one server, so a gateway merging several servers must prefix names to avoid collisions; Bifrost exposes aggregated tools as <server>-<tool> for this reason. Second, since 2026-07-28 every list result carries ttlMs and cacheScope, and servers should return tools in deterministic order so prompt caches hit. Client-side features sit outside these primitives: sampling and roots are now deprecated, while elicitation remains the mechanism for asking the user a question mid-call. Bifrost's tool execution flow treats tool calls returned by the model as suggestions until an explicit execute call, the human-in-the-loop model the specification recommends.

Capability Negotiation and Protocol Versions

Capability negotiation in MCP is how a client and server agree on the protocol version and the optional features each side supports. In revision 2026-07-28 there is no handshake: every request declares its version and client capabilities in _meta, and the server accepts or rejects each one independently.

Servers advertise capabilities through the mandatory server/discover method, which a client may call first or skip and handle a version error inline:

{
  "jsonrpc": "2.0",
  "id": "discover-1",
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": "discover-1",
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": { "tools": {}, "resources": {} },
    "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "ExampleServer", "version": "1.0.0" } },
    "ttlMs": 3600000,
    "cacheScope": "public"
  }
}

If the server does not implement the requested version, it returns error -32022 (UnsupportedProtocolVersion) with a supported list and the client retries with one of those versions. A request that depends on an undeclared client capability is rejected with -32021, and optional extensions are negotiated through an extensions map inside the capabilities object.

Revisions 2025-11-25 and earlier are now legacy: they open a session with an initialize request carrying protocolVersion, capabilities, and clientInfo, receive the server's capabilities in the result, then send notifications/initialized. Most deployed servers still speak this era, so a dual-era client probes stdio servers with server/discover and falls back to initialize on an unrecognized error, and inspects the body of a 400 Bad Request on HTTP. Bifrost tracks each upstream server through its connection states and lifecycles, so a dead credential is surfaced rather than retried. Only a gateway negotiates on the client's behalf across many servers, one of the differences between an MCP gateway, an MCP proxy, and an MCP server.

How Does an MCP Tool Call Work End to End?

An MCP tool call is a tools/list request to discover definitions, a model decision inside the host, a tools/call request carrying the chosen name and arguments, and a result containing content blocks. On Streamable HTTP, each request is its own POST, with headers mirroring the body so intermediaries can route without parsing JSON.

Discovery returns definitions the host injects into the model's context:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "tools": [{
      "name": "get_weather",
      "description": "Get current weather information for a location",
      "inputSchema": {
        "type": "object",
        "properties": { "location": { "type": "string" } },
        "required": ["location"]
      }
    }],
    "ttlMs": 300000,
    "cacheScope": "public"
  }
}

Invocation is a tools/call POST. The headers MCP-Protocol-Version, Mcp-Method, and Mcp-Name must match the body or the server answers 400 with a HeaderMismatch error (-32020):

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "location": "Seattle, WA" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "content": [{ "type": "text", "text": "Current weather in Seattle: 58°F, overcast" }],
    "isError": false
  }
}

Tool failures are reported inside the result with isError: true so the model can read them; protocol failures are JSON-RPC errors. When a server needs more input, it returns resultType: "input_required" with an inputRequests map (for example an elicitation/create form) and an opaque requestState; the client gathers the input and retries the call with inputResponses under a new id. Authorization on HTTP follows OAuth 2.1 with the MCP server as resource server, the subject of MCP authentication and OAuth 2.1 patterns.

Bifrost exposes the same sequence at its own /mcp endpoint: tools/list returns only the tools the caller's virtual key allows, and tools/call is checked against that allow-list again at execution. When Bifrost runs the model loop, Agent Mode gates each call between turns; when a host such as Claude Desktop or Cursor connects to Bifrost as a server, the host's approval UI decides; the MCP gateway mode reference covers both.

MCP vs API: What the Protocol Standardizes

MCP differs from a conventional API in that it standardizes discovery, schema exchange, and invocation across every server, whereas a REST API defines a bespoke contract per service that a developer integrates by hand. The model, not the developer, chooses which tool to call at runtime from the published descriptions.

Concern Conventional REST API MCP protocol
Discovery Read the docs or an OpenAPI file tools/list, resources/list, prompts/list at runtime
Contract Per-service endpoints and payloads Uniform JSON-RPC methods with JSON Schema inputs
Caller Application code written in advance The model, selecting tools from published definitions
Authorization Anything the vendor picked OAuth 2.1 profile on HTTP; environment credentials on stdio

An MCP server therefore wraps an existing API once, and any MCP-capable host can use it without new integration code. The trade is that tool definitions consume context tokens on every turn, which is why deployments with dozens of servers move to a gateway that curates what the model sees. The Model Context Protocol guide covers where MCP fits alongside function calling and RAG; the MCP gateway resource page covers the token-cost side.

What the MCP Specification Leaves Out: Policy, Quota, and Audit

The MCP specification defines transports, message shapes, primitives, version negotiation, and an OAuth 2.1 authorization profile; it does not define access policy, usage quotas, or audit records. Those concerns are assigned to the host ("enforces security policies and consent requirements") and, in practice, to the infrastructure between hosts and servers. Three gaps stand out at scale:

  • Policy: tools/list may vary by the credentials presented, but nothing standardizes who administers that mapping, how a team-wide allow-list is expressed, or how one tool is blocked for one group and permitted for another. Argument inspection before execution has no protocol hook.
  • Quota: there is no budget, rate limit, or cost concept in the schema. A tool call is free from the protocol's point of view, even when it triggers a paid API downstream.
  • Audit: the protocol carries OpenTelemetry traceparent in _meta and deprecates its own logging utility, but no message records who called which tool with which arguments, or who changed an allow-list.

Each gap is by design; the specification keeps servers "extremely easy to build" by pushing control to the host. The host, however, is a desktop app or coding agent on an engineer's laptop, and an enterprise cannot rely on hundreds of individually configured hosts to enforce one policy. That is the case for an MCP gateway as the control point and for MCP guardrails and tool-level policy enforcement at the execution boundary. Regulated industries also need the enterprise deployment controls (VPC isolation, RBAC, signed audit events) the specification never mentions.

How Bifrost Fills the Gap as MCP Client and MCP Server

Bifrost acts as an MCP client toward every upstream server and as an MCP server toward every host, so the policy, quota, and audit layer lives in one place for both directions of traffic. Hosts connect to a single /mcp endpoint, Bifrost aggregates the tools of every connected server, and each tools/list and tools/call passes through governance before reaching an upstream.

Gap in the specification Bifrost mechanism Where it applies
Policy Virtual keys with per-client, per-tool allow-lists, deny-by-default tools/list and tools/call
Policy Virtual MCPs: curated bundles served at /mcp/<slug> Host-facing endpoint
Policy MCP guardrails: CEL rules on mcp_client, mcp_tool, mcp_arguments Before and after execution
Quota Budgets and rate limits on the same virtual key; expired keys fail closed Every request
Audit Request logs for LLM and MCP calls; signed audit logs for admin changes Runtime and configuration

On the client side, Bifrost connects upstream over STDIO, HTTP, or SSE with six MCP authentication types: none, headers, oauth, per_user_oauth, per_user_headers, and token_exchange. Per-user modes store each end user's credential against their identity; token exchange persists nothing.

On the server side, MCP tool filtering means a key with no MCP configuration sees no tools except those from clients marked allow-by-default, and an x-bf-mcp-include-tools header can narrow a request but never widen it. Guardrail rules inspect or redact arguments before a tool runs and the result after, and LLM and MCP log entries carry captured request headers as metadata under gateway observability.

For deployments with many servers, Code Mode replaces hundreds of tool definitions in context with four meta-tools and a sandbox where the model writes Python. At 508 tools across 16 servers, input tokens fell from 75.1M to 5.4M (92.8 percent) with a 65/65 pass rate; the mechanism is explained in code mode in the Bifrost MCP gateway and the full numbers in the MCP gateway benchmark writeup.

The gateway underneath adds 11 microseconds of overhead per request at 5,000 requests per second in the published Bifrost benchmarks and reaches 25+ providers and 10,000+ models through one OpenAI-compatible API.

Frequently Asked Questions

How is MCP different from API?

An API is a per-service contract that a developer integrates in advance; MCP is one protocol that lets a model discover and call any server's tools at runtime through tools/list and tools/call. MCP servers typically wrap an existing API, so the two are complementary: the API does the work and the MCP protocol makes it discoverable by a model.

Does ChatGPT use MCP?

Yes. OpenAI supports remote MCP servers as connectors in ChatGPT, in Codex, and through its API, per the OpenAI MCP documentation. Claude apps, Cursor, and most coding agents are MCP hosts too, so a gateway such as Bifrost can serve all of them from one governed /mcp endpoint.

What is MCP vs RAG?

RAG (retrieval-augmented generation) is a technique for injecting retrieved documents into a prompt; MCP is a protocol for connecting a model to tools, resources, and prompts. A RAG pipeline can be exposed as an MCP tool or resource, so MCP is the transport and RAG is one workload it carries. Neither replaces the other.

Who invented MCP for AI?

Anthropic published the Model Context Protocol in November 2024 as an open standard with an open-source specification and SDKs. It is now maintained through a public SEP (Specification Enhancement Proposal) process with a twelve-month deprecation policy. The JSON-RPC 2.0 specification it builds on predates MCP by more than a decade.

What changed in the 2026-07-28 MCP specification?

The 2026-07-28 revision removed the initialize handshake and protocol-level sessions, moved version and capabilities into per-request _meta, added the mandatory server/discover method, replaced server-initiated requests with input_required results and the HTTP GET stream with subscriptions/listen, and deprecated roots, sampling, and logging. The full list is in the specification changelog.

Does the MCP specification define rate limits or audit logs?

No. The specification defines message shapes, transports, primitives, and an OAuth 2.1 authorization profile, and assigns policy and consent to the host. Budgets, rate limits, allow-lists, and audit records come from the infrastructure around the protocol; in Bifrost they attach to the virtual key every MCP request presents.

Getting Started with Bifrost as Your MCP Gateway

The MCP protocol gives every host and server one wire format; a gateway gives the organization one place to decide what travels over it. Bifrost speaks the protocol as client and server and applies allow-lists, guardrails, budgets, and logs to every tools/call with microsecond overhead. To see how Bifrost governs MCP traffic across your hosts and servers, book a demo with the Bifrost team, or start from the Bifrost resources hub.