Microsoft Foundry supports the Agent-to-Agent (A2A) protocol for hosted agents, enabling seamless communication with agents built using other frameworks and technologies. This ensures interoperability across diverse agent ecosystems.
Building an agent shouldn't require you to reinvent platform capabilities. With Microsoft Foundry, hosted agents inherit essential capabilities such as authentication, ingress management, A2A transport, and discovery endpoints, freeing developers to focus on business logic and user value.
This article demonstrates the quickest route from a local containerized agent to a fully discoverable A2A endpoint hosted in Foundry. You'll learn how to enable A2A support so other agents can discover your agent through its agent card and communicate with it using the standardized Agent-to-Agent protocol.
Important
The hosted-agent surface is evolving. Pin your Azure Developer CLI (azd) extension and SDK versions and validate field names against the schema installed in your environment.
What you will build
By the end, you will have:
- A container listening on port 8088
- A native POST /invocations endpoint
- A POST /responses endpoint used as the A2A bridge
- An Entra-protected Foundry endpoint
- A published agent card that other agents can retrieve
The example uses a request-validation agent. It accepts structured JSON and returns a verdict such as approve, reject, or needs_human_review. The same pattern applies to agents in other business domains.
The key idea: two configuration planes
Hosted-agent deployments combine two related but independent configuration planes:
| Plane | Defines | Configured through |
| Control plane | The agent version, image or source build, environment, resources, and supported protocol versions | azure.yaml and azd |
| Data plane | The live endpoint, authentication, enabled protocols, and published agent card | agentEndpoint and endpoint update commands |
A protocol declared on the agent version but omitted from the endpoint is not available to callers. The reverse is also true. Both declarations are required.
The request path looks like this:
A2A client
| JSON-RPC over HTTPS with an Entra bearer token
v
Foundry data plane
| validates identity, terminates A2A, and bridges to Responses
v
Your container on port 8088
|-- GET /readiness
|-- POST /invocations
|-- POST /responses
This distinction prevents a common design mistake: adding application-level A2A routes to a Foundry-hosted container. Foundry already owns that transport layer.
Start with the runtime contract
Before configuring azure.yaml, make the container satisfy the hosted runtime contract:
- Serve plain HTTP on port 8088. TLS terminates upstream.
- Expose a fast, shallow GET /readiness check.
- Handle SIGTERM and drain in-flight work.
- Emit OpenTelemetry signals for platform correlation.
The Azure agent server packages provide the hosting pieces. A representative dependency set is:
dependencies = [
"azure-ai-agentserver-core==2.0.0",
"azure-ai-agentserver-invocations==1.0.0",
"azure-ai-agentserver-responses==1.0.0b9",
"agent-framework-core==1.13.0",
"agent-framework-foundry==1.10.4",
]
Keep readiness shallow
Do not put downstream dependency probes in /readiness. A temporary outage in a model, database, or reference-data service should not make the platform recycle a container that can still accept and report requests correctly.
Expose a separate deep health endpoint for deliberate operational diagnostics, for example GET /health.
Serve Invocations and Responses from one host
Invocations is the direct, native contract. Responses is the chat-shaped protocol that Foundry uses as the landing zone for A2A traffic.
Compose the protocol hosts and point both handlers at the same service:
from azure.ai.agentserver.invocations import InvocationAgentServerHost
from azure.ai.agentserver.responses import ResponsesAgentServerHost
class AgentHost(InvocationAgentServerHost, ResponsesAgentServerHost):
"""Expose both hosted-agent protocols."""
def create_app() -> AgentHost:
host = AgentHost()
host.invoke_handler(invocations.handle)
host.response_handler(responses.handle)
return host
Sharing the service is important. A request should produce the same decision, findings, correlation data, and error envelope whether it arrived through direct invocation or A2A.
A native invocation handler can remain straightforward:
async def handle(self, request: Request) -> Response:
correlation_id = current_correlation_id()
try:
payload = await request.json()
except ValueError:
return self._error(ValidationError("Request body is not valid JSON"), correlation_id)
if not isinstance(payload, dict):
return self._error(ValidationError("Request body must be a JSON object"), correlation_id)
try:
result = await self._service.run(payload, correlation_id=correlation_id)
except Exception as error:
return self._error(error, correlation_id)
return JSONResponse(result.model_dump(by_alias=True))
Keep failures behind one stable envelope. Do not expose stack traces, internal exception text, or raw dependency payloads to callers.
{
"detail": "The upstream authorization request failed.",
"correlationId": "00000000-0000-0000-0000-000000000000",
"errorCode": "AUTHENTICATION_ERROR"
}
Configure the hosted agent
The following is the important shape of azure.yaml. Keep environment-specific values in the azd environment or infrastructure outputs instead of branching the application source.
services:
ai-project:
host: azure.ai.project
test-agent:
project: .
host: azure.ai.agent
language: python
uses:
- ai-project
env:
CONFIDENCE_THRESHOLD: "0.6"
MODEL_DEPLOYMENT: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
KEY_VAULT_URL: ${keyVaultUri}
LOG_FORMAT: json
LOG_LEVEL: INFO
codeConfiguration:
dependencyResolution: remote_build
entryPoint: main.py
runtime: python_3_13
container:
resources:
cpu: "1"
memory: 2Gi
kind: hosted
name: test-agent
startupCommand: python main.py
protocols:
- protocol: responses
version: 2.0.0
- protocol: invocations
version: 2.0.0
Remote Build feature lets Foundry build the image from the published source. Retain a Dockerfile for local reproduction and for workloads that later need a custom base image or system packages.
Caution
azure.yaml is committed to source control. Put names, URLs, and tuning values there, but keep secret material in Key Vault and resolve it at startup through managed identity.
Enable A2A through the endpoint
A2A is enabled at the data-plane endpoint. Add the agent card and endpoint configuration after the version-level protocols are declared:
Other agents and model-driven planners use the card to decide whether to call your agent. Write it for routing, not marketing. State the input, output, terminal outcomes, modality, and constraints.
A useful card description answers three questions quickly:
- What information does the agent accept?
- What does it return?
- When should another agent choose it?
agentCard:
description: Validates a request and returns a verdict with findings and a confidence score.
version: "1.0"
skills:
- id: validate-request
name: Validate Request
description: Accepts a JSON request as text and returns a JSON verdict.
tags: [validation, decision-support, automation]
agentEndpoint:
authorizationSchemes:
- type: Entra
protocols:
- responses
- invocations
- a2a
Notice the asymmetry:
- responses and invocations appear in the agent version because the container implements them.
- a2a appears in the endpoint because Foundry provides and exposes the A2A transport.
- responses must be enabled before A2A because it is the bridge target.
Apply the endpoint configuration after deploying the agent version:
azd ai agent endpoint update change-validator --no-prompt
azd ai agent endpoint show --output json
Preserve correlation across the platform boundary
Foundry forwards headers prefixed with x-client- into the container. Standardize on x-client-correlation-id and resolve it with this precedence:
- The x-client-correlation-id header
- The trace ID from a W3C traceparent header
- A newly generated UUID
Also bind the platform identifiers to logs and audit records:
| Header | Audit or log field |
| x-agent-session-id | agent_session_id |
| x-agent-user-id | agent_user_id |
| x-agent-foundry-call-id | foundry_call_id |
Echo the correlation ID in the response header and body, propagate it to downstream calls, and index it in the audit store. One value should connect the caller report, container trace, audit record, and provider logs.
Use two checks. A successful deployment is not enough if the endpoint protocols were not applied.
Check the live protocols
azd ai agent endpoint show --output json
Confirm that the endpoint reports both a2a and responses.
Retrieve the published card
curl -H "Authorization: Bearer $token" `
"$projectEndpoint/agents/test-agent/endpoint/protocols/a2a/agentCard/v1.0"
A 404 usually means the endpoint update did not apply. If the card resolves but calls fail, verify that responses is present in the endpoint protocol list.
Below are few limitations that needs attention while exposing an agent using A2A protocol.
Note
- Only text modality is supported. File data and other nontext modalities aren't supported.
- Streaming responses (server-sent events) aren't supported.
- Incoming A2A requires the responses protocol. Agents that don't use the responses protocol can't be exposed as A2A endpoints.
Troubleshooting checklist
When a hosted A2A deployment fails, check these in order:
- The container listens on port 8088 and uses plain HTTP.
- Both version-level protocols are declared.
- The endpoint enables responses, invocations, and a2a.
- The endpoint update ran after the agent version was deployed.
- The card URL returns successfully.
- The calling workload identity has the required Foundry permissions.
- JSON text is parsed after removing optional Markdown fences.
- Correlation headers use the x-client- prefix.
- Readiness remains shallow and fast.
References:
Enable incoming A2A on a Foundry agent - Microsoft Foundry | Microsoft Learn
Author azure.yaml for hosted agents - Microsoft Foundry | Microsoft Learn
Host Microsoft Agent Framework agents as Foundry hosted agents - Microsoft Foundry | Microsoft Learn