Model Context Protocol: Architecture, System Design, and the Security Surface

A technical deep dive for architects, covering protocol internals, deployment topology, authorization, threat modeling, and the business consequences of each design choice.

Audience: solution and enterprise architects, platform engineers, security reviewers

1. Why MCP exists

Enterprise AI systems share an integration problem. Every model host (an IDE assistant, a chat application, an agent runtime, an internal copilot) needs to reach the same systems: ticketing, source control, CRM, cloud control planes, document stores, and internal APIs. Without a shared protocol, each host-to-tool pairing needs its own connector with its own authentication handling, schema conventions, and error model. With N hosts and M tools, that can mean up to N×M bespoke integrations.

Conceptually, MCP can move integration effort toward N+M reusable interfaces, although authorization, host differences, and operational requirements still introduce additional integration work. A tool is exposed once as an MCP server, and any compliant host can consume it. The protocol is an open specification built on JSON-RPC 2.0. It defines a small set of primitives, explicit capability negotiation, transport independence, and a standard authorization model for remote servers.

The useful architectural question is not whether MCP is a good abstraction. It is whether the protocol's trust model, failure semantics, and operational characteristics fit your requirements. The rest of this article works through that question layer by layer.

2. Architectural roles

Three roles should stay distinct in every design review.

The host is an important policy enforcement point; the gateway, MCP server, and downstream domain service must independently enforce their applicable security controls. The server must be treated as partially untrusted, even when your own team wrote it, because its outputs flow directly into the model's context window and can shape subsequent behavior.

Host, client, and server roles A host contains one client per server. Each client connects to a server over JSON-RPC. Local servers use stdio; remote servers use Streamable HTTP. Host (IDE, chat app, agent runtime) LLM session · policy engine · consent UI · audit sink Client A one-to-one with server A Client B one-to-one with server B Local server (stdio) Runs with user OS privileges Remote server (HTTP) Scoped bearer token per request
Figure 1. Roles and transports. The host enforces user-facing controls; servers and backend systems independently enforce authorization.

3. Protocol foundations

3.1 Message model and handshake

All traffic is JSON-RPC 2.0: requests carry an id, responses echo it, and notifications carry no id and expect no response. Every session begins with an initialize exchange in which client and server agree on a protocol version and declare capabilities. The client then sends notifications/initialized to complete initialization before normal operation.

// Client → Server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "roots": { "listChanged": true }, "elicitation": {} },
    "clientInfo": { "name": "example-host", "version": "1.4.0" }
  }
}

// Client → Server (notification, no id)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

The handshake is the first enforcement point. A host that requires structured output or a specific authorization revision should refuse servers that do not advertise it, rather than degrade silently. Protocol revisions are date-stamped; pin the versions your host supports, track the changelog, and test upgrades explicitly. Verify current revision details at modelcontextprotocol.io before committing to a design, because the specification evolves.

3.2 Primitives and who controls them

MCP separates capabilities by who controls their invocation. This is the most important design idea in the specification.

PrimitiveControlled byTypical useSecurity concern
ToolsModelActions and queriesSide effects, injection, overbroad reach
ResourcesApplicationDocuments, config, log excerptsData exposure, untrusted content
PromptsUserTemplated workflows, slash commandsLower risk; review template contents
SamplingServer requests, host approvesServer-side agent logic using host's modelBudget burn, behavior steering, context leakage
RootsClient declaresFilesystem or URI boundariesBoundary violations if not enforced
ElicitationServer requests, user answersStructured input mid-operationCredential phishing through host UI

3.3 Tool definitions are prompt surface

Hosts may supply tool names, descriptions, and JSON Schemas to the model; how much is included on each turn depends on host implementation and tool-selection strategy. A description can influence the model’s tool selection but must not be treated as a trusted system-level instruction. This makes descriptions a high-value injection target and means they deserve the same review, versioning, and testing discipline as code.

