Executive Summary
MCP is valuable in a multi-agent system because it standardizes an integration boundary. It is not a system constitution.
The 2026-07-28 specification makes the protocol core stateless: requests are self-contained, the protocol-level initialize/initialized handshake is removed, and Mcp-Session-Id is removed. That simplifies routing and horizontal operation. It does not make the application stateless, automatically secure delegation, or eliminate the need for authorization.
A production design has two distinct planes:
Agent / Host / MCP Client
|
| MCP request and result
v
MCP Server / Tool Adapter
|
| trusted identity + policy decision
v
Owning Service / Database / Effect
Protocol plane: interoperability, schemas, request lifecycle
Control plane: identity, authorization, approval, limits, state, audit
The control plane must remain authoritative even when several agents, servers, and frameworks participate in one task.
What MCP 2026-07-28 Provides
At the protocol layer, MCP provides:
- JSON-RPC messages;
- stateless, self-contained requests;
- tools, resources, and prompts;
- per-request capabilities and metadata;
- transport and error semantics;
- optional extensions such as Tasks and MCP Apps;
- an HTTP authorization profile based on established OAuth and OpenID Connect specifications;
- W3C Trace Context propagation conventions.
It does not prove:
- that a caller owns a target object;
- that one agent may delegate authority to another;
- that a requested action matches user intent;
- that a destructive effect was approved;
- that tool output is trustworthy;
- that a retry is safe;
- that application state is current;
- that a workflow should continue.
This separation is not a limitation to work around. It is the correct ownership boundary: the protocol carries requests; the application enforces meaning.
Stateless Protocol, Stateful Application
Earlier MCP profiles used a protocol-level initialization handshake and session identifier. In 2026-07-28, each core request carries the information needed for any compatible server instance to process it.
An application can still be stateful. The recommended pattern is an explicit, authorized handle:
tools/call browser.create -> { "browser_id": "br_7f..." }
tools/call browser.navigate { "browser_id": "br_7f...", ... }
tools/call browser.close { "browser_id": "br_7f..." }
Treat the handle as a reference, not authority.
For every use, the server should verify:
- the authenticated subject and tenant own or may use the handle;
- the handle was created for this workload and purpose;
- it has not expired, been revoked, or reached a terminal state;
- the requested transition is valid;
- concurrent use is allowed;
- the result is bounded and correctly isolated.
Do not encode identity or permission only in a model-visible opaque string. A guessed or leaked handle must not authorize access.
Identity in a Multi-Agent Call
One task may contain several identities:
| Identity | Meaning |
|---|---|
| resource owner | the user or organization whose data and authority are involved |
| authenticated subject | the identity represented by the access token |
| client | the MCP client registered with the authorization server |
| workload | the application or job executing the task |
| actor | the agent or component requesting the current action |
| delegator | the actor that assigned work to another actor |
| approver | the human or service that approved a bounded effect |
Collapsing them into one user_id destroys accountability and often expands authority accidentally.
Use a trusted actor chain established by the application or identity system:
resource_owner -> workload -> planner_agent -> executor_agent -> tool
Each hop should narrow or preserve authority, never silently widen it. Model-generated text such as "the planner approved this" is not proof of delegation.
Authorization: What the MCP Profile Covers
For protected HTTP servers, the MCP authorization specification defines roles around OAuth:
- the MCP server acts as an OAuth resource server;
- the MCP client acts as an OAuth client;
- protected resource metadata identifies authorization servers;
- the
resourceparameter binds requests to the intended MCP server; - the server validates that an access token was issued for its audience;
- insufficient scope can trigger a bounded step-up flow.
The specification explicitly forbids token passthrough: an MCP server must not accept a token that was not issued for that server and forward it to a downstream API.
That still leaves business authorization to the application:
transport authorization:
"May this authenticated client call this protected MCP server?"
business authorization:
"May this subject, through this workload and actor chain,
cancel this particular order in its current state for this purpose?"
Both are required.
A Go Policy Boundary
This standalone Go program illustrates a policy decision for cancelling one order. Save it as main.go and run go run main.go. The principal must come from verified authentication, the order from the owning database, and the approval from a trusted approval store. Only the order ID and idempotency key are caller input; never deserialize the trusted records from model-generated arguments.
package main
import (
"errors"
"fmt"
"time"
)
type Principal struct {
SubjectID string
TenantID string
ActorID string
Scopes map[string]bool
}
type CancelRequest struct {
OrderID string
IdempotencyKey string
}
type Order struct {
ID string
TenantID string
OwnerID string
Status string
}
type Binding struct {
TenantID, SubjectID, ActorID string
Operation, OrderID, Key string
}
type Approval struct {
Binding Binding
ExpiresAt time.Time
Revoked bool
Consumed bool
}
func authorizeCancel(now time.Time, p Principal, r CancelRequest, o Order, a Approval) error {
if p.SubjectID == "" || p.TenantID == "" || p.ActorID == "" {
return errors.New("identity_required")
}
if r.OrderID == "" || r.IdempotencyKey == "" {
return errors.New("request_incomplete")
}
if !p.Scopes["orders:cancel"] {
return errors.New("insufficient_scope")
}
if r.OrderID != o.ID || p.TenantID != o.TenantID || p.SubjectID != o.OwnerID {
return errors.New("object_not_authorized")
}
if o.Status != "open" {
return errors.New("invalid_state")
}
expected := Binding{
TenantID: p.TenantID, SubjectID: p.SubjectID, ActorID: p.ActorID,
Operation: "orders.cancel@1", OrderID: r.OrderID, Key: r.IdempotencyKey,
}
if a.Binding != expected || a.Revoked || a.Consumed || !now.Before(a.ExpiresAt) {
return errors.New("approval_invalid")
}
return nil
}
func main() {
now := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
p := Principal{"user-1", "tenant-1", "executor-1", map[string]bool{"orders:cancel": true}}
r := CancelRequest{"order-1", "cancel-1"}
o := Order{"order-1", "tenant-1", "user-1", "open"}
a := Approval{
Binding: Binding{
TenantID: p.TenantID, SubjectID: p.SubjectID, ActorID: p.ActorID,
Operation: "orders.cancel@1", OrderID: r.OrderID, Key: r.IdempotencyKey,
},
ExpiresAt: now.Add(time.Minute),
}
fmt.Println(authorizeCancel(now, p, r, o, a))
r.OrderID, o.ID = "order-2", "order-2"
fmt.Println(authorizeCancel(now, p, r, o, a))
}
Expected output is <nil> followed by approval_invalid: approval for order 1 cannot authorize order 2, even when both belong to the same user. This function only evaluates trusted snapshots; it neither verifies tokens nor executes or persists a cancellation.
The owning service must recheck identity, current policy, order state, and approval validity at execution. It must consume the approval, change the order, and persist the idempotency outcome atomically. A repeated request with the same key and request fingerprint returns the recorded outcome; a conflicting fingerprint is rejected. The in-memory check alone cannot prevent a concurrent replay.
Approval Is a Bound Capability
An approval should bind at least:
- authenticated subject;
- delegated actor or workload;
- tool and operation revision;
- target object or bounded set;
- material arguments;
- allowed effect;
- expiration;
- maximum use count;
- correlation and idempotency key.
"Approve all future actions" is not a safe shortcut. Neither is approving a plan and assuming every later generated argument remains covered.
The MCP protocol can carry an input request or a task state. The application must decide what an approval means, persist it safely, and validate it immediately before the effect.
Schema Validation Is Not Authorization
Tool schemas are valuable:
- clients can discover required fields;
- invalid shapes can be rejected early;
- enums and bounds can narrow input;
- output can be validated;
- contracts can be versioned.
But a schema cannot establish:
- who the caller is;
- whether the record belongs to the caller's tenant;
- whether the current value is still valid;
- whether a price, balance, or quota changed;
- whether the action was approved;
- whether a duplicate request already succeeded.
Treat tool descriptions, annotations, resource content, and remote schemas as untrusted unless they come from a trusted, pinned source.
Tool Results Are an Input Boundary
A second agent may consume a first agent's tool result. That does not make the result an instruction.
Tool and resource results can contain:
- prompt injection;
- attacker-controlled URLs;
- oversized or recursive data;
- HTML or files with active content;
- stale or conflicting records;
- secrets and personal data;
- text that impersonates policy or approval.
Apply:
- MIME and file-type allowlists;
- byte, row, depth, and redirect limits;
- URL and egress policy;
- content isolation in prompts and UIs;
- malware and archive scanning where relevant;
- provenance and freshness metadata;
- retention and deletion rules.
The host should render external content as evidence, not merge it into trusted system instructions.
Confused Deputies and Delegation
Multi-agent systems amplify the confused-deputy problem. A privileged executor may receive a plausible request from a less trusted planner and act with broader credentials.
Defenses include:
- preserve the originating subject and actor chain;
- mint audience-bound, short-lived credentials for the exact service;
- restrict scopes to the delegated task;
- require object-level checks at the owning service;
- prevent an agent from choosing its own identity or audience;
- bind approval to arguments and effect;
- reject token passthrough;
- audit delegation and policy decisions.
If token exchange is used, it must be an identity-system operation with explicit policy, not copying a bearer token into another agent's context.
Effects, Retries, and Unknown Outcomes
Tool calls fail in more states than "success" and "error":
not_started
started
committed
response_lost
outcome_unknown
compensated
For effectful tools:
- require a durable idempotency key;
- store request fingerprints and terminal outcomes;
- distinguish safe retries from reconciliation;
- expose a status lookup by operation ID;
- use transactions or outboxes where possible;
- define compensation separately from retry;
- never let the model infer success from a timeout.
An executor that times out after the downstream service commits must query the outcome. Blind retry can duplicate a payment, message, ticket, or deployment.
Concurrency and Shared State
Multiple agents may race on the same object. Protocol request ordering is not a business concurrency control.
Use the owning system's mechanisms:
- optimistic versions or ETags;
- database transactions;
- compare-and-swap;
- leases or locks with clear expiry;
- workflow state machines;
- single-writer queues;
- conflict responses that include current state.
The application should decide whether agents may run in parallel and how results merge. "The coordinator will figure it out" is not a consistency model.
Control-Plane Checklist
| Control | Enforcement point |
|---|---|
| token issuer, audience, expiry | MCP server / resource server |
| subject, tenant, delegated actor | trusted identity middleware |
| object and field access | owning service |
| tool allowlist and argument bounds | host and tool adapter |
| approval binding | application policy service |
| state transition | owning workflow or database |
| idempotency and reconciliation | effect-owning service |
| time, turn, and cost budgets | agent runtime / host |
| filesystem and network isolation | sandbox infrastructure |
| result limits and content trust | server, host, and renderer |
| audit and trace correlation | every boundary |
Duplicating a critical check can provide defense in depth, but do not split authority ambiguously. The owning service remains the final enforcement point for its data and effects.
Failure Tests
Test at least:
| Scenario | Expected behavior |
|---|---|
| token for another audience | reject before tool execution |
| cross-tenant object ID | reject at owning service |
| executor receives planner token | reject passthrough or wrong audience |
| expired or altered approval | reject effect |
| duplicate call after response loss | return recorded outcome, do not repeat |
| leaked state handle | deny without matching subject and tenant |
| injected instruction in tool result | treat as data, not policy |
| two agents update one version | one succeeds; conflict is explicit |
| process restarts mid-task | resume from durable application state |
| cancellation races with commit | return a defined reconciled outcome |
Run these tests against the composed system, not only a mocked policy function.
Observability Without Hidden Reasoning
Use the trace context defined by the protocol to correlate:
user request -> host -> MCP client -> MCP server -> owning service
Record:
- trace and correlation IDs;
- authenticated subject, tenant, workload, actor, and delegator IDs;
- tool name, contract revision, and server revision;
- redacted argument fingerprint;
- policy decision and reason code;
- approval reference;
- operation and idempotency IDs;
- state transition;
- result and failure class;
- latency and bounded usage.
Do not make hidden chain-of-thought retention a governance requirement. It is neither a reliable policy record nor a safe default for privacy. Observable actions and explicit decisions are the audit source.
FAQ
Is MCP an authorization or policy engine?
No. The HTTP authorization profile supplies an OAuth boundary. The service that owns the data still decides whether this subject and actor may perform this operation on this object, in its current state.
What changed for state in MCP 2026-07-28?
The core removes the protocol initialization handshake and session ID. Application state can remain behind explicit handles, but each handle lookup needs authorization, expiry checks, and valid state transitions.
Can one agent delegate its MCP access token to another?
Do not copy bearer tokens between agent contexts as a delegation mechanism. Use the identity system's approved delegation flow with explicit actor, audience, scope, and expiry constraints. MCP forbids accepting tokens intended for a different resource and passing them through to downstream APIs.
Does JSON Schema make a tool safe to call?
It checks the expressed input constraints, not ownership or approval. A syntactically valid order_id can still identify another tenant's order. Authorize against trusted records immediately before execution.
What belongs in a multi-agent audit record?
Record verified identities, delegation, policy and approval decisions, tool revisions, redacted argument fingerprints, state transitions, and outcomes with correlation IDs. Hidden model reasoning and raw credentials are not required.
Conclusion
MCP gives multi-agent systems a standard protocol boundary. The 2026-07-28 stateless core makes that boundary easier to route, cache, trace, and scale, while explicit handles let applications retain state when needed.
Security comes from a separate, enforced control plane: audience-bound identity, explicit delegation, object-level authorization, bound approvals, transactional state checks, idempotency, concurrency control, untrusted-result handling, and reconstructable audit records.
The durable rule is simple: protocol metadata may describe a request; only trusted application policy may authorize its meaning and effect.
Primary Sources
- MCP 2026-07-28 Specification
- MCP 2026-07-28 Authorization
- MCP Security Best Practices
- MCP 2026-07-28 Release Candidate Explanation
- OAuth 2.0 Resource Indicators, RFC 8707
- OAuth 2.0 Protected Resource Metadata, RFC 9728