MCP vs A2A vs Function Calling: Which Layer Does What in 2026

TL;DR
MCP vs A2A vs function calling compared on the 2026-07-28 MCP spec and A2A 1.0: what each protocol does, where they overlap, and how to stack them.
Last updated: October 17, 2026
Function calling is the model-side primitive: the model returns a structured call, and your code runs it. MCP (spec version 2026-07-28) standardizes how any host discovers and calls tools that live in a separate server, and A2A (version 1.0) standardizes how one agent hands a stateful task to another agent it cannot see inside. They are layers, not rivals. Almost every agent uses function calling, many now need MCP, and only systems that delegate work across team or vendor boundaries need A2A.
The A2A project's own docs describe the split this way: "MCP is vertical. It deepens a single agent," while "A2A is horizontal. It connects agents across that boundary." This post walks through the message flow of each layer, shows the same capability built at all three, and covers the places where the 2026 versions of MCP and A2A now overlap.
Official Sources#
All sources were checked on October 17, 2026.
| Resource | What it confirms |
|---|---|
| MCP specification 2026-07-28 | Roles, primitives and the stateless base protocol |
| MCP 2026-07-28 changelog | Sessions removed, server/discover, multi round-trip requests, deprecations |
| MCP Tasks extension | Task statuses, polling and durable task handles |
| MCP GOVERNANCE.md | MCP as a Series of LF Projects, LLC; Apache 2.0 project license |
| MCP governance and stewardship | Steering Group roles and the SEP process |
| A2A specification (latest, 1.0) | Agent Card, Task lifecycle, operations, bindings, versioning |
| A2A CHANGELOG | 1.0.0 release date, 1.0.1 patch, breaking changes |
| A2A and MCP | The A2A project's own positioning of the two protocols |
| Anthropic tool use overview | tool_use / tool_result flow, client vs server tools, token costs |
| Anthropic MCP connector | Remote MCP servers from the Messages API, tool calls only |
| OpenAI function calling guide | function_call / function_call_output, strict mode, tool_choice |
| OpenAI MCP servers guide | The Responses API mcp tool and its output items |
At a Glance#
| Function calling | MCP (2026-07-28) | A2A (1.0) | |
|---|---|---|---|
| Who talks to whom | Model and your code | Host's client and one tool server (1:1) | Client agent and an opaque remote agent |
| Wire format | Provider-specific JSON inside the model API | JSON-RPC 2.0 over stdio or Streamable HTTP | JSON-RPC 2.0, gRPC or HTTP+JSON, which must be functionally equivalent |
| Discovery | You hardcode the schemas | server/discover plus tools/list (with ttlMs, cacheScope) | Agent Card at /.well-known/agent-card.json |
| Unit of work | One call | One tool call, optionally a Task via the extension | A Task with 8 states, a context and artifacts |
| State | None; you replay history | Stateless core; explicit handles when needed | Stateful tasks by design |
| Long-running work | You build it | Tasks extension: tasks/get polling, tasks/update | SSE streaming, SubscribeToTask, push-notification webhooks |
| Human input mid-flight | Your app handles it | InputRequiredResult and elicitation | TASK_STATE_INPUT_REQUIRED, TASK_STATE_AUTH_REQUIRED |
| Versioning | Per provider API | io.modelcontextprotocol/protocolVersion in _meta on every request | A2A-Version header on every request |
| Governance | Each model vendor | LF Projects (Linux Foundation) series, Apache 2.0; Steering Group + SEP process | Linux Foundation project, Apache 2.0 |
Function Calling: The Primitive Under Everything#
Anthropic's docs put it plainly: "Tool use (also called function calling) lets Claude call functions that you define or that Anthropic provides." You send a tools array with a name, a description and a JSON Schema for each function. When Claude wants one, the response comes back with stop_reason: "tool_use" and one or more tool_use blocks. Your code runs the function and replies with a tool_result block that carries the same id. Anthropic also runs some tools on its own infrastructure (server tools such as code_execution and tool_search), but the pattern most apps start with is client tools that run in your process.
OpenAI's Responses API has the same shape under different names. The guide lists five steps: make a request with tools, receive a tool call, execute code in your application, make a second request with the tool output, and receive a final response (or more tool calls). The model emits a function_call item with a call_id, name and JSON-encoded arguments, and you send back a function_call_output with the matching call_id. Strict mode makes calls adhere to the schema. In the Responses API it is effectively the default: if you omit strict, the API normalizes your schema into strict mode when it can (falling back to best-effort calling when it cannot), and you opt out with strict: false. Chat Completions stays non-strict by default. Strict mode requires additionalProperties: false on every object and every property listed in required. tool_choice accepts auto, required, a forced function, an allowed_tools list, or none, and parallel_tool_calls: false limits a turn to zero or one call. The guide also gives a soft suggestion to "aim for fewer than 20 functions available at the start of a turn."
%% title: Plain function calling round trip
sequenceDiagram
participant App as Your app
participant API as Model API
App->>API: messages + tools (JSON Schema)
API-->>App: tool_use / function_call (name, args, id)
Note over App: Your code runs the function
App->>API: tool_result / function_call_output (same id)
API-->>App: Final answer or another tool callHere is a single order-status tool with the Anthropic TypeScript SDK. This example and the others in this post were type-checked against the package versions named in each caption; none of them were run against live endpoints.
// Type-checked against @anthropic-ai/sdk@0.135.0 (ESM, top-level await)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const tools: Anthropic.Tool[] = [
{
name: "get_order_status",
description: "Look up the shipping status of an order by its ID.",
input_schema: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
},
];
// Your code, your process: the model only ever sees the schema above.
function getOrderStatus(orderId: string): string {
return JSON.stringify({ order_id: orderId, status: "shipped" });
}
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "Where is order A-1042?" },
];
while (true) {
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
tools,
messages,
});
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason !== "tool_use") break;
const results: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use" && block.name === "get_order_status") {
const { order_id } = block.input as { order_id: string };
results.push({
type: "tool_result",
tool_use_id: block.id,
content: getOrderStatus(order_id),
});
}
}
messages.push({ role: "user", content: results });
}
What it does well. There is no infrastructure to run. The tool code lives in your process, you control every argument before it executes, and there is no extra network hop between the model's decision and your function.
Tradeoffs. The schemas belong to one app and follow one provider's wire shape. Nothing else can discover or reuse them, so the same tool used from your app, an IDE and a CLI agent ends up defined three times. Tool definitions also cost tokens on every request: Anthropic counts the tools parameter as input tokens and adds a tool-use system prompt, which is 286 tokens for Claude Opus 5.5 with tool_choice set to auto or none.
For the narrower two-way version of this comparison, see MCP vs Function Calling: When to Use Each.
MCP: A Standard Plug Between Hosts and Tool Servers#
MCP uses JSON-RPC 2.0 messages between three roles. Hosts are "LLM applications that initiate connections," clients are "[c]onnectors within the host application," and servers are "[s]ervices that provide context and capabilities." Each client "communicates with exactly one server," so a host that uses five servers runs five clients. Servers offer three primitives: tools ("[f]unctions for the AI model to execute"), resources and prompts. Clients can offer elicitation, which lets a server ask the user for input.
One architecture rule matters a lot for the comparison with A2A: servers "should not be able to read the whole conversation, nor 'see into' other servers." The host keeps the conversation. A server sees only the tool calls the host sends it.
Like A2A, MCP sits under neutral governance. Its GOVERNANCE.md states that the project "has been established as Model Context Protocol a Series of LF Projects, LLC," code and specification contributions are made under Apache License 2.0, and a Steering Group of Maintainers, Core Maintainers and Lead Maintainers runs the project. Spec changes go through Specification Enhancement Proposals (SEPs).
What changed in 2026-07-28#
The 2026-07-28 revision is a breaking one, and it moves MCP closer to a pure tool and context layer:
- Stateless core. Protocol-level sessions and the
Mcp-Session-Idheader are gone from Streamable HTTP, and so is theinitialize/notifications/initializedhandshake. Every request now carries its protocol version and client capabilities in_meta, under the keysio.modelcontextprotocol/protocolVersionandio.modelcontextprotocol/clientCapabilities. Servers that need state across calls pass explicit handles they mint themselves. server/discover. Servers must implement it to advertise their supported versions, capabilities and identity. Clients may call it before any other request.- Multi round-trip requests. Servers no longer send requests to clients. Instead of
sampling/createMessage,elicitation/createorroots/list, a server returns anInputRequiredResult(resultType: "input_required") withinputRequests, and the client retries the original request withinputResponses. Every result now carries a requiredresultTypeof"complete"or"input_required". - Cacheable tool lists.
tools/listresults must includettlMsandcacheScope("public"or"private"), and servers should return tools in a deterministic order to improve LLM prompt cache hit rates. Tool schemas can use any JSON Schema 2020-12 keyword. - Tasks moved to an extension. Long-running work now lives in the official
io.modelcontextprotocol/tasksextension, with polling viatasks/getand a newtasks/updatefor client-to-server input. - Deprecations. Roots, Sampling and Logging are deprecated, with a minimum twelve-month window. For Sampling, the suggested migration is to integrate directly with LLM provider APIs.
The two standard transports are stdio (newline-delimited messages over a subprocess the client launches) and Streamable HTTP (each message is an HTTP POST to a single endpoint, answered with JSON or a request-scoped SSE stream). The transport spec sums up the new direction: servers "do not initiate JSON-RPC requests and clients do not send JSON-RPC responses." For the full migration map, see The MCP 2026-07-28 Rewrite: What Breaks and How to Migrate.
%% title: MCP 2026-07-28 tool call
sequenceDiagram
participant Model
participant Host as Host + MCP client
participant Server as MCP server
Host->>Server: server/discover (optional)
Server-->>Host: versions + capabilities
Host->>Server: tools/list
Server-->>Host: tools, ttlMs, cacheScope
Host->>Model: Tool schemas as function definitions
Model-->>Host: Call get_order_status
Host->>Server: tools/call (_meta: version, capabilities)
alt Needs user input
Server-->>Host: resultType input_required
Host->>Server: tools/call retry + inputResponses
end
Server-->>Host: resultType complete + content
Host->>Model: Tool resultNotice the middle of that diagram: the model still uses function calling. The host turns tools/list output into function definitions for whichever model API it uses, and turns the model's tool call into a tools/call. MCP standardizes the connection between the host and the tool, not the connection between the host and the model.
Here is the same order-status capability as an MCP server. It uses v2 of the TypeScript SDK, where @modelcontextprotocol/server (and its sibling @modelcontextprotocol/client) replaces the single v1 @modelcontextprotocol/sdk package and implements the 2026-07-28 spec.
// Type-checked against @modelcontextprotocol/server@2.3.1 and zod@4.6.7
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import * as z from "zod";
serveStdio(() => {
const server = new McpServer(
{ name: "orders", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
server.registerTool(
"get_order_status",
{
title: "Order status",
description: "Look up the shipping status of an order by its ID.",
inputSchema: z.object({ order_id: z.string() }),
outputSchema: z.object({ order_id: z.string(), status: z.string() }),
annotations: { readOnlyHint: true },
},
async ({ order_id }) => {
const output = { order_id, status: "shipped" };
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output,
};
},
);
return server;
});
Any MCP host can now use this tool without knowing which model sits behind it. Two model providers can also act as the MCP client for you, server side. Anthropic's MCP connector (beta header mcp-client-2025-11-20) takes an mcp_servers array plus mcp_toolset entries in tools, and its docs note that "of the feature set of the MCP specification, only tool calls are currently supported." OpenAI's Responses API accepts a tool of type: "mcp" with server_label, server_url, require_approval, allowed_tools and an optional authorization token. Its output contains mcp_list_tools, mcp_call and mcp_approval_request items, and you set require_approval to "never" (for all tools or a named subset) only for servers you trust.
What it does well. You write a tool once and use it from every host that speaks MCP. Servers are isolated from the conversation and from each other, and the spec says there should always be a human in the loop with the ability to deny tool invocations.
Tradeoffs. You add a process or a network hop, plus a consent and auth surface to run. The protocol also moves: 2026-07-28 removed the initialize handshake outright, so a server built around it needs porting rather than waiting out a grace period. Separately, deprecated features such as Roots, Sampling and Logging get at least twelve months. Clients must treat tool annotations such as readOnlyHint as untrusted unless the server is trusted. For a longer build walkthrough, see How to Build MCP Servers.
A2A: Delegating Tasks to Agents You Cannot See Inside#
A2A describes itself as "an open standard designed to facilitate communication and interoperability between independent, potentially opaque AI agent systems," so that agents can collaborate "without needing access to each other's internal state, memory, or tools." It is an open source project under the Linux Foundation, contributed by Google and licensed Apache 2.0. Version 1.0.0 shipped on March 12, 2026, and a 1.0.1 patch followed on May 26, 2026. Patch versions do not affect compatibility, so this post says A2A 1.0. Official SDKs exist for Python, Go, JavaScript, Java, .NET and Rust.
The core objects:
- Agent Card. Every A2A server must publish one. Clients can fetch it from
https://{server_domain}/.well-known/agent-card.json, from a registry, or from direct configuration. It listssupportedInterfaces(URL, protocol binding and version),capabilities(streaming, push notifications, extensions), default input and output modes,skillsand security schemes. Cards can be signed, and the JS SDK exports averifyAgentCardSignaturehelper. - Message and Part. A message has a role (user or agent) and parts. A part holds text, raw file bytes, a file URL, or structured data.
- Task. The "fundamental unit of work," with an id, a
contextId, a status, a history and output artifacts.
A task moves through eight states: TASK_STATE_SUBMITTED, WORKING, COMPLETED, FAILED, CANCELED, REJECTED, INPUT_REQUIRED and AUTH_REQUIRED. COMPLETED, FAILED, CANCELED and REJECTED are terminal, while INPUT_REQUIRED and AUTH_REQUIRED are interrupted states. A blocking SendMessage returns when the task reaches either kind, and return_immediately: true makes it return as soon as the task exists. To answer an interrupted task, the client sends another message that references the existing taskId.
| Operation | JSON-RPC and gRPC method | REST endpoint |
|---|---|---|
| Send a message | SendMessage | POST /message:send |
| Send and stream | SendStreamingMessage | POST /message:stream |
| Read a task | GetTask | GET /tasks/{id} |
| List tasks | ListTasks | GET /tasks |
| Cancel a task | CancelTask | POST /tasks/{id}:cancel |
| Re-attach to a stream | SubscribeToTask | POST /tasks/{id}:subscribe |
| Manage push webhooks | CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig | /tasks/{id}/pushNotificationConfigs |
| Authenticated card | GetExtendedAgentCard | GET /extendedAgentCard |
Note the plural on ListTaskPushNotificationConfigs: the 1.0.0 changelog lists that rename as a breaking change. An agent that supports several bindings must provide identical functionality across them. Clients must send an A2A-Version header (for example 1.0) on every request. Agents treat an empty value as 0.3 and answer an unsupported version with VersionNotSupportedError.
Streaming responses start with a Task (or a single Message), followed by status and artifact update events. The spec's streaming notes say the stream closes when the task reaches a terminal or interrupted state (the operation section lists only terminal states, so clients should handle either behavior), so the answer to an INPUT_REQUIRED question opens a new request on the same task, as in the diagram below.
%% title: A2A task delegation
sequenceDiagram
participant Client as Client agent
participant Remote as Remote agent
Client->>Remote: GET /.well-known/agent-card.json
Remote-->>Client: Agent Card (skills, interfaces, auth)
Client->>Remote: SendStreamingMessage (A2A-Version: 1.0)
Remote-->>Client: Task (TASK_STATE_WORKING)
Remote-->>Client: TaskStatusUpdateEvent (INPUT_REQUIRED)
Note over Client,Remote: Stream may close at the interrupted state
Client->>Remote: SendStreamingMessage (same taskId, answer)
Remote-->>Client: Task (TASK_STATE_WORKING)
Remote-->>Client: TaskArtifactUpdateEvent (artifact parts)
Remote-->>Client: TaskStatusUpdateEvent (COMPLETED)Here is the client side, asking a billing agent to act on the same order. @a2a-js/sdk 1.3.0 implements A2A 1.0 (with opt-in 0.3 compatibility), and its ClientFactory reads the Agent Card and picks a transport for you. The default factory covers JSON-RPC and HTTP+JSON; gRPC is opt-in.
// Type-checked against @a2a-js/sdk@1.3.0 (ESM, top-level await)
import { randomUUID } from "node:crypto";
import { SendMessageRequest, taskStateToJSON } from "@a2a-js/sdk";
import { ClientFactory } from "@a2a-js/sdk/client";
const factory = new ClientFactory();
// Fetches https://agents.example.com/.well-known/agent-card.json and picks a
// transport the card advertises (JSON-RPC or HTTP+JSON by default; gRPC needs
// GrpcTransportFactory from @a2a-js/sdk/client/grpc).
const billingAgent = await factory.createFromUrl("https://agents.example.com");
const result = await billingAgent.sendMessage(
SendMessageRequest.fromJSON({
message: {
messageId: randomUUID(),
role: "ROLE_USER",
parts: [{ text: "Refund order A-1042 if it has not shipped." }],
},
}),
);
if ("status" in result) {
// A Task: stateful, with an id you can poll, stream or cancel later.
const state = result.status ? taskStateToJSON(result.status.state) : "unknown";
console.log(result.id, state, result.artifacts.length);
} else {
// A direct Message reply, no task created.
console.log(result.parts);
}
Two SDK details are easy to miss. SendMessageRequest.fromJSON accepts the same ProtoJSON shape that goes over the wire ("ROLE_USER", parts as { text }), so you can copy examples from the spec. And TaskState in the SDK is a numeric enum, so taskStateToJSON turns it back into a readable name such as TASK_STATE_INPUT_REQUIRED for logs.
What it does well. A2A was built for long-running, multi-turn work across organizations. The remote agent keeps its tools, memory and prompts private ("opaque execution" is one of the spec's guiding principles). Auth uses standard schemes declared in the Agent Card, including OAuth 2.0 (1.0 removed the implicit and password flows and added device code and PKCE), OpenID Connect, mutual TLS and API keys.
Tradeoffs. It is much heavier than a tool call. A server needs a task store, auth, and webhook delivery if it supports push notifications. If every agent lives in one process or one codebase, A2A adds a network protocol where a function call would do. Microsoft Agent Framework is one example of a framework that documents support for both A2A and MCP.
Where They Overlap (and Where They Do Not)#
The 2026 versions have blurred the lines, so it is worth being precise.
Long-running work exists in both. The MCP Tasks extension gives a tool call the statuses working, input_required, completed, failed and cancelled. The server decides per request whether to create a task, and the task id is a durable handle a client can resume polling after a restart. A2A tasks have eight states, SSE streaming, SubscribeToTask and push webhooks. The difference is scope. An MCP task wraps one tool call that the host's model chose. An A2A task is a conversation-scoped unit of work with its own contextId, message history and artifacts, run by an agent that makes its own decisions.
Both can pause for a human. MCP returns an InputRequiredResult and the client retries with answers. A2A moves the task to TASK_STATE_INPUT_REQUIRED (or TASK_STATE_AUTH_REQUIRED for credentials) and waits for a follow-up message on the same task. In MCP the host's user answers a question about one call. In A2A the client agent, or its user, answers a question from another agent.
An A2A agent can look like a tool. The A2A docs note that a remote agent "can expose some skills as MCP-compatible resources," which "works best when the skills are well-defined and can be called in a tool-like, stateless way." The same page adds the rule of thumb: "A2A is about agents partnering on tasks; MCP is more about agents using capabilities."
Function calling sits under both. MCP tools reach the model as function definitions. An orchestrator usually decides to call a remote A2A agent through a function or tool call as well. Neither project claims to replace the other, and the A2A docs end their comparison with "Use both together."
How to Combine Them#
A layered setup that matches each protocol's design: the orchestrator uses function calling for in-process helpers, MCP for shared tool servers, and A2A for agents that other teams or vendors own. Each remote agent keeps its own MCP servers private behind the A2A boundary.
%% title: Combining all three layers
flowchart LR
U[User] --> O[Orchestrator agent]
O -- A2A --> B[Billing agent]
O -- A2A --> S[Shipping agent]
O -- function calling --> L[In-process helpers]
B -- MCP --> P[(Payments server)]
S -- MCP --> C[(Carrier server)]
S -- MCP --> D[(Orders DB server)]The glue between the model and A2A is usually one function definition. The model decides when to delegate; your code makes the A2A call and hands the result back as a tool result.
// Type-checked against @a2a-js/sdk@1.3.0 and @anthropic-ai/sdk@0.135.0
import { randomUUID } from "node:crypto";
import Anthropic from "@anthropic-ai/sdk";
import { SendMessageRequest, taskStateToJSON } from "@a2a-js/sdk";
import { ClientFactory } from "@a2a-js/sdk/client";
// To the model, a whole remote agent is just one more function definition.
export const delegateTool: Anthropic.Tool = {
name: "delegate_to_billing",
description:
"Hand a billing or refund request to the billing team's agent. " +
"Use for refunds, invoices and payment disputes.",
input_schema: {
type: "object",
properties: { request: { type: "string" } },
required: ["request"],
},
};
const billingAgent = await new ClientFactory().createFromUrl(
"https://agents.example.com",
);
// Your code runs this when the model emits a delegate_to_billing tool_use block.
export async function delegateToBilling(request: string): Promise<string> {
const result = await billingAgent.sendMessage(
SendMessageRequest.fromJSON({
message: {
messageId: randomUUID(),
role: "ROLE_USER",
parts: [{ text: request }],
},
}),
);
if (!("status" in result)) {
return result.parts
.map((p) => (p.content?.$case === "text" ? p.content.value : ""))
.join("\n");
}
const state = result.status ? taskStateToJSON(result.status.state) : "unknown";
const text = result.artifacts
.flatMap((a) => a.parts)
.map((p) => (p.content?.$case === "text" ? p.content.value : ""))
.join("\n");
// Return the task id too, so a later turn can poll or cancel it.
return JSON.stringify({ taskId: result.id, state, text });
}
Practical rules for this setup:
- Return the task id to the model. If the billing agent comes back with
TASK_STATE_INPUT_REQUIRED, the next turn needs that id to answer on the same task. Dropping it forces a new task and loses the remote agent's context. - Do not put A2A between agents in the same process. Framework handoffs and subagents cover that case with no network, auth or task store. See How to Coordinate Multiple AI Agents for those patterns.
- Keep consent where each spec puts it. MCP expects the host to keep a human able to deny tool calls, and to treat annotations from untrusted servers as untrusted. A2A declares auth requirements in the Agent Card, so check them, and the card's signature if present, before sending anything sensitive.
- Pin versions on both protocols. Send
A2A-Version: 1.0(the JS SDK'sA2A_PROTOCOL_VERSIONconstant is"1.0"), because an empty header means 0.3. On the MCP side, a server written against 2025-11-25 that still expectsinitializeneeds porting before it speaks 2026-07-28. - Keep tool lists stable. Deterministic
tools/listordering helps prompt caching, and each function definition costs input tokens on every request, so the delegation tool should replace a set of billing tools, not sit beside them.
Pick by Task#
| Task | Pick | Why |
|---|---|---|
| One app, a few tools, one provider | Function calling | No extra moving parts; the code stays in your process |
| The same tool from Claude Code, an IDE and your own app | MCP server | Write once, any MCP host can discover and call it |
| A tool that needs user confirmation or runs a long job | MCP (InputRequiredResult or the Tasks extension) | Standard pause, retry and polling semantics |
| Hand a multi-step job to another team's or vendor's agent | A2A | Stateful tasks, opaque execution, standard auth and discovery |
| Several agents inside one codebase | Framework handoffs or subagents | A2A adds a network protocol you do not need |
| A cross-org agent that has its own tools | A2A outside, MCP inside | Each protocol at the boundary it was designed for |
In moving parts, function calling costs nothing beyond your app, MCP costs a server process or endpoint, and A2A costs an agent service with a task store, auth and, optionally, webhook delivery. Start at the lowest layer that solves the problem and add the next one when a real boundary appears.
FAQ#
Is A2A a replacement for MCP?#
No. The A2A project positions them as complementary: "MCP is vertical. It deepens a single agent," and "A2A is horizontal. It connects agents across that boundary." A common setup uses A2A between agents and MCP inside each agent for its tools.
Do I still need function calling if I use MCP?#
Yes. The model still emits tool calls through its provider's tool-use API, and the host maps those calls to tools/call. Anthropic's MCP connector and OpenAI's mcp tool do that mapping on the provider's side, and Anthropic's connector currently supports tool calls only.
Is MCP still stateful after 2026-07-28?#
No. The core protocol is stateless: sessions, the Mcp-Session-Id header and the initialize handshake were removed, and every request carries its version and capabilities in _meta. Servers that need state pass explicit handles, and long-running work uses the Tasks extension.
Are MCP and A2A both under the Linux Foundation?#
Yes. A2A is a Linux Foundation project licensed Apache 2.0. MCP is established as a Series of LF Projects, LLC, takes code and specification contributions under Apache 2.0, and is run by a Steering Group that reviews spec changes as SEPs.
Which SDK versions were these examples checked against?#
@anthropic-ai/sdk 0.135.0, @modelcontextprotocol/server 2.3.1 with zod 4.6.7, and @a2a-js/sdk 1.3.0, compiled with TypeScript 7.0.2 in strict mode. They were type-checked only, not run against live endpoints.
Sources#
- https://modelcontextprotocol.io/specification/2026-07-28
- https://modelcontextprotocol.io/specification/2026-07-28/changelog
- https://modelcontextprotocol.io/specification/2026-07-28/architecture
- https://modelcontextprotocol.io/specification/2026-07-28/server/tools
- https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
- https://modelcontextprotocol.io/extensions/tasks/overview
- https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/GOVERNANCE.md
- https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/community/governance.mdx
- https://a2a-protocol.org/latest/specification/
- https://a2a-protocol.org/latest/topics/a2a-and-mcp/
- https://github.com/a2aproject/A2A/blob/main/CHANGELOG.md
- https://github.com/a2aproject/A2A
- https://github.com/a2aproject/a2a-js
- https://github.com/modelcontextprotocol/typescript-sdk
- https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- https://platform.claude.com/docs/en/agents-and-tools/mcp-connector
- https://developers.openai.com/api/docs/guides/function-calling
- https://developers.openai.com/api/docs/guides/tools-connectors-mcp
Continue Reading#
- MCP vs Function Calling: When to Use Each - the two-layer version of this comparison
- The MCP 2026-07-28 Rewrite: What Breaks and How to Migrate - the full migration map for the stateless spec
- How to Coordinate Multiple AI Agents: 6 Patterns (2026) - in-process multi-agent patterns, for when you do not need A2A
Get the next comparison like this in your inbox
One email a week on MCP and the rest of the AI dev stack. Free.
Read next on AI coding tools
MCP vs Function Calling: When to Use Each
MCP servers and function calling both let AI tools interact with external systems. They solve different problems. Here is when to reach for each.
6 min readThe MCP 2026-07-28 Rewrite: What Breaks and How to Migrate
MCP 2026-07-28 breaking changes: a stateless core, deprecated Roots, Sampling and Logging, and tougher OAuth, plus a migration checklist for server authors.
11 min readMCP Goes Stateless: The 2026-07-28 Migration Guide
MCP 2026-07-28 spec migration guide: sessions and the initialize handshake are gone. What changed, what breaks, and how to move your MCP servers to stateless.
9 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