Annotations such as readOnlyHint, destructiveHint, and idempotentHint help hosts decide when to ask for confirmation. They are server-supplied hints, not guarantees. A host that makes authorization decisions solely on a server's self-declared readOnlyHint has delegated its security decision to the party it is supposed to constrain.

Design rule. Treat every tool description as a security artifact. Review it at onboarding, pin it by hash, and diff it on every change.

4. Transports

The transport determines most operational and security characteristics.

4.1 stdio

The host launches the server as a child process and exchanges newline-delimited JSON over standard input and output. There is no network exposure, but the server inherits the user's operating-system privileges. Installing a stdio server is effectively installing arbitrary code. Its supply chain (package registry, version pinning, provenance, signature verification) requires the same governance as any other software dependency.

4.2 Streamable HTTP

Introduced in the 2025-03-26 revision and replacing HTTP+SSE, Streamable HTTP is the transport for remote servers. The client POSTs JSON-RPC messages to a single endpoint. The server responds with a direct JSON body, or opens an SSE stream when it needs to send server-initiated messages or hold a long-running response. Servers may assign an Mcp-Session-Id at initialization; this header is not mandatory for stateless deployments. Clients should send the negotiated protocol version on subsequent requests where required by the selected specification revision.

5. The request lifecycle

Following one tool call end to end shows where state, failures, and trust boundaries sit.

Tool call lifecycle Seven steps from discovery through context assembly, model decision, policy gate, dispatch, execution, and result. Protocol errors and tool errors return on separate paths. Discovery tools/list Context Schemas loaded Model decision Proposes call Policy gate Allow or deny Dispatch tools/call Execution Scoped token Result isError: true Protocol errors and isError results take different paths back to the model.
Figure 2. Tool call lifecycle. The policy gate sits between the model's decision and any side effect.
  1. Discovery. The client calls tools/list and caches the result. It must handle notifications/tools/list_changed, because servers can alter their tool surface at runtime.
  2. Context assembly. Tool definitions are included in each model request and consume tokens every turn. Large servers can occupy a meaningful share of the context window before the user has typed anything.
  3. Model decision. The model emits a call with arguments. The host validates those arguments against inputSchema before dispatch. The model is never trusted to produce valid or safe input.
  4. Policy gate. The host evaluates whether the tool is permitted for this user, workspace, and data classification; whether confirmation is required; and whether arguments fall within bounds such as a path inside a permitted root or a recipient on an allowlist.
  5. Dispatch. The client sends tools/call. For remote servers, the request carries the bearer token for that server's audience.
  6. Execution. The server performs the operation, using its own credentials or a token obtained through token exchange for each downstream dependency.
  7. Result. The server returns content and, in newer revisions, structuredContent conforming to outputSchema, enabling deterministic parsing downstream.

5.1 Error taxonomy

Two error classes must stay separate. Protocol errors are JSON-RPC error responses: unknown tool, malformed request, or server fault. Tool execution errors return as a normal result with isError: true, so the model can observe the failure and recover. Conflating the two causes real bugs, because a protocol error is often handled by the client's retry logic while a tool error is something the model should reason about.

// Tool execution error: returned as a result the model can reason about
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      { "type": "text",
        "text": "Rate limited by ticketing API. Retry after 30 seconds." }
    ],
    "isError": true
  }
}

5.2 Timeouts and idempotency

Timeouts are not solved by the protocol. Each server must define its own behavior for long-running operations and communicate progress, and the host must enforce a ceiling so that a hung tool cannot stall an agent loop indefinitely.

Idempotency is equally important. Models retry, and networks cause client retries. Any side-effecting tool should be idempotent by construction, accept an idempotency key, or be gated so that a duplicate invocation is harmless or detected.

6. End-to-end enterprise MCP execution sequence

This example uses an enterprise IdP, a remote MCP server, a gateway, and a domain API. It explicitly separates identity validation from operation authorization. The diagram shows a successful path; a denied policy decision stops execution before the MCP server invokes the ERP API.

