Agentic AI AcademyAgentic AI Academy
AgentCore Runtime/Lesson 4 of 6

Protocols: HTTP, MCP, A2A & AG-UI

One container, four wire contracts

Intermediate 14 minBuilder
What you'll be able to do
  • Explain why a Runtime declares a single protocol mode and how AWS routes the container port for each of HTTP, MCP, A2A, and AG-UI
  • Compare the four protocols across port, mount path, message format, discovery mechanism, and authentication
  • Host an MCP server inside Runtime using `streamable-http` on `POST /mcp`, defaulting to `stateless_http=True` and accepting the platform-generated `Mcp-Session-Id`
  • Decide when stateful MCP (`stateless_http=False`) is required — elicitation and sampling — versus the stateless default
  • Serve an A2A agent on port 9000 with `serve_a2a`, exposing an Agent Card at `/.well-known/agent.json`
  • Identify the per-protocol discovery and auth differences (SigV4 vs OAuth 2.0, 403 vs 401 responses, session headers)
At a glance

An AgentCore Runtime container declares exactly ONE protocol mode, and AWS routes the container's listening port accordingly. This lesson compares the four supported wire contracts — HTTP, MCP, A2A, and AG-UI — by port, path, format, discovery, and auth, then goes deep on the two that change how you write your server: hosting an MCP server inside Runtime (streamable-http, stateless by default, the platform-supplied `Mcp-Session-Id` you must accept) and serving Agent-to-Agent (A2A) with the SDK's `serve_a2a` helper and Agent Cards.

  1. 1One container, one declared protocol
  2. 2The four protocols side by side
  3. 3HTTP: the default request/response surface
  4. 4Hosting an MCP server inside Runtime
  5. 5A2A: serving an agent other agents can find
  6. 6AG-UI, plus the discovery and auth differences

One container, one declared protocol

AgentCore Runtime is framework- and protocol-agnostic: it doesn't care whether your agent is Strands, LangGraph, or hand-rolled, and it speaks four different wire contracts. The catch is that a single Runtime declares exactly one protocol mode, and AWS routes the container's listening port to match it. You don't run all four at once in one container — you pick the contract that fits the consumer.

Think of the protocol as the shape of the conversation at the container boundary, independent of the agent logic inside:

  • HTTP — a request/response (or streaming) REST surface for apps and the AWS SDK. The default for "invoke my agent."
  • MCP — your container is a Model Context Protocol server; clients call it as a tool provider over JSON-RPC.
  • A2A — your container is an Agent-to-Agent peer that other agents discover and call, also JSON-RPC.
  • AG-UI — an interactive event stream for rendering an agent's progress in a UI.

The protocol choice drives three concrete things: which port the container must listen on, which path it mounts the handler at, and what message format travels on the wire. Auth (SigV4 or OAuth 2.0) and the session model layer on top.

Key insight

Protocol is a container contract, not agent logic

The same reasoning agent can be exposed over any of the four protocols — you change the server wrapper, not the brains. HTTP via BedrockAgentCoreApp, MCP via FastMCP's streamable-http, A2A via serve_a2a. Pick the protocol by who is calling: an app/SDK (HTTP), an MCP client (MCP), another agent (A2A), or a live UI (AG-UI).

The four protocols side by side

Here is the full comparison. Memorize the port-to-protocol mapping — a container listening on the wrong port for its declared mode simply won't receive traffic.

FeatureHTTPMCPA2AAG-UI
Port8080800090008080
Mount path/invocations, /ws/mcp/ (root)/invocations (SSE), /ws
FormatREST JSON / SSE / WebSocketJSON-RPCJSON-RPC 2.0Event streams (SSE / WebSocket)
DiscoveryN/ATool listingAgent Cards (/.well-known/agent.json)N/A
AuthSigV4, OAuth 2.0SigV4, OAuth 2.0SigV4, OAuth 2.0SigV4, OAuth 2.0

A few things to notice immediately:

  • HTTP and AG-UI share port 8080. HTTP is the general-purpose request/response surface (plus optional streaming); AG-UI is the same port but oriented at interactive UI event streams.
  • MCP and A2A both speak JSON-RPC, but they answer different questions. MCP exposes tools ("what can you do for me?"); A2A exposes an agent ("who are you and how do I delegate to you?").
  • Discovery differs sharply. MCP clients enumerate tools via tool listing; A2A peers fetch a structured Agent Card from /.well-known/agent.json. HTTP and AG-UI have no built-in discovery contract.
  • Auth is uniform across all four: every protocol accepts either AWS SigV4 (IAM-signed requests) or OAuth 2.0 (JWT bearer tokens). What differs is the failure response and the session header, covered later in this lesson.

