A deployment fails at 02:13. A coding agent can see the failed revision and the last 200 log lines. An operations agent knows how to distinguish a bad image from a bad node pool. Neither should receive the infrastructure-admin token just because they need to collaborate.
That is the useful boundary between two open protocols that are often put in the same “agents” bucket. The Model Context Protocol (MCP) gives an agent a typed way to discover and call a platform's tools. Agent2Agent (A2A) gives agents a way to discover one another and exchange stateful work. They are complementary, but neither is an authorization system for a production control plane.
Here is a concrete design for a self-hosted PaaS: use MCP for the narrow action at the platform boundary, use A2A for the handoff between specialists, and keep approval, policy, and the durable audit trail in one platform-owned service.
The deployment incident: two protocols, one approval
Start with an ordinary incident: a Git push produced deployment dep_847, which failed its readiness check. The coding agent should investigate the application. The operations agent should assess the cluster and recommend the least disruptive recovery. A human should authorize a rollback if one is needed.
The trust boundary is the design, not a diagram someone adds after the fact:
| Component | It may receive | It must not receive |
|---|---|---|
| Coding agent | User request, workspace/deploy IDs, read-only diagnostic results | Cluster-admin credential or another agent's token |
| Ops agent | A2A task context, redacted diagnostic artifact, a capability to request approval | The coding agent's user session or a general platform token |
| A2A transport | Signed request metadata and task/status messages | Control-plane credentials or unredacted secret values |
| MCP control-plane server | Short-lived caller identity plus exact tool arguments | Authority inferred from an A2A message alone |
| Policy and approval service | Requested action, resource IDs, current policy, human decision | A model's unverified claim that approval happened |
| Audit store | Immutable correlation IDs and authorization decisions | Secrets, raw access tokens, or prompt text by default |
The incident then follows this sequence:
coding agent --MCP get_deploy/read_logs--> control plane
coding agent --A2A investigate(dep_847, evidence)--> ops agent
ops agent --A2A status/artifact: "bad release; rollback candidate rev-41"--> coding agent
coding agent --MCP request_approval(rollback dep_847 -> rev-41)--> policy service
human approver --approve once--> policy service
coding agent --MCP rollback_deploy(approval_id)--> control plane
control plane --audit result + deployment state--> audit storeThe core fact is easy to miss: the A2A handoff carries work, not authority. The coding agent asks the ops agent for an assessment. The ops agent produces an artifact and can request an approval. Only the platform policy service turns that approval into a short-lived, resource-bound permission for the MCP rollback_deploy call.
That matters because A2A tasks can be long-running and multi-turn. A2A defines Agent Cards for discovery, messages, task state, artifacts, streaming, and push updates; a task is an excellent envelope for an investigation that outlives one chat response. It deliberately does not make an agent's message equivalent to an administrator's authorization decision. Its current specification even provides an AUTH_REQUIRED task state for an agent to ask the client to fulfill a required authorization.
Give each protocol the job it is good at
The shortest design rule is: MCP crosses into the platform; A2A crosses between agents; the policy layer decides. The table makes that less slogan-like.
| Need | Owner | Concrete behavior |
|---|---|---|
| Discover platform capabilities | MCP | tools/list exposes get_deploy, read_logs, request_rollback_approval, and rollback_deploy with schemas. |
| Read deployment facts | MCP | The control plane checks workspace membership, then returns bounded, redacted data. |
| Ask a specialist to investigate | A2A | The coding agent creates an investigate-deployment task with an evidence reference, not a credential. |
| Track a long-running handoff | A2A | Status updates move from working to input/auth-required, completed, failed, or cancelled; the conclusion is an artifact. |
| Decide whether a mutation is allowed | Platform policy service | It evaluates user, agent, workspace, environment, deploy, change window, and approval rules on the server. |
| Execute the mutation | MCP | rollback_deploy accepts an approval ID and exact target revision; server-side policy re-checks it. |
| Reconstruct what happened | Platform audit service | It joins the A2A task and each MCP action to one incident correlation ID. |
MCP's tools model fits the platform boundary because a server publishes a name, an input schema, and optionally an output schema, then a client calls tools/call. That gives a control plane a small contract to validate. It does not mean the model should freely select a dangerous tool: the MCP specification says tool annotations are untrusted unless the server is trusted and recommends a human able to deny sensitive invocations.
A2A fits the delegation boundary because its Agent Card describes an agent's identity, skills, interfaces, and security requirements. Its task object creates a handle for asynchronous progress and artifacts. That is materially different from exposing every internal tool of the operations agent to the coding agent—or, worse, letting both agents hold the same bearer token.
Make the MCP surface deliberately boring
The safest agent tool is not run_kubectl. It is a small operation whose input matches the business action an operator would already recognize. For the incident above, the tool catalog can be this narrow:
{
"name": "read_deploy_logs",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"workspaceId": { "type": "string" },
"deployId": { "type": "string" },
"tailLines": { "type": "integer", "minimum": 1, "maximum": 200 }
},
"required": ["workspaceId", "deployId"]
}
}Its server verifies the audience, expiry, user-to-workspace relationship, and deploy ownership before it returns a capped, secret-redacted result. The agent receives facts, not a shell. A retry has no side effect.
The mutation must be more constrained still:
{
"name": "rollback_deploy",
"arguments": {
"workspaceId": "wrk_12",
"deployId": "dep_847",
"targetRevision": "rev_41",
"approvalId": "apr_902"
}
}The server—not the tool description—enforces all the interesting rules: apr_902 must be unexpired, bound to this workspace, deploy, target revision, and requested operation; the human must have the right role; and the deploy must still be eligible for rollback. The endpoint records an idempotency key and re-evaluates policy when it executes, so a delayed or replayed request cannot turn yesterday's approval into today's production change.
This approach also avoids an OAuth trap that matters in chains of services. MCP's authorization guidance requires token audience validation and explicitly forbids simply passing the token received by an MCP server to a downstream API. Give each component its own short-lived credential for its own audience. A2A correlation data can identify a task; it should never become a substitute for access control.
Preserve the thread without passing the keys
A2A lets the coding agent delegate the hard reasoning while retaining a durable reference to the work. The caller discovers the ops agent from its Agent Card, then sends a task like this:
{
"message": {
"role": "user",
"parts": [{
"kind": "data",
"data": {
"incidentId": "inc_3001",
"deployId": "dep_847",
"evidenceRef": "audit://inc_3001/diagnostics/1",
"requestedOutcome": "diagnose and recommend a safe recovery"
}
}]
}
}The reference resolves through the control plane after authorization; it is not a signed URL to a bucket full of secrets. The ops agent may return a structured artifact with a diagnosis, confidence, evidence IDs, and proposed target revision. If it needs a privileged fact, it asks for a new scoped read through the policy layer. If it determines rollback is appropriate, it moves the task to AUTH_REQUIRED and states exactly what needs approval: “Rollback dep_847 to rev_41 in production.”
Every operation then writes the same correlation tuple:
| Field | Example |
|---|---|
| Incident and A2A task | inc_3001, task_a2a_77 |
| Request chain | user ID, coding-agent ID, ops-agent ID |
| Platform target | workspace, environment, deploy, proposed revision |
| Policy decision | policy version, allowed/denied, reason, expiry |
| Human decision | approver ID, timestamp, approval ID |
| MCP execution | tool, validated arguments hash, result, idempotency key |
This produces a trace a human can actually audit: who requested the action, which agent proposed it, what evidence it saw, who authorized it, and what the platform changed. It also avoids the false comfort of logging only the final API call while the multi-agent reasoning that triggered it disappears.
Design for the ways agents fail
Prompt injection, retries, and confused identities are normal inputs to an agent control plane, not edge cases. Four controls make the design fail closed:
- An injected diagnostics result asks for a shell. The ops agent cannot call an unbounded shell tool; it can only request declared MCP operations. The MCP server validates schema and authorization server-side, and the host treats tool metadata from untrusted servers as untrusted.
- A caller retries a handoff or mutation. A2A task/message IDs and control-plane idempotency keys make duplicates observable. The rollback endpoint returns the existing result for the same key rather than creating a second change.
- An agent tries to read another tenant's task. Task retrieval checks authenticated access on every request; correlation IDs are opaque identifiers, not permissions.
- A delegated agent asks for broader scope. It can create an approval request with the new, explicit resource set. It cannot extend the original approval or mint a broader token. The policy service denies by default.
Start with read-only tools: deploy state, bounded logs, metrics, and a dry-run recovery plan. Add A2A delegation next, but make its output advisory. Add an approval-bound mutation only after the audit record, role checks, expiry, and idempotency behavior have been tested with retries and revoked access. That sequence is slower than handing an agent a platform key, but it leaves an operator with a system they can reason about at 02:13.
Protocols move requests; the platform owns responsibility
MCP and A2A solve different interoperability problems. Use MCP to make platform actions explicit and validated. Use A2A to let specialized agents exchange asynchronous work and artifacts. Do not ask either protocol to carry a vague, transitive promise that one agent is “allowed to do whatever the other agent could do.”
For a self-hosted PaaS, this split is practical rather than theoretical: the control plane already knows the workspace, deployment, policy, and audit record. Keep the right to mutate infrastructure there. Agents can become first-class operators without becoming first-class holders of an all-powerful infrastructure credential.
Bex.co is an open-source, AI-native Render alternative for deploying Git repositories to HTTPS services on machines you control. Its agent-facing control plane is the kind of boundary where narrow actions, policy checks, and machine-readable state matter.
Sources
- MCP tools specification — tool discovery, calls, schemas, and human-in-the-loop guidance.
- MCP authorization specification — audience validation and the prohibition on token passthrough.
- A2A protocol specification — Agent Cards, tasks, messages, artifacts, and protocol operations.
- A2A authorization and task states — task-scoped authorization requests and access checks.
- NIST on software-agent identity and authorization — the emerging need for agent identification, authorization, auditing, and non-repudiation controls.