End-to-end MCP enterprise execution sequenceAuthentication, initialization, tool discovery, authorization, execution, and audit across enterprise components.UserAI hostIdPGatewayMCP serverPolicyERP APIAuditRequest invoice statusConnect to MCP endpointOAuth challengeAuthorization code + PKCEScoped tokeninitialize + tokeninitializeCapabilities (via gateway)tools/list (via gateway)Approved tool definitionsConfirm proposed writetools/callEvaluate tool + principalPermit / denyAuthorized tool callScoped backend requestTransaction outcomeTool result (via gateway)Outcome and transaction IDTelemetry and audit events are emitted throughout; denial exits before backend execution.
Figure 3. Enterprise request lifecycle, from user authentication through discovery, tool execution, and audit.
BoundaryRequired enforcement
Host → gatewayAuthenticated client, user intent, confirmation for sensitive actions
Gateway → MCP serverTool allowlist, audience validation, rate limits, trace propagation
MCP server → domain APIResource-level authorization, scoped downstream identity, transaction rules
Every boundary → auditCorrelation IDs, decision and outcome, payload minimization

Failure handling: token expiration triggers reauthorization; an unavailable policy engine should fail closed for sensitive actions; a timed-out write requires reconciliation before retry; and audit failure must follow an explicit, risk-based availability policy.

7. Authorization for remote servers

The authorization model for HTTP-based servers uses OAuth-based mechanisms and related IETF standards, with exact requirements depending on the selected MCP specification revision. The elements an architect needs to understand are the following.

Authorization flow for a remote MCP server Host discovers protected resource metadata, obtains a token from the authorization server with PKCE and a resource indicator, then calls the MCP server. The server performs token exchange before calling a downstream API. Host PKCE client Auth server Issues audience-bound tokens MCP server Validates audience 1. Fetch protected resource metadata 2. Auth code + PKCE + resource 3. Audience-bound token 4. tools/call with bearer token 5. Token exchange for downstream The client token is never forwarded to the downstream API.
Figure 3. Authorization sequence. Step 5 is where token passthrough would occur if implemented incorrectly.

6.1 The rule most implementations break: no token passthrough

An MCP server must validate that tokens were issued for it, and it must not forward the client's token to downstream APIs. Passthrough breaks audience boundaries and turns the server into a confused deputy with access to everything the token reaches. The correct pattern is to obtain appropriate downstream credentials through approved delegation or token exchange where supported, or use a tightly constrained service identity; do not forward the MCP access token to a different resource audience.

Validation on every request should check signature, issuer, expiry, and audience. The following sketch shows the minimum checks in a gateway or server middleware.

def validate_mcp_token(token: str, expected_audience: str) -> Claims:
    claims = jwt.decode(
        token,
        key=jwks_client.get_signing_key_from_jwt(token).key,
        algorithms=["RS256", "ES256"],   # never allow "none" or HS* with a public key
        audience=expected_audience,      # RFC 8707 resource indicator
        issuer=TRUSTED_ISSUER,
        options={"require": ["exp", "iat", "aud", "iss", "sub"]},
    )
    if claims.get("azp") not in APPROVED_CLIENT_IDS:
        raise AuthError("client not approved for this server")
    return claims

6.2 Identity model for agent actions

The MCP server can act in three identity modes, each with different audit, consent, and blast-radius properties.

ModeHow it worksAuditBlast radius
As the user (delegated)Token exchange preserves the user's identity and scopeAttributable to the personBounded by the user's own access
As the applicationService principal with application permissionsAttributed to the app; user context lostOften far exceeds any single user
HybridUser context carried for audit; app permissions for specific actionsBoth user and app recordedControlled by per-action policy

Application permissions granted to an MCP server are a common way to give an AI-accessible surface more reach than any individual user has. Prefer delegated access for anything touching personal or regulated data, and reserve application permissions for narrow, audited operations.

8. Security threat model