Source for this contract: the Runtime service contract documentation (runtime-service-contract.html).

Watch out

Wrong port = silent failure

AWS routes traffic to the port that matches your declared protocol mode: HTTP/AG-UI on 8080, MCP on 8000, A2A on 9000. If your MCP server binds 8080 instead of 8000, the platform routes MCP traffic to a port nothing is listening on. Bind the port the protocol requires (FastMCP and serve_a2a default to the right ports), and bind to host 0.0.0.0, not 127.0.0.1.

HTTP: the default request/response surface

HTTP is the contract you get from BedrockAgentCoreApp and the one the AWS SDK's InvokeAgentRuntime data-plane call targets. The container runs on host 0.0.0.0, port 8080, platform ARM64.

Endpoints:

  • POST /invocations — the primary entrypoint. Input is JSON; the response is either JSON (Content-Type: application/json, e.g. {"response":"...","status":"success"}) or Server-Sent Events (Content-Type: text/event-stream, with lines of the form data: {...}) when you stream.
  • GET /ping — health check. Returns HTTP 200 with body {"status": "<Healthy|HealthyBusy>", "time_of_last_update": <unix_ts>}.
  • /ws — optional WebSocket on the same port 8080, established via an HTTP-Upgrade handshake (101); carries text and binary; the session is bound via the X-Amzn-Bedrock-AgentCore-Runtime-Session-Id header.

You can exercise the exact cloud contract locally with curl — there is nothing AWS-specific about the wire format:

bash
curl -X POST http://localhost:8080/invocations \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"What is the weather?"}'

Auth response semantics differ by auth mode — this is the cleanest way to tell, from a 4xx alone, how an agent is protected:

text
# OAuth-protected agent, missing/invalid token:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource?qualifier={QUALIFIER}"

# SigV4 (IAM)-protected agent, unauthorized caller:
HTTP/1.1 403 Forbidden
ACCESS_DENIED            # note: NO WWW-Authenticate header

An OAuth agent returns 401 + a WWW-Authenticate: Bearer … header pointing at its protected-resource metadata; a SigV4 agent returns 403 + ACCESS_DENIED with no WWW-Authenticate.

Tip

Streaming is just a Content-Type switch

The same POST /invocations endpoint serves a single JSON body or an SSE stream — the client distinguishes them by Content-Type. With BedrockAgentCoreApp, an async generator entrypoint auto-serves text/event-stream and each yield becomes one data: SSE event. No separate endpoint, no separate protocol mode.

Hosting an MCP server inside Runtime

When you declare the MCP protocol, your container becomes a Model Context Protocol server that MCP clients call as a tool provider. The contract is specific:

  • Port 8000, single endpoint POST /mcp, messages are JSON-RPC.
  • Transport must be streamable-http. This is non-negotiable — the older stdio transport is for local processes, not a hosted Runtime; SSE-only transport is not the contract here.
  • Default to stateless mode: stateless_http=True. Stateless is the right default for a horizontally-scaled, session-isolated Runtime where any microVM can answer any request.
  • The platform always injects and returns an Mcp-Session-Id. Your server must accept the platform-generated ID — it must not reject or override it. This is the single most common mistake when porting a local MCP server: a server that insists on minting its own session ID will fight the platform.

The minimal hosted MCP server, using the official mcp SDK's FastMCP:

python
from mcp.server.fastmcp import FastMCP

# Bind 0.0.0.0; stateless is the Runtime default.
mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b

if __name__ == "__main__":
    # streamable-http is REQUIRED; served at /mcp on port 8000.
    mcp.run(transport="streamable-http")

When do you need stateful mode (stateless_http=False)? Only when the MCP interaction is genuinely multi-turn at the protocol level — specifically the two MCP capabilities that require the server to hold a live session and call back to the client:

  • Elicitation — the server pauses a tool call to prompt the client (and through it, the user) for additional input mid-flight.
  • Sampling — the server asks the client's LLM to generate a completion on its behalf.

If you use neither, stay stateless. Stateful mode trades scale-out simplicity for the ability to keep a conversation pinned, so reach for it only when elicitation or sampling forces your hand.

Watch out

Accept the platform's Mcp-Session-Id — don't generate your own

