Interop matrix
For: application developers.
Use this matrix to check which protocol features were exercised with each client's own SDK. Every agent and flow is available as an MCP tool and an A2A agent. The rows report test results; they do not compare products or establish whether an unlisted client works.
make interop starts a dev server, a node and two agents: one answers and one asks a person a question. Each client runs with an operator's tenant API key, another operator's key and a key scoped to one agent. For the OAuth check, a person signs in through a fake identity provider and approves the client in the console; that check uses no API key.
Last run: 2026-10-21, on orchestrator-zero 5af15e15e (commit 5af15e15e, built 2026-10-20T23:05:25Z, go1.27.2 linux/amd64).
pass is a feature that did what the protocol and this server's documentation say; n/a is a feature the client does not offer (its protocol revision or its SDK has none), as its note says; fail did not work, and its note says how. An empty cell is a feature that client's driver does not run.
MCP clients
| Client | Version | Tested | handshake | tools/list | tools/call | input required (MRTR) | Tasks extension | subscriptions/listen | conversation | scoped key | OAuth |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Python MCP SDK (mcp 1.x) | 1.30.0 | 2026-10-21 | pass | pass | pass | n/a | n/a | n/a | pass | pass | pass |
| Python MCP SDK (mcp) | 2.3.0 | 2026-10-21 | pass | pass | pass | pass | pass | pass | pass | pass | pass |
| Python MCP SDK (mcp), legacy mode | 2.3.0 | 2026-10-21 | pass | pass | pass | n/a | n/a | n/a | pass | pass | pass |
| TypeScript MCP SDK (@modelcontextprotocol/client) | 2.3.1 | 2026-10-21 | pass | pass | pass | pass | n/a | pass | pass | pass | pass |
| TypeScript MCP SDK (@modelcontextprotocol/client), legacy mode | 2.3.1 | 2026-10-21 | pass | pass | pass | n/a | n/a | n/a | pass | pass | pass |
| TypeScript MCP SDK (@modelcontextprotocol/sdk) | 1.32.1 | 2026-10-21 | pass | pass | pass | n/a | n/a | n/a | pass | pass | pass |
- handshake:
server/discoverfor MCP 2026-07-28,initializefor 2025-11-25 and before. - input required (MRTR): the agent asks a person a question; the client's elicitation handler answers it and the SDK retries the call with the answer.
- Tasks extension:
tasks/get,tasks/updatewith the answer, andtasks/cancel. - conversation: a call that sends back
_meta["ai.oz0/conversation"]is the next turn; another key of the same tenant that sends it is told it is unknown. - scoped key: a key scoped to one agent lists and calls only that agent.
- OAuth: no key at all: the client finds the tenant's authorization server from the 401, registers with a loopback redirect address, sends the person to the console, where they sign in through the identity provider and approve the client, redeems the code with PKCE, and then lists and calls the person's agents as them.
- input required (MRTR), n/a: 2025-11-25 has no input_required results; the question waits in the inbox (Python MCP SDK (mcp 1.x)
1.30.0, Python MCP SDK (mcp), legacy mode2.3.0, TypeScript MCP SDK (@modelcontextprotocol/client), legacy mode2.3.1, TypeScript MCP SDK (@modelcontextprotocol/sdk)1.32.1). - Tasks extension, n/a: the SDK has no client for the Tasks extension: it refuses a task as tools/call's result (TypeScript MCP SDK (@modelcontextprotocol/client)
2.3.1). - Tasks extension, n/a: the Tasks extension needs 2026-07-28 (Python MCP SDK (mcp 1.x)
1.30.0, Python MCP SDK (mcp), legacy mode2.3.0, TypeScript MCP SDK (@modelcontextprotocol/client), legacy mode2.3.1, TypeScript MCP SDK (@modelcontextprotocol/sdk)1.32.1). - subscriptions/listen, n/a: subscriptions/listen is 2026-07-28's (Python MCP SDK (mcp 1.x)
1.30.0, Python MCP SDK (mcp), legacy mode2.3.0, TypeScript MCP SDK (@modelcontextprotocol/client), legacy mode2.3.1, TypeScript MCP SDK (@modelcontextprotocol/sdk)1.32.1).
A2A clients
| Client | Version | Tested | signed card | SendMessage, blocking | SendMessage, returnImmediately + GetTask | input required | CancelTask | contexts | artifact hash | scoped key | ADK's default client |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A2A JavaScript SDK (@a2a-js/sdk) | 1.3.0 | 2026-10-21 | pass | pass | pass | pass | pass | pass | pass | pass | |
| Google ADK (google-adk), RemoteA2aAgent | 2.11.0 | 2026-10-21 | n/a | pass | pass | pass | pass | ||||
| Python A2A SDK (a2a-sdk) | 1.2.2 | 2026-10-21 | pass | pass | pass | pass | pass | pass | pass | pass |
- signed card: the card's JWS (ES256 over its RFC 8785 form) verified with the key at its
jku. - contexts: a message with a task's
contextIdis the next turn; another key of the same tenant that sends it is told it is unknown. - artifact hash:
metadata["ai.oz0/sha256"]recomputed from the parts the client received. - The server speaks A2A 1.0 only, and refuses a client of 0.3 (no
A2A-Version: 1.0header) with-32009. - signed card, n/a: ADK resolves the card by its URL and does not check its signature (Google ADK (google-adk), RemoteA2aAgent
2.11.0).
Our A2A client
Outside A2A agents run through the A2A client of the verified plugin a2a-remote. make interop also runs it against a sample server built with each A2A SDK, on loopback, as a job's session would call an outside agent.
| Server | Version | Tested | card and interface | signed card | SendMessage + GetTask | input required | CancelTask |
|---|---|---|---|---|---|---|---|
| A2A JavaScript SDK (@a2a-js/sdk), its server | 1.3.0 | 2026-10-21 | pass | pass | pass | pass | pass |
| Python A2A SDK (a2a-sdk), its server | 1.2.2 | 2026-10-21 | pass | pass | pass | pass | pass |
- card and interface: the card read, and its JSON-RPC interface on the card's own origin chosen.
- signed card: the card's signature (the SDK's own signer) verified with the key at its
jku. - SendMessage + GetTask: a task sent with
returnImmediatelyand read until it answers. - input required: the server's question answered, as a person answers it, and the task completed.
- CancelTask: a running task cancelled when the job is.
Agent frameworks as harnesses
The harness templates run an agent written for one of these frameworks as one session of a job. make interop-frameworks installs each template's example plugin on a dev server with two nodes and runs it against a fake model API behind the gateway, with the framework's own pinned release. Last run: 2026-10-21, on orchestrator-zero 5af15e15e (commit 5af15e15e, built 2026-10-20T23:05:25Z, go1.27.2 linux/amd64).
| Framework | Version | Tested | turn | tool call asked and answered | quota resume | budget refusal | lost node |
|---|---|---|---|---|---|---|---|
| AWS Strands (strands-agents) | 1.59.0 | 2026-10-21 | pass | pass | pass | pass | pass |
| Google ADK (google-adk) | 2.11.0 | 2026-10-21 | pass | pass | pass | pass | |
| Microsoft Agent Framework (agent-framework-core) | 1.21.0 | 2026-10-21 | pass | pass | pass | pass |
- turn: a job ran as one session; every model call went through the gateway, in the framework's own API, and was metered under the job.
- tool call asked and answered: each of the framework's tool calls was asked as
<harness>.<tool>; a read ran, a write waited for a person's approval and ran once approved. - quota resume: a quota refused the session's second model call; once it was raised, the session was started again with its session ID and went on from its conversation.
- budget refusal: the gateway's refusal stopped the session's call, and the job waited for a budget decision instead of failing.
- lost node: the node was killed under a session; the restart waited for a person, and a fresh session ran from scratch on the other node.
- Each is governed and metered, and durable only as one step: a lost node starts the session again from scratch, and that restart asks a person first unless lint proves the agent's tools only read.
Not tested
| Client | Protocol | Why not | What a run needs |
|---|---|---|---|
| Claude Code | mcp | Its MCP client runs inside the agent, and driving it headless (claude -p) needs a signed-in account or an API key and calls the provider's model; the harness signs in to nothing and calls no outside service | A test account or key, and a decision to call the provider from the harness; then claude mcp add --transport http with the tenant's /mcp URL, which connects as a person with OAuth, or with a tenant key |
| Cursor | mcp | Its MCP client runs in the editor and its command-line agent, both of which need a signed-in account | A test account; the client then connects to the tenant's /mcp URL as a person with OAuth |
| Visual Studio Code | mcp | Its MCP client is part of the chat's agent mode, which needs a signed-in account; nothing drives it headless without one | A test account; the client then connects to the tenant's /mcp URL as a person with OAuth |
| IBM watsonx Orchestrate | mcp, a2a | A hosted service | A trial account, and an edge it can reach |
| Microsoft Copilot Studio | mcp, a2a | A hosted service | A trial tenant, and an edge it can reach |
| Google ADK, hosted parts (Agent Engine) | a2a | A hosted service; ADK's own A2A client, which runs locally, is tested above | A cloud project, and an edge it can reach |
Product names belong to their owners and are used here only to say which client or framework was run; a row says what we ran, not that the owner endorses it.