MCP's threat model differs from conventional API security because the consumer of the API is a language model. The model reads untrusted content and can be steered by it. The threats below belong in every design review.

7.1 Capability separation

A useful principle is to classify tools by trust profile. Sources read untrusted content, such as inbound email, web fetches, and public tickets. Sinks perform external or irreversible actions, such as sending, deleting, deploying, or transferring. A policy engine should block or require explicit confirmation when a sink runs in a context where a source has contributed content, and it should display the full arguments to the user before the action proceeds.

This will not stop every attack. It does convert an unbounded risk into a bounded, auditable one, which is the most defensible position currently available.

def authorize_call(ctx: CallContext, tool: ToolSpec, args: dict) -> Decision:
    # 1. Schema validation before any policy evaluation
    validate(args, tool.input_schema)

    # 2. Taint: did untrusted content enter this conversation?
    tainted = ctx.session.untrusted_sources_seen

    # 3. Sink rule: irreversible or external actions in tainted context
    if tool.is_sink and tainted:
        return Decision.require_confirmation(show_args=args)

    # 4. Data classification and destination
    if tool.is_sink and classify(args) > tool.max_classification:
        return Decision.deny(reason="data classification exceeds tool limit")

    # 5. Bounds on arguments (paths, recipients, sizes)
    if not within_bounds(tool, args, ctx.policy):
        return Decision.deny(reason="argument outside policy bounds")

    return Decision.allow()

9. Reference enterprise architecture

Beyond individual developer machines, the pattern that scales is a gateway: a governed point through which all host-to-server MCP traffic flows. The gateway centralizes common identity checks, server approval, policy, audit, and egress controls. Each MCP server and downstream service still owns resource-level authorization and domain validation.

Enterprise MCP gateway architecture Hosts connect to an MCP gateway that enforces authentication, a server registry, tool pinning, policy, rate limits, audit, and egress control. The gateway routes to internal servers, SaaS connectors, and downstream APIs. IDE assistant Developer Chat client User-facing Agent runtime Multi-step Copilot Internal tools MCP gateway Identity Audience checks Registry Pinned servers Policy Source and sink Audit Full trail Rate limits Quotas, sampling Tool pinning Drift alerts Egress and DLP Result inspection Internal servers Wrap internal APIs SaaS connectors Remote, OAuth 2.1 Downstream APIs Scoped token exchange
Figure 4. Gateway architecture. Common controls are centralized at the gateway; domain-specific authorization remains at the MCP server and backend.

8.1 What the gateway owns

The gateway provides one identity enforcement point, one registry of approved servers, one audit stream, and one egress policy. It is also a single point of failure and a high-value target. It must be deployed as a highly available service with its own resilience design, and its administrative interfaces need the strongest access controls in the estate.

8.2 Audit record design

Each invocation should produce a record with the caller identity, the tool and server identifiers, a hash or redacted form of the arguments (full arguments may contain regulated data), the result classification, the policy decision with its reason, latency, and the trace identifier. Records should be append-only and retained according to compliance requirements.

{
  "ts": "2026-10-19T14:22:07.318Z",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "caller": { "user": "user-123@example.invalid", "host": "ide", "client_id": "ide-prod-01" },
  "server": "tickets-internal",
  "tool": "update_ticket_status",
  "tool_pin": "sha256:9c1e...a7f2",
  "args_sha256": "b41d...08e3",
  "policy": { "decision": "require_confirmation", "reason": "sink_in_tainted_context" },
  "confirmation": "approved_by_user",
  "result": { "isError": false, "classification": "internal", "latency_ms": 412 }
}

10. System design

An MCP deployment has two planes. The control plane decides what is allowed: which servers are approved, which policies apply, which tool definitions are pinned, and where audit records go. The data plane carries traffic: gateway instances that authenticate and enforce policy on every call, and the MCP servers that perform the work. Separating them means policy can change without redeploying servers, and the data plane can scale without touching governance.