AgentCore always supplies the Mcp-Session-Id header and round-trips it. Your server must accept it as-is. A FastMCP server configured to generate or validate its own session IDs against a local store will reject the platform's ID and break the session. Let the platform own session identity; you just honor it.

Note

The MCP session header is different from HTTP's

MCP sessions ride the Mcp-Session-Id header. HTTP, A2A, and AG-UI all use X-Amzn-Bedrock-AgentCore-Runtime-Session-Id instead. The header name is part of each protocol's contract — don't assume one fits all four.

A2A: serving an agent other agents can find

The Agent-to-Agent (A2A) protocol turns your Runtime into a peer that other agents discover and delegate to — the multi-agent building block. The contract:

  • Port 9000, mounted at / (root), messages are JSON-RPC 2.0.
  • Discovery via Agent Cards served at /.well-known/agent.json — a structured document describing the agent's identity, skills, and how to call it. This is the A2A analog of MCP's tool listing.

The SDK ships a helper, serve_a2a, in the bedrock-agentcore[a2a] extra. Install the extra, wrap your framework's executor, and call serve_a2a — it wires up the Agent Card, the JSON-RPC surface, and /ping for you:

bash
pip install 'bedrock-agentcore[a2a]'
python
from strands import Agent
from strands.a2a import StrandsA2AExecutor
from bedrock_agentcore.runtime import serve_a2a

agent = Agent(
    model="us.anthropic.claude-sonnet-4-20250514",
    system_prompt="You are a helpful assistant.",
)

# Serves /.well-known/agent.json + JSON-RPC + /ping on port 9000.
serve_a2a(StrandsA2AExecutor(agent), port=9000)

That single serve_a2a call replaces the boilerplate of hand-building the Agent Card endpoint and the JSON-RPC router. Note the model ID in the example is illustrative — claude-sonnet-4-20250514 is now a legacy ID; pin a current model ID from the live model catalog rather than copying this one verbatim.

The practical pattern: deploy reusable specialist agents as A2A Runtimes, publish their Agent Cards, and let an orchestrator agent discover and call them as peers — composability without hard-coding endpoints.

Tip

MCP vs A2A: tools vs agents

Both are JSON-RPC, but the mental model differs. Expose MCP when you have tools/capabilities a client should call (/mcp, tool listing). Expose A2A when you have an autonomous agent other agents should discover and delegate whole tasks to (/, Agent Card at /.well-known/agent.json). Same agent brains, different framing of the contract.

AG-UI, plus the discovery and auth differences

AG-UI (Agent-to-User-Interface) is the protocol for interactive UI rendering. It shares port 8080 with HTTP and mounts at /invocations (SSE) plus the optional /ws WebSocket. The wire format is event streams — a sequence of UI-oriented events (tool calls, partial outputs, state updates) that a front end consumes over SSE or WebSocket to render an agent's progress live. Where plain HTTP streaming gives you raw data: chunks, AG-UI is structured for driving a UI surface.

Discovery — three different stories:

ProtocolHow a caller discovers what's available
HTTPN/A — you publish the request/response shape out of band
MCPTool listing — the client enumerates the server's tools over JSON-RPC
A2AAgent Card at /.well-known/agent.json
AG-UIN/A — the UI and agent agree on the event schema out of band

Auth — uniform options, different mechanics. Every protocol accepts SigV4 (IAM-signed requests, e.g. via InvokeAgentRuntime) or OAuth 2.0 (a JWT bearer token). The differences show up at the edges:

  • Failure response: an OAuth agent answers an unauthenticated call with 401 + WWW-Authenticate: Bearer …; a SigV4 agent answers an unauthorized call with 403 + ACCESS_DENIED and no WWW-Authenticate header.
  • Session header by protocol — the identifier that pins a caller to a microVM differs:
ProtocolSession header
HTTPX-Amzn-Bedrock-AgentCore-Runtime-Session-Id
MCPMcp-Session-Id
A2AX-Amzn-Bedrock-AgentCore-Runtime-Session-Id
AG-UIX-Amzn-Bedrock-AgentCore-Runtime-Session-Id

MCP is the odd one out: it uses the MCP-native Mcp-Session-Id header (the one the platform generates and you must accept), while the other three share the X-Amzn-Bedrock-AgentCore-Runtime-Session-Id header.

Example

Choosing a protocol from the consumer

