MCP (Model Context Protocol) is a standard way for an AI application to get tools and context from a separate program. It is worth its cost when two or more AI hosts need the same tools, or when another team owns and ships those tools. When one application owns its tools, a direct call to the tool registry is smaller, faster, and easier to secure. This part explains the parts of MCP, where it sits next to function calling and REST, and the design rules that keep an MCP server safe.
AI Agent Engineering Series
- AI Agents Part 1: Architecture
- AI Agents Part 2: Harness and CLI
- AI Agents Part 3: Tools and APIs
- AI Agents Part 4: MCP
- AI Agents Part 5: Context and Memory
- AI Agents Part 6: Agentic RAG
- AI Agents Part 7: Evals and Observability
- AI Agents Part 8: Security and Deployment
Three participants and two layers
The MCP architecture page names three participants. The host is the AI application, such as a coding agent or a desktop assistant. The host creates one client for each server connection. The server is a program that offers tools, resources, or prompts.
The protocol has two layers. The data layer is a set of JSON-RPC 2.0 messages such as tools/list and tools/call. The transport layer carries those messages. It is either the standard input and output of a child process (stdio) or HTTP POST (Streamable HTTP).
The architecture page also states what MCP leaves out. MCP does not control how an application uses its model or manages context. It has no agent loop, no planner, no memory, and no approval flow. Those stay in the harness from Part 2. MCP is also only one protocol among several. AG-UI, for example, covers the link between an agent backend and its user interface.
Each layer owns one decision and holds information that the other layers do not see.
| Layer | Decision it owns |
|---|---|
| Model | Which visible tool moves the task forward |
| Host | May this request reach this server, for this user, now |
| MCP client | How to encode and send the request |
| MCP server | Which handler runs the request, and are the arguments valid |
| Business service | Is the operation allowed under its own access and business rules |
A common failure is to move a host decision into the server. A server sees one request at a time. It does not see the user's goal, the other servers, or the run's budget. Approval and cross-server policy must stay in the host.
Tools, resources, and prompts
A server offers three kinds of capability, and each has a different controller.
| Capability | What it is | Who chooses to use it | Example |
|---|---|---|---|
| Tool | An operation with a JSON Schema for its input | The model, through the host | Read an order, draft a refund |
| Resource | Content at a stable URI | The host application | A policy document, a table schema |
| Prompt | A reusable message template | The user, through the host | "Summarize this incident" |
The resources specification and the prompts specification define the other two types. The split matters for safety. A tool call is a model decision, so it needs the policy checks from Part 3. A resource is read when the application decides, so the host can cache it, filter it, and budget it like any other context (Part 5). Most teams start with tools. Add resources when the model needs reference text that does not change with each call.
One tool call crosses three formats
One tool call often uses function calling, MCP, and REST in sequence. The model emits a function call in its provider's format. For example, the Claude tool use documentation defines a tool with name, description, and input_schema, and your code runs each call. The host checks the call, then its MCP client sends tools/call to the server. The server maps the call to a handler, and the handler often calls a REST API.
The bridge from MCP to function calling is small. The MCP tool's inputSchema becomes the provider's input_schema, and the name and description pass through. Some platforms remove this step. The Claude tool use documentation lists an MCP connector that reaches remote MCP servers from the Messages API with no separate MCP client. Your policy checks must still run somewhere, so decide where before you hand the connection to a platform.
Each interface has a different caller and a different cost.
| Interface | Who calls it | Contract | Best fit | Cost it adds |
|---|---|---|---|---|
| Direct function call | The harness, in-process | Language types and the registry schema | One application owns the tools | Almost none |
| Function calling | The model, through its host | Provider tool format | Every model-directed call | Schema design and validation |
| REST or GraphQL | Any client on the network | HTTP semantics, OpenAPI | A stable service for many client types | Versioning, auth, rate limits |
| CLI | People, scripts, coding agents | Arguments, exit codes, stdout | Developer and operations tasks | Output parsing, shell access |
| MCP | AI hosts | JSON-RPC methods, JSON Schema tools | The same tools in several AI hosts | A process or service, protocol versions, server trust |
MCP does not remove the need for a REST API. Web, mobile, and batch clients still use the normal API. The MCP server is an adapter for AI hosts, and it usually calls that API underneath.
When MCP earns its cost
Reuse across hosts is the main benefit of MCP. A new AI host can use an existing tool with no new adapter code, and standard discovery saves some integration work. The process boundary, the protocol version, and the server identity to verify are costs. Decide with the reuse you have today.
| Situation | Choice | Reason |
|---|---|---|
| One application, tools ship in the same release | Direct registry call | No second consumer, so the protocol is pure overhead |
| Two or more AI hosts need the same tools | MCP server | One adapter serves every host |
| Another team owns the tools and ships on its own schedule | MCP server | The protocol is the contract between the teams |
| A vendor already maintains an MCP server for its product | Use the vendor server, behind host policy | No adapter to write, but the server is third-party code |
| A coding agent needs a task that a shell command already does | CLI with clear --help text |
Smaller interface, and the agent already has a shell |
| You plan to wrap every internal function | Neither, until you pick tasks | A large catalog makes tool selection worse (AI agent tool selection) |
The move is cheap to postpone. If the registry from Part 3 is the contract, putting it behind MCP later takes one small adapter. The adapter maps tools/list to the registry's definitions, and tools/call to its dispatch method. Start direct and add MCP when the second host arrives.
Every request stands alone
Protocol revision 2026-07-28 changed the connection model. Older guides that start with an initialize call are out of date. The 2026-07-28 release post lists the changes:
- The
initializeandinitializedexchange and theMcp-Session-Idheader are retired. - Each request carries its protocol version, client identity, and client capabilities in
_meta. - A
server/discovermethod returns supported versions and capabilities. The versioning page says that servers must implement it, but clients do not have to call it first. - Streamable HTTP requests carry
Mcp-MethodandMcp-Nameheaders, so gateways can route without parsing the body. - Roots, Sampling, and Logging are deprecated. The release post says that they keep working for at least twelve months.
- Tasks moved out of the core into the
io.modelcontextprotocol/tasksextension.
The base protocol page calls MCP a stateless protocol. Two _meta fields are required on every request, and their keys use the io.modelcontextprotocol/ prefix. A request without them is malformed, and the server must reject it with JSON-RPC error -32602. A request for a version that the server does not support gets error -32022 (UnsupportedProtocolVersion). This is the shape of one self-contained call:
tools/call
name: get_order
arguments: { order_id: "ord_4821" }
_meta:
io.modelcontextprotocol/protocolVersion "2026-07-28" required
io.modelcontextprotocol/clientCapabilities { } required
io.modelcontextprotocol/clientInfo name, version optional
traceparent W3C context optional
(no initialize first, no session id)
The base protocol page states the design rule. A server must not rely on earlier requests on the same connection for context. State that spans requests must use an explicit identifier that the client sends each time. An open stdio process is not a session.
The rule changes server design in three places.
- Scale-out is simple. Any server replica can answer any request, because nothing lives in the connection. A gateway can route on the
Mcp-MethodandMcp-Nameheaders alone. - Cross-call state becomes data. A preview, a cursor, or a long job needs an ID, an owner, and an expiry date in a store that every replica can read.
- Identity fields are not proof. The base protocol page says that
clientInfoandserverInfoare self-reported and should not drive security decisions. Identity comes from the transport (the process the host started, or a verified OAuth token).
The same page reserves traceparent in _meta for OpenTelemetry trace context in the W3C Trace Context format. Pass it on every call, so that the server's spans join the host's trace. Part 7 uses those traces for grading.
Two kinds of error
The tools specification defines two kinds of error, and they go to different readers.
| Kind | Examples | Form | Who reads it |
|---|---|---|---|
| Protocol error | Unknown tool, malformed request, missing _meta |
JSON-RPC error object |
The host and the client code |
| Tool execution error | Bad arguments, API failure, business rule | Normal result with isError: true |
The model, so it can correct the call |
The split lets the model recover from its own mistakes. A wrong field name or an amount above the refund limit comes back as a readable result. The model can then fix the call on the next turn. Give each execution error a stable code and a retryable flag, so that the host knows whether a retry is safe.
The tools specification also says that servers must validate all tool inputs, apply access control, and rate limit tool calls. Do not assume that the SDK validates for you. A low-level server handler can receive raw arguments that never passed a schema check. If the handler does not validate, no layer does.
For structured results, the tools specification recommends structuredContent plus a JSON copy in a text block for older clients.
Writes need a handle and a signed approval
A stateless protocol has no place to park a pending write, so writes use the preview pattern from Part 3 with an explicit handle. The write tool returns a preview of the exact effect and a preview_id, and changes nothing. A separate execute tool runs the write only if the call also carries an approval token.
The token is an HMAC (RFC 2104) of the preview_id. The host's approval step signs it with a secret that only the host and the server hold. The model can read the preview and can even call the execute tool. It cannot make a valid token, because it never sees the secret.
Four design rules keep this path safe:
- Hide the execute tool from the model. The host filters it out of the tool list that it gives the model. The token check still protects it if a model calls it anyway.
- Store previews in a shared database with an expiry time. A stdio process can restart, and an HTTP request can reach another replica, so memory loses the handle. The tools specification's stateful tools guidance calls a handle "a name, not a capability". Check the caller against the preview's owner on every call. State the expiry in the tool description, and return a tool error for an expired handle, so that the model can ask for a new preview.
- Commit with an idempotency key. If a write times out after the server called the API, a blind retry can create a second draft. The business service must enforce the key (Part 3 shows how to derive it).
- Bind the approval to the effect. Sign the preview ID, and keep the exact effect under that ID. A changed amount then needs a new preview and a new approval. Part 8 extends this to a hash of tool, arguments, tenant, and policy version.
Tool annotations such as readOnlyHint and destructiveHint help a host decide what to show and what to confirm. The tools specification says that clients must treat annotations as untrusted unless they come from trusted servers. A server can mark a delete as read-only. The approval token is the control that stops an unapproved write, and the annotation only helps the user interface.
Choose a transport
The transport decides where the server runs, where its credentials come from, and which attacks matter.
| Question | stdio | Streamable HTTP |
|---|---|---|
| Where does the server run | Child process of the host, same machine | Separate service, usually remote |
| How many clients | Usually one | Many |
| Where do credentials come from | The environment that the host sets | OAuth access tokens on each request |
| Main risks | Local file and process access, stdout corruption | DNS rebinding, SSRF, token misuse, network exposure |
| Use it for | Coding agents, desktop apps, local tools | Shared enterprise connectors, vendor servers |
Each transport has one rule that teams often miss. The stdio transport page says that each message is one line of JSON on stdout, and that the server must write nothing else there. One stray print statement in a handler can corrupt the stream, so log to stderr. With stdio, the host also controls the command, the working directory, the environment, and the process lifetime. Use operating-system isolation when a local server should not see the whole machine.
The Streamable HTTP page sets three security rules. Servers must validate the Origin header to prevent DNS rebinding. Local servers should bind to 127.0.0.1 instead of all interfaces. Servers should authenticate every connection. The page also says that a server must reject a request with -32020 (HeaderMismatch) when the Mcp-Method or Mcp-Name header does not match the body. That check stops a request that a gateway routed on one method and the server would run as another. Also protect the server from server-side request forgery (SSRF), where a caller makes the server connect to an internal address that the caller cannot reach.
Tokens are issued for one server
Authentication confirms who the caller is. Authorization decides what the caller may do. The MCP authorization specification applies to HTTP transports. The base protocol page says that stdio servers should get credentials from the environment instead.
Authorization is optional in MCP. An HTTP server that implements it follows OAuth 2.1 with four rules:
- The MCP server must publish OAuth Protected Resource Metadata, which tells the client which authorization server to use.
- The client must send a resource indicator (RFC 8707) that names the MCP server, so the token is issued for that server only.
- The server must check that each token names it as the audience, and it must not accept or pass through other tokens.
- The client must send the token in the
Authorizationheader on every request, never in the URL.
Rule 3 matters most when the MCP server calls another API. A forwarded token hides which service called the API. A token stolen from one server would then open every API behind it. Use a separate credential or a token exchange for the downstream call. Part 8 covers the credential broker, identity, and policy in full.
Results are context and untrusted input
A tool result becomes model context. Shape it at the server boundary with the output allow-list from Part 3. The tools specification also says that servers must sanitize tool outputs.
Treat every result as untrusted text. A record, an email, or a web page in a result can carry instructions that try to change the next model action. This attack is indirect prompt injection. Keep results in the tool-result channel, never let result text change policy, and require approval for a write that follows a read of untrusted content. The prompt injection playbook covers the containment controls.
Tool descriptions are untrusted too. A server that you did not write can change a description between releases and steer the model toward a tool. Pin the server version, and snapshot tools/list in CI so that a changed name, description, or schema fails a test before release. Tool names are unique only within one server, so prefix them with a server ID when one host connects to several servers.
When an API has hundreds of operations, one MCP tool per operation is the wrong shape. Every definition uses context on every turn. Code mode gives the model a small search tool and an execute tool that runs model-written code in a sandbox. MCP search and execute explains that pattern and when it wins. Keep one tool per operation for a small set of writes, so each write still goes through the preview and approval path above.
Failure modes and their owners
An MCP call crosses several boundaries. A generic "tool failed" message tells the host neither what failed nor whether another attempt is safe. Assign each failure to the layer that can detect it.
| Failure | Owner | Response |
|---|---|---|
| Unknown tool | MCP server | Protocol error -32602. The host refreshes its tool list. |
| Expired or wrong-audience token | MCP server | HTTP 401. The host refreshes the token through the approved flow. |
| User lacks access | Business service | Deny without revealing the protected record |
| Server process exits | Host | Restart it, retry reads, and look up the status of writes |
| Timeout after a write | Business service | Resolve with the idempotency key, never a blind retry |
| Server compromised or down | Host | Remove its tools from the model's list and revoke its tokens |
A read can usually take one bounded retry. A write needs evidence. If a write times out after the server called the API, the host cannot know whether the draft exists. Only the business service can answer, with the idempotency key. Record the protocol version, the server name and version, the tool name, and a hash of the tool schema in each trace event. When behavior changes, that record shows whether the model, the host, or the server moved.
Use MCP Inspector, the project's official debugging tool, to read protocol messages during development. Test authorization, hostile input, and tool selection with your own eval cases (Part 7).
How OpenNash helps
OpenNash forward-deployed engineers work with Fortune 2000 teams that need their tools in more than one AI host. We pick the first read and write operations with the service owner. We put them behind an MCP server with the preview and approval path, and we add trace and schema checks to your CI. You own the server, the tool schemas, and the tests. If only one application uses your tools, we tell you to keep the direct call.
Book a call with an OpenNash forward-deployed engineer or email us at hello@opennash.com. In the first session, we review your tools and AI hosts. Then we show where an MCP server, or code mode, delivers the highest business value and the fastest ROI.
Previous: AI Agents Part 3: Tools and APIs
Next: AI Agents Part 5: Context and Memory explains how the host decides what information the model receives on each turn.