Control plane and data plane The control plane contains the registry, policy store, pinning service, and audit store. It pushes snapshots to a data plane of stateless gateway pods, which call MCP servers and downstream APIs. Control plane Registry Approved list Policy store Rules as code Pinning Definition hashes Audit Immutable store config and policy snapshots Data plane Gateway pods Stateless, HA Auth cache Token validation Policy engine Per-request decision MCP servers Horizontal scale Session store Only if stateful Downstream APIs Token exchange
Figure 5. Control and data planes. Policy changes propagate as snapshots; the data plane never waits on the control plane for a request.

9.1 Components

9.2 Request path

In this reference topology, a call crosses the gateway once in each direction. The gateway validates the token, looks up the target server and its pinned definitions in the registry, evaluates applicable policy against the arguments, and writes a pre-dispatch audit record. The MCP server and domain service enforce their own resource and transaction controls. It then forwards the call with a server-scoped token. When the result returns, the gateway inspects it for data-loss patterns and size limits, completes the audit record with the outcome and latency, and returns the result to the host. Two audit records per call is the minimum for a trail that can reconstruct what happened.

9.3 State and scaling

Most MCP traffic needs no shared state, which is the main reason to prefer stateless servers. State appears in three places: session context for subscriptions and in-flight elicitation, idempotency keys for side-effecting tools, and rate-limit counters. Keep session state in a shared store with a TTL. Make tool calls idempotent so that a retry on a different gateway instance is safe. Rate-limit counters need a distributed store, or per-instance limits divided by instance count, accepting some imprecision.

Capacity planning should start from tool-call volume and average latency, then add headroom for token-exchange round trips, one per downstream service per call or per cached token lifetime. Track tokens consumed by tool definitions as a separate cost line; that cost grows with the number of connected servers rather than with request volume.

9.4 Failure modes

FailureBehaviorMitigation
Control plane unavailableGateways run on last-known-good policy and registry snapshotMaximum snapshot staleness; fail closed beyond it
Auth server unavailableNew tokens cannot be issued; cached validation keeps existing sessions workingShort token lifetimes balanced against cache duration
Server timeoutGateway returns an isError result after the host's ceilingPer-tool timeouts; circuit breaker per server
Pinning driftCalls to the changed tool are blocked pending reviewAlert the owning team; explicit re-approval
Audit sink unavailableSink-classified tools are refusedBounded local buffer; never silently drop pre-dispatch records
Gateway region failureTraffic shifts to another regionActive-active with a regional registry replica
Design decision. When the audit sink is down, the system should refuse irreversible actions rather than perform them unrecorded. Availability and auditability conflict here. Decide the trade-off per tool class before an incident forces the answer.

9.5 Observability

Emit OpenTelemetry spans for each gateway decision and each downstream server call, propagating trace context across hops. Dashboards should track invocations per tool, p95 latency, error rate split between protocol and tool errors, policy denial rate, and pinning drift events. A sudden rise in denials is often the first visible sign of misconfiguration or an injection attempt, so denial rate belongs in alerting, not only in a chart.

# Example gateway policy (declarative, source-controlled)
server: tickets-internal
trust_tier: first_party
pinned_definitions:
  update_ticket_status: sha256:9c1e4f...a7f2
tools:
  search_tickets:
    classification: internal
    role: source               # reads untrusted content
    rate_limit: 60/min
  update_ticket_status:
    classification: internal
    role: sink                 # irreversible state change
    confirmation: when_tainted
    allowed_principals: [group:support-leads]
egress:
  deny_metadata_endpoints: true
  allowed_domains: [tickets.internal.example.com]

11. Design guidance

12. Business impact

The business case rests on four effects that should be measured rather than assumed.

A sound adoption path starts with read-only servers against non-sensitive data, then builds the gateway, registry, and audit pipeline, and only then adds write capabilities behind confirmation gates and taint-aware policy. The controls must be proven before the most valuable integrations arrive.