Building a chatbot backend an app calls via the SDK → HTTP. Publishing a tool server for MCP clients (other agents, IDEs) to call → MCP. Standing up a specialist agent for an orchestrator to delegate to → A2A. Rendering a live 'the agent is thinking / calling a tool' UI → AG-UI. The agent code can be identical; the wrapper and declared protocol change.

Try it: Host an MCP server and an A2A agent on Runtime, and read the wire contract

Goal: experience two different protocol modes from one agent and prove you understand the port/path/session rules.

Part A — Host an MCP server (port 8000, /mcp).

  1. Create mcp_server.py with a FastMCP server bound to host="0.0.0.0", stateless_http=True, exposing one @mcp.tool() (e.g. an add(a, b) function), and mcp.run(transport="streamable-http").
  2. Run it locally and confirm it serves on port 8000 at /mcp. Inspect the response headers and confirm an Mcp-Session-Id is present. Note in a comment why your server must accept this ID rather than generate its own.
  3. Write one sentence: when would you flip stateless_http=False? (Answer should mention elicitation and sampling.)

Part B — Serve the same agent over A2A (port 9000, root).

  1. pip install 'bedrock-agentcore[a2a]'.
  2. Create a2a_server.py that builds a Strands Agent, wraps it in StrandsA2AExecutor, and calls serve_a2a(StrandsA2AExecutor(agent), port=9000). Pin a current model ID from the live Bedrock model catalog (do not copy a legacy ID verbatim).
  3. Run it and fetch GET http://localhost:9000/.well-known/agent.json. Confirm you get an Agent Card back. Hit GET /ping and confirm health.

Part C — Contrast the contracts. Fill in a small table for HTTP, MCP, A2A, and AG-UI with each one's port, mount path, message format, discovery mechanism, and session header. Then answer: which protocol uses the Mcp-Session-Id header, and which three share X-Amzn-Bedrock-AgentCore-Runtime-Session-Id? For an OAuth-protected vs a SigV4-protected agent, what status code and headers come back on an unauthenticated request?

Stretch. Sketch (no deploy required) how you'd expose the same reasoning agent over HTTP for your app AND over A2A for an orchestrator — and explain why that requires two Runtimes, not one container.

Key takeaways

  1. 1A Runtime declares exactly ONE protocol mode and AWS routes the container port to match: HTTP on 8080 (`/invocations`, `/ws`), MCP on 8000 (`/mcp`), A2A on 9000 (`/` root), AG-UI on 8080 (`/invocations` SSE, `/ws`).
  2. 2HTTP is REST JSON with optional SSE or WebSocket streaming on the same port; the SDK's `InvokeAgentRuntime` targets it, and you can test the exact contract locally with `curl` against `POST /invocations`.
  3. 3Hosting MCP inside Runtime requires the `streamable-http` transport on `POST /mcp` (JSON-RPC), defaults to `stateless_http=True`, and your server MUST accept the platform-generated `Mcp-Session-Id` rather than mint its own.
  4. 4Stateful MCP (`stateless_http=False`) is only needed for elicitation (server-initiated prompts) and sampling (server-requested LLM generations); otherwise keep it stateless for scale-out.
  5. 5A2A serves on port 9000 with JSON-RPC 2.0 and publishes an Agent Card at `/.well-known/agent.json`; the SDK helper `serve_a2a` (extra `bedrock-agentcore[a2a]`) wires the card, JSON-RPC, and `/ping`.
  6. 6AG-UI streams interactive UI events (SSE/WebSocket) on port 8080 for rendering agent progress live.
  7. 7Auth options are uniform (SigV4 or OAuth 2.0) but mechanics differ: OAuth fails with 401 + `WWW-Authenticate`, SigV4 fails with 403 + `ACCESS_DENIED`; MCP uses the `Mcp-Session-Id` session header while the other three use `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id`.

Quiz

Lock in what you learned

Check your understanding

0 / 4 answered

1.You want your AgentCore Runtime to expose tools to MCP clients AND be callable as a REST endpoint by your app's SDK at the same time, from one container. What's the correct understanding?

2.Your hosted MCP server works locally but, once deployed to Runtime, sessions break. The server is configured with `streamable-http` and `stateless_http=True`. What is the most likely cause?

3.An unauthenticated client gets `HTTP 403` with body `ACCESS_DENIED` and no `WWW-Authenticate` header from a Runtime. What does this tell you about how the agent is protected?

4.You're standing up a specialist research agent that an orchestrator agent should be able to discover and delegate tasks to. Which protocol and discovery mechanism fit, and what SDK helper serves it?

Go deeper

Hand-picked sources to keep learning