Start with Boundaries, Not a “Complete Stack”
MCP, A2A, and A2UI are names commonly associated with three useful boundaries. They are not mandatory layers, nor do they collectively authorize a system.
| Boundary | Typical purpose | Still owned by the application |
|---|---|---|
| Tool boundary | expose tools, resources, or prompts to an AI application | user identity, object authorization, side-effect policy |
| Remote task boundary | delegate work to an independently operated Agent service | service identity, durable task state, tenant isolation |
| UI boundary | render constrained Agent-proposed information and collect intent | trusted presentation, action authorization, accessibility |
A local function is preferable when one service owns the workflow. Use a remote task boundary when independent ownership or lifecycle handling has real value. Use a dynamic UI only when it improves a specific user task beyond text or a fixed product flow.
Pin What You Actually Deploy
Specifications, transports, SDKs, extension formats, and examples evolve. Record the tested surface rather than assuming a blog, package name, or ecosystem claim is permanent.
{
"tool_protocol": "pinned-spec-and-transport",
"task_protocol": "pinned-spec-and-binding",
"ui_contract": "pinned-schema-and-catalog",
"runtime": "package-and-revision",
"authentication": "deployment-specific",
"artifact_policy": "approved-store-and-retention",
"checked_at": "recorded-time"
}
Upgrade through compatibility fixtures, failure tests, and a rollback plan. A versioned record is more operationally useful than a vendor support matrix.
Tool Boundary: Capability Is Not Permission
An MCP-like tool interface can describe names, inputs, and results. That helps interoperability; it does not allow a model, caller, or tool annotation to read an object or perform an external write.
For every invocation:
- obtain subject and tenant from trusted authentication middleware;
- validate the requested tool against an allowlist and a bounded schema;
- resolve target objects using server-side authorization;
- limit result bytes, rows, files, and outbound destinations;
- treat tool results as untrusted content before they enter a model or UI;
- require confirmation and idempotency for consequential effects.
Do not expose arbitrary SQL, shell commands, filesystem paths, or unrestricted HTTP fetches as generic Agent capabilities.
Remote Task Boundary: Discovery Is Not Trust
An A2A-like task protocol can carry messages, task updates, and artifacts between services. A card or capability description is useful for routing, but it is untrusted metadata until provenance is established.
Validate endpoint ownership, service identity, token audience and expiry, requested skill, tenant, target object, callback destination, and artifact access. A remote task must have durable state outside the model:
accepted -> running -> waiting_for_input -> terminal
| |
+-> canceled +-> succeeded | failed | rejected | outcome_unknown
Bind an idempotency key to the authenticated principal, tenant, action, and normalized parameters. A completed state is not proof that a payment, notification, or deletion happened exactly once.
UI Boundary: Presentation Is Not Authorization
An A2UI-style contract can avoid arbitrary UI code by allowing a renderer to map a small component catalog to reviewed local components. The payload remains untrusted input.
Reject payloads that exceed byte, depth, component, update-rate, URL, rich-text, image, or data-reference limits. Unknown components should render a product-owned fallback or be rejected. JSON Schema validates shape, not ownership, price, permission, or business truth.
The following is an illustrative policy fragment: session.subject_id must come from trusted authentication context, while action.token is an opaque server-issued reference. Neither value should be treated as authorization merely because it arrived in a UI payload.
def authorize_action(session, action, offers):
if action.name not in {"select_offer", "request_confirmation"}:
return {"status": "rejected", "reason": "unknown_action"}
offer = offers.resolve(action.token, subject_id=session.subject_id)
if offer is None:
return {"status": "rejected", "reason": "not_available"}
return {"status": "accepted", "offer_id": offer.public_id}
This is application policy, not protocol code. An effectful endpoint must recheck the current object, confirmation binding, expiry, idempotency key, and business invariants immediately before execution.
A Safe Composition
A composition can be useful without becoming a rigid stack:
The host owns the user experience and action policy. The remote service owns its task state and its tool policy. Artifact references require authenticated retrieval; callback URLs are an SSRF boundary. Do not let model output choose unrestricted callback, tool, or UI destinations.
Failure, Privacy, and Accessibility
Define reconnection, cancellation, duplicate messages, timeout after an effect, ordering, backpressure, artifact expiry, deletion propagation, and outcome-unknown behavior before launch.
The UI contract must also include keyboard interaction, semantic labels, focus management after updates, contrast, localization, right-to-left layout, and a fixed fallback. Label Agent-generated content and reserve credentials, payment, and permission controls for product-owned components.
Log redacted correlation IDs, contract revisions, policy decisions, lifecycle classes, and latency. Do not default to retaining raw prompts, hidden reasoning, private artifacts, or full UI payloads.
Test the Composition
| Test class | Failure to demonstrate |
|---|---|
| Authorization | cross-tenant task, stale object, model-provided identity |
| Task recovery | duplicate request, reconnect gap, cancellation race, uncertain effect |
| Artifact safety | oversized file, unsafe MIME type, expired reference, SSRF callback |
| UI safety | unknown component, unsafe URL, prompt-injected label, payload flood |
| Accessibility | keyboard-only flow, screen-reader update, long translation |
| Upgrade | old client, schema mismatch, transport rollback |
Choose the smallest composition that passes these tests for representative user tasks.
FAQ
Is MCP required for every Agent tool?
No. A local function may be more appropriate. Use a protocol boundary when it provides a real interoperability or ownership benefit, while preserving server-side authorization and result limits.
Can a remote Agent send UI directly to the user?
Treat it as a proposal. A host renderer should validate and scope the payload, preserve product-owned controls, and route any action through the host's server-side policy.
Are protocol schemas security controls?
They are useful input validation controls. They do not authenticate a caller, determine object ownership, constrain external effects, or make generated content trustworthy.