13. Enterprise scenario: controlled invoice approval

Consider an AI assistant asked to approve a supplier invoice. This is a useful reference scenario because it combines retrieval, a state-changing tool, delegated identity, approval limits, and transaction audit.

Governed invoice approval architectureAI host connects to gateway and finance MCP server, which checks policy and invokes an ERP domain API; audit spans every step. AI hostUser confirmation MCP gatewayToken + policy Finance MCPResource validation ERP domain APIApproval rules Correlated trace + protected auditPrincipal · decision · invoice · outcome
Figure 6. Business transaction controls stay authoritative in the ERP domain even when an AI agent initiates the action.
  1. Read: the host calls a read-only invoice lookup tool and displays amount, supplier, status, and permitted actions.
  2. Decide: the model proposes approve_invoice; the host checks intent and requests explicit confirmation for the write.
  3. Authorize: the gateway checks caller and tool scope; the finance server verifies invoice-level entitlements and approval threshold.
  4. Execute: the ERP API checks current invoice state and records the approval with an idempotency key and concurrency control.
  5. Audit: correlated records capture the decision, user approval, backend transaction ID, and final outcome without retaining unnecessary sensitive payloads.

If the invoice is already approved, the approval limit is exceeded, or the backend times out, the server returns a controlled outcome. A retry must not create a second approval. The model must not infer success from a timeout.

14. Open questions for architects

Several decisions remain open and require explicit answers rather than defaults.

15. Deployment, testing, and architecture decisions

14.1 Deployment and isolation

Separate public ingress, MCP gateway, domain MCP servers, and backend systems into distinct network trust zones. Use workload identity or mTLS where appropriate for service-to-service authentication, restrict egress, and isolate tenants at identity, cache, session, and data layers. Stateless gateways are easier to scale; subscriptions and resumable streams may require session affinity or shared state.

14.2 Verification before production

Test classMinimum verification
Protocol conformanceInitialization, capability negotiation, tool discovery, cancellation, version compatibility
AuthorizationWrong audience, expired token, cross-tenant access, privilege escalation, denied tools
AdversarialPrompt injection in results, poisoned tool metadata, SSRF, chained exfiltration
ReliabilityBackend timeout, duplicate write, gateway failover, audit outage, policy snapshot staleness
ObservabilityTrace continuity, redaction, policy-decision audit, missing-span detection

14.3 Architecture decisions

DecisionOptionsGuidance
OwnershipCentralized / federated serversFavor domain-owned servers with common standards
ConnectivityDirect / gateway-mediatedGateway for shared, regulated access; avoid unnecessary hops for local use
IdentityDelegated / service / hybridPrefer user-scoped delegation where user permissions matter
ExecutionSync / asyncUse job patterns for long-running or non-idempotent workflows

14.4 Adoption and success measures

Start with read-only, bounded capabilities; introduce governed writes after authorization and audit controls are proven. Measure onboarding lead time, tool reuse, p95 execution latency, policy coverage, transaction success rate, cost per completed workflow, and incident frequency. Avoid treating the number of registered tools as a success metric.

16. Conclusion

MCP has a clean separation of roles, a deliberate model of who controls each capability, and a standard authorization story for remote servers. Its security properties, however, come from architecture rather than from the protocol alone: how identity propagates, how tool definitions are governed, how untrusted content is kept away from privileged actions, and how the estate is observed and audited.

Treat MCP servers as partially untrusted capability providers, the host as the policy enforcement point, and the gateway as the place where governance becomes operational. Separate the control plane from the data plane so policy can change without redeploying capability. Done this way, MCP delivers its promise: integrations built once, governed centrally, and usable across whichever tools an organization adopts.

Technical references

Validate deployment-specific requirements against the selected MCP specification revision. Relevant primary sources: MCP specification, MCP architecture, RFC 9728, RFC 8707, RFC 8693, OpenTelemetry, and OWASP GenAI Security.