The A2A CLI on Azure: One Client for Container Apps, Agent Framework and Localhost A developer published a step-by-step guide and open-source repository showing how to run A2A 1.0 agents on Azure and drive them with a single A2A CLI. The setup pairs a Rust agent built on the official A2A Rust SDK with a Microsoft Agent Framework coordinator on Azure OpenAI's gpt-5.4-mini, both deployed to Azure Container Apps behind Entra ID and also runnable locally, with the CLI executing the same commands against all four endpoints and passing 19 of 19 checks. All access uses Entra ID tokens via az login, with no API keys anywhere in the stack. This article provides a step by step guide to running A2A agents on Azure and driving them with the A2A CLI. A Rust agent on the official A2A Rust SDK runs in Azure Container Apps. A Microsoft Agent Framework agent on an Azure OpenAI model runs in Container Apps behind Entra ID and hands its work to the Rust agent. Both also run on a laptop, and one set of a2a commands checks all four. https://github.com/xbill9/rust-dev/tree/main/a2a-agentframework-azure https://github.com/xbill9/rust-dev/tree/main/a2a-agentframework-azure The A2A CLI drove all four endpoints with the same commands, and 19 of 19 checks passed. Every hop speaks A2A 1.0: the Rust SDK serves it, Agent Framework serves it, and the Agent Framework agent calls the Rust agent with the A2A 1.0 Python client. On Azure the CLI needs one extra flag, an Entra ID bearer token, which az login already provides. Model access, registry pulls and the token all run on Entra ID, with no API key anywhere. | Agent | Where it runs | A2A | Model | |---|---|---|---| | mersenne-agent Rust | laptop, port 8304 | 1.0, JSON-RPC and REST | none | | mersenne-agent same image | Container Apps, 0.25 vCPU, 0.5 GiB | 1.0, JSON-RPC and REST | none | | Agent Framework coordinator | laptop, port 8309 | 1.0, JSON-RPC and REST | gpt-5.4-mini on Azure OpenAI | | Agent Framework coordinator same code | Container Apps, Entra ID auth | 1.0, JSON-RPC and REST | gpt-5.4-mini on Azure OpenAI | a2a CLI ──► Container Apps auth Entra ID token ──► Agent Framework Agent ──Entra ID──► Azure OpenAI gpt-5.4-mini └── find mersenne primes tool └── a2a-sdk 1.x client ──► Container Apps A2A 1.0 ──► mersenne-agent a2a CLI ────────────────────────────────────────────────────────────────────────────────► Container Apps A2A 1.0 The workload is a Lucas-Lehmer test over the first N known Mersenne prime exponents, timed by the agent itself. Every reply carries the line Found first N Mersenne primes in X ms. and a data part with requested , found and elapsed ms . Versions, read from the machine on 2026-11-06: | Component | Version | |---|---| | A2A CLI a2a | v0.3.1 | | a2a-lf / a2a-server-lf Rust SDK, A2A 1.0 | 0.4.2 / 0.5.3 | | agent-framework-core / agent-framework-openai / agent-framework-a2a | 1.24.0 / 1.18.0 / 1.0.0b261030 | | a2a-sdk Python, A2A 1.0 / azure-identity | 1.3.0 / 1.27.0 | | Python / Rust | 3.14.7 / 1.99.1 | | Azure CLI | 2.90.0 | | Model | gpt-5.4-mini 2026-03-17, GlobalStandard | | Region | eastus2 | PATH as a2a : go install github.com/a2aproject/a2a-cli@latest , then rename a2a-cli to containerapp extension, signed in with az login to a subscription where you can create resource groups and Entra ID app registrations a2a version a2a v0.3.1 git clone https://github.com/xbill9/rust-dev && cd rust-dev/a2a-agentframework-azure scripts/deploy-model.py The script creates one resource group in eastus2 holding a container registry, a user-assigned managed identity, a Container Apps environment and an AIServices account with a gpt-5.4-mini deployment. It grants "Cognitive Services OpenAI User" on the account twice: to the signed-in user, for the laptop, and to the managed identity, for the containers. bash $ az group create -n a2a-agentframework-azure-rg -l eastus2 $ az containerapp env create -n a2a-af-env -g a2a-agentframework-azure-rg -l eastus2 --logs-destination none $ az cognitiveservices account create -n a2a-af-openai-defaba -g a2a-agentframework-azure-rg -l eastus2 --kind AIServices --sku S0 --custom-domain a2a-af-openai-defaba --yes $ az cognitiveservices account deployment create ... --model-name gpt-5.4-mini --model-version 2026-03-17 --model-format OpenAI --sku-name GlobalStandard --sku-capacity 50 ✅ https://a2a-af-openai-defaba.cognitiveservices.azure.com/ deployment gpt-5.4-mini The account gets a custom subdomain, and every call to the model authenticates with Entra ID. The coordinator holds no key. scripts/start-local.sh php up: 8304 rust agent, 8309 agent framework coordinator - http://localhost:8304 The coordinator is an Agent Framework Agent with one tool, served over A2A 1.0 by agent framework a2a on the a2a-sdk server routes. The same file runs on the laptop and in Container Apps: client = OpenAIChatClient model=MODEL, azure endpoint=os.environ "AZURE OPENAI ENDPOINT" , credential=DefaultAzureCredential , agent = Agent client=client, name=NAME, description=DESCRIPTION, instructions=INSTRUCTIONS, tools= find mersenne primes handler = DefaultRequestHandler agent executor=WholeTextA2AExecutor agent, stream=True , task store=InMemoryTaskStore , agent card=card, return Starlette routes= create agent card routes card , create rest routes handler, path prefix="/rest" , create jsonrpc routes handler, "/" , DefaultAzureCredential picks up az login on the laptop and the managed identity in Azure. The CLI reads both cards and shows each one's protocol version: a2a card get -a http://localhost:8304 a2a card get -a http://localhost:8309 Name: Mersenne Prime Agent Rust Version: 0.1.0 Interfaces: JSONRPC A2A v1.0 http://localhost:8304/jsonrpc HTTP+JSON A2A v1.0 http://localhost:8304/rest Name: Agent Framework Mersenne Coordinator Version: 0.1.0 Interfaces: JSONRPC A2A v1.0 http://localhost:8309/ HTTP+JSON A2A v1.0 http://localhost:8309/rest Agent Framework's A2A package is built on a2a-sdk 1.x, the same protocol version as the Rust agent. The tool sends a data part and reads the Rust agent's structured report straight from the reply: value = Value value.struct value.update {"count": count} request = SendMessageRequest message=Message role=Role.ROLE USER, message id=os.urandom 16 .hex , parts= Part data=value , async with httpx.AsyncClient timeout=60 as http: client = await create client RUST AGENT URL, ClientConfig streaming=False, httpx client=http async for event in client.send message request : task = event.task if event.HasField "task" else None for artifact in task.artifacts if task else : for part in artifact.parts: if part.HasField "data" : report = MessageToDict part.data elif part.HasField "text" : summary = part.text The tool refuses a reply whose requested or found differs from the count the model asked for, and the instructions tell the model to quote the agent's own reply line. The model picks N; the Rust agent does the counting and the timing. Data parts cross a protobuf Struct , so integers arrive as floats and the tool reads them with int . agent-framework-a2a requires a2a-sdk 1.x, and the same machine runs a Strands rig whose a2a-sdk is pinned to 0.3: agent-framework-a2a wheel metadata Requires-Dist Requires-Dist: agent-framework-core =1.22.0,<2 Requires-Dist: a2a-sdk =1.0.0,<2 shared pyenv Python used by the Strands rig a2a-sdk 0.3.26 scripts/start-local.sh installs the coordinator's pinned requirements into build/local-site with pip install --target , and starts the coordinator with PYTHONPATH=build/local-site . The two SDK versions never meet, and the container image installs the same pins. scripts/deploy-rust.py builds docker/agent.Dockerfile , pushes it to the registry and creates a container app with external ingress. The app pulls with the managed identity, which holds AcrPull, so there is no registry password. The script computes the app's address from the environment's default domain before creating it and sets ADVERTISE URL to it, so the card points at Container Apps from the first revision. scripts/deploy-rust.py bash $ docker build --platform linux/amd64 -f docker/agent.Dockerfile -t a2aafazuredefaba.azurecr.io/mersenne-agent:20261106T153734Z /home/xbill/rust-dev/a2a-agentframework-azure $ docker push a2aafazuredefaba.azurecr.io/mersenne-agent:20261106T153734Z $ az containerapp create -n a2a-rust-mersenne ... --target-port 8080 --ingress external --registry-identity