Building MCP APIs and Servers
The Model Context Protocol as a control-plane API surface: the JSON-RPC foundation, tools versus resources versus prompts, transports, why tool design is API design, OAuth 2.1 authorization for remote servers, and the security surface that comes with letting a model call your infrastructure.
Overview
MCP as a control-plane API
The Model Context Protocol is an open standard for connecting AI applications to external systems. A platform engineer needs it not because it is an AI topic, but because an MCP server exposing infrastructure actions is a control-plane API with a non-human caller. Every question about authorization, blast radius, schema design and auditability applies with the difficulty turned up.
Integration arithmetic
The specification reaches for USB-C: the value is the connector being the same everywhere. Language Server Protocol did the same for editors and language tooling.
Architecture
- Host — The AI application; creates one client per server
- Client — Holds a dedicated connection to one server
- Server — Exposes capabilities, locally over stdio or remotely over Streamable HTTP
- Data layer — JSON-RPC 2.0 primitives and methods
- Transport layer — Framing and delivery only
Three primitives, by who decides
- Tools — The model invokes them
- Resources — The application controls them
- Prompts — The user selects them
> [!TIP] Getting this split right separates a well-designed server from one that dumps everything into tools and floods the model with choices.
Two eras of connection setup
- Revisions up to 2025-11-25 — Session: initialize request plus initialized notification negotiate version and capabilities once per connection
- Revision 2026-07-28 — Stateless: each request carries version, client identity and capabilities in _meta; mandatory, cacheable server/discover; opt-in notifications via subscriptions/listen; sampling deprecated in favor of calling LLM providers directly
Knowing both signals you read the spec recently rather than a 2024 blog post.
Tool design is API design
A model picks a tool from its name and description and builds arguments from its JSON Schema. It has no source code, no Stack Overflow and no colleague.
- Vague description — Wrong calls
- Free string where an enum belongs — Hallucinated values
- Error that says "failed" — Nothing learned
- "Service not found; known services are X, Y, Z" — A correct retry
- One tool doing five things behind a mode flag — Used wrongly
Authorization
> [!WARNING] Token passthrough is explicitly forbidden. Accepting or forwarding tokens issued for anything else destroys audience separation and creates a confused deputy.
The genuinely new security surface
- Prompt injection through tool output — The instruction comes from a fetched webpage, ticket or log line, and the model does not reliably separate data from instructions
- Confused deputy — In OAuth proxy servers
- State handle hijacking — Now that the protocol is stateless
- SSRF — Through attacker-controlled metadata URLs
- Over-broad tools — Hand a successful injection more power than it should have
> [!IMPORTANT] The failure mode is unlike any normal API: the caller can be talked into calling you.
Key concepts
- Host, client, server
- The host is the AI application coordinating everything; it instantiates one client per server, and each client holds a dedicated connection. The server is the program providing context and capabilities, local or remote. Server does not imply network — a filesystem server launched as a child process over stdio is still a server, which is why the transport question is separate from the participant question.
- Data layer and transport layer
- The data layer is the JSON-RPC 2.0 protocol: primitives, methods, capability semantics. The transport layer is a binding that defines framing, delivery, request metadata carriage, and cancellation, and nothing about meaning. Protocol semantics are identical across transports, so the same handlers work over stdio and HTTP without change.
- Tools
- Model-invoked functions, discovered with tools/list and executed with tools/call. Each carries a name, a title, a description, and an inputSchema in JSON Schema. Results return a content array of typed items. Because the model chooses when to call them, tools are where effects live and therefore where authorization, confirmation, and blast-radius thinking concentrate.
- Resources
- Application-controlled context addressed by URI, discovered with resources/list and fetched with resources/read. The host decides what to place in context rather than the model deciding to fetch it. Schemas, configuration, documentation, and file contents belong here — turning them into tools makes the model reason about retrieving data it could simply have been given.
- Prompts
- User-selected templates, listed with prompts/list and retrieved with prompts/get, typically surfacing as explicit commands in a client. They encode a workflow a person deliberately invokes, which makes them the right home for multi-step procedures with known shape rather than something the model should assemble itself from tool calls.
- Stdio and Streamable HTTP
- Stdio is newline-delimited JSON-RPC over the standard streams of a client-launched subprocess, with logging on stderr; no network surface exists. Streamable HTTP sends each message as a POST to a single MCP endpoint, with replies as a JSON object or a request-scoped SSE stream. The earlier separate HTTP-plus-SSE transport is deprecated.
- Discovery and statelessness
- Revisions through 2025-11-25 used a connection-scoped initialize handshake and initialized notification to negotiate version and capabilities once. Revision 2026-07-28 made the protocol stateless: every request carries protocolVersion, clientInfo, and clientCapabilities in _meta, and a mandatory server/discover request returns supported versions, capabilities, and identity with cacheable ttlMs. Implementations detect and fall back across eras.
- OAuth 2.1 for remote servers
- HTTP-transport servers act as OAuth 2.1 resource servers. A 401 carries WWW-Authenticate with a resource_metadata URL; the client reads Protected Resource Metadata (RFC 9728), discovers the authorization server, uses PKCE, and sends a resource parameter (RFC 8707) naming the canonical server URI. Servers must validate token audience and must not accept or forward tokens issued for other resources. Stdio servers take credentials from the environment instead.
When to use it
- Exposing an existing internal API to AI applications once rather than building a bespoke integration per assistant, IDE, and agent runtime
- Giving an agent scoped, audited access to operational surfaces — read deployment status, query logs, look up service ownership — where the read side carries most of the value and least of the risk
- Standardizing how internal tools are described so a single authorization and audit path serves every AI client instead of each one inventing its own
- Packaging local developer capabilities — filesystem, repository, database schema — as a stdio server where the process boundary is the security boundary and no network exposure exists
- Any case where the caller decides at runtime which capability to invoke, which is what makes machine-readable descriptions and JSON Schema worth the protocol overhead
Interview questions
- Explain MCP to someone who builds APIs. What is it, and what is it not?
- Walk me through the wire protocol from connection to a tool result.
- What makes a good tool definition? Show me a bad one and fix it.
- You are building an MCP server that can restart production services. What is the security model?
- Where does prompt injection enter an MCP system, and what actually mitigates it?
- Compare MCP with REST and gRPC. When would you not use MCP?
Sign up free to read the full topic
More DevOps topics
Start free on Cariara · Coding problems · System design studio