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

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.

The parts of an MCP deploymentThe host is the AI application. Inside it, the model picks a visible tool and the harness runs the loop, the policy, and the approval step. The host opens one MCP client per server. One client talks to a local server over stdio, and another talks to a remote server over Streamable HTTP. Each server calls a business API that applies its own access rules. MCP covers only the hop between the host's clients and the servers. Host: the AI application Model picks a visible tool Harness loop · policy · approval MCP client 1 one per server MCP client 2 one per server stdio Streamable HTTP Orders server local child process CRM server remote shared service Orders API own auth and rules CRM API own auth and rules MCP (orange) covers the client-to-server hop only
FIG 01The loop and the policy live in the host, and each server is a separate program with its own trust level.

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.

One tool call across function calling, MCP, and RESTThe model emits a function call in its provider's format. The host checks policy, arguments, and approval. The MCP client encodes a tools/call request with _meta. The MCP server validates the arguments and maps the call to a handler. The business API runs the operation under its own access rules. The result goes back to the host, which puts it in the next model turn. The top two steps use function calling, the middle two use MCP, and the last uses REST. Model emits a function call in its own format Host checks policy, arguments, and approval MCP client encodes tools/call with _meta MCP server validates arguments, maps to a handler Business API runs the call under its own access rules result returns to the host function calling MCP REST
FIG 02The host translates between the model's format and MCP, and its policy check sits between the two.

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:

  1. The initialize and initialized exchange and the Mcp-Session-Id header are retired.
  2. Each request carries its protocol version, client identity, and client capabilities in _meta.
  3. A server/discover method returns supported versions and capabilities. The versioning page says that servers must implement it, but clients do not have to call it first.
  4. Streamable HTTP requests carry Mcp-Method and Mcp-Name headers, so gateways can route without parsing the body.
  5. Roots, Sampling, and Logging are deprecated. The release post says that they keep working for at least twelve months.
  6. Tasks moved out of the core into the io.modelcontextprotocol/tasks extension.

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-Method and Mcp-Name headers 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 clientInfo and serverInfo are 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.

A write over MCP: preview, signed approval, commitThe model asks for a write. The host sends tools/call to the server. The server stores a preview under a preview ID and returns it with no side effect. The host shows the preview to a person. When the person approves, the host signs the preview ID with a secret that the model never sees. The host sends execute with the token. The server checks the token, commits once with an idempotency key, and returns a receipt. The model cannot sign a token, so a forged approval fails. Model Host MCP server write call tools/call preview + preview_id preview, no effect Person approves host signs the ID Cannot sign never sees secret execute + token Checks token commits once receipt receipt
FIG 03The model can see and request a write, but only the host's approval step can produce the token that runs it.

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:

  1. The MCP server must publish OAuth Protected Resource Metadata, which tells the client which authorization server to use.
  2. The client must send a resource indicator (RFC 8707) that names the MCP server, so the token is issued for that server only.
  3. The server must check that each token names it as the audience, and it must not accept or pass through other tokens.
  4. The client must send the token in the Authorization header on every request, never in the URL.
Token audience across the MCP server boundaryThe host gets token A for one resource, the MCP server, and sends it on each request. The MCP server checks that token A names it as the audience. To call a downstream REST API, the server must not pass token A through. It uses a separate credential or an exchanged token B, so the REST API can apply its own access checks to a caller it can identify. Host (MCP client) gets a token for one resource token A, audience = MCP server MCP server rejects any other audience Pass A through forbidden by the spec Token B separate credential REST API applies its own access checks
FIG 04Each hop gets its own token. A stolen MCP token cannot be replayed at the API behind the server, but it can still call the MCP tools it was granted.

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.