← back
00 — opening
Last updated May 31, 2026

Four protocols, one conversation.

How an API call, MCP, and the two rival ACPs actually talk. Different seams, different words for “client” and “server”, all riding the same wire.

API app server MCP agent tools ACP · zed editor agent ACP→A2A agent agent DNS · TLS · HTTP the shared wire · every contract becomes bytes here
Each protocol is a contract between two parties. The words shift at every seam, but underneath, all four collapse into the same packets on the same wire.
scroll
01 — the wire

Before any protocol, a packet has to find you.

Every protocol on this page is, underneath, one HTTP request riding TLS riding TCP. Before the first byte reaches a server, three things happen: a name becomes an address (DNS), a secure channel opens (TLS), and a structured message crosses it (HTTP). Step through one request and watch the layers stack up.

one https request, end to end
01 URL parse 02 DNS resolve 03 TCP connect 04 TLS handshake 05 HTTP request 06 200 response — name → address — — open a secure channel — — ask & answer —
01 / url
You have a name, not an address.
Step through to follow one https:// request from a typed URL to a rendered response.
in essence
# one https GET, peeled open layer by layer
ip   = dns_resolve("api.example.com")        # name → IP, cached by TTL
sock = tcp_connect(ip, 443)               # SYN · SYN-ACK · ACK
tls  = tls_handshake(sock, sni="api.example.com")  # 1 round trip; verify cert vs a CA
tls.send("GET /v1 HTTP/2 · Host: ... · Authorization: ...")
resp = tls.recv()                          # 200 OK + headers + body
the contrast: Everything else here is a refinement of this round trip. Change the body's grammar and you get an API. Add a session and a capability handshake and you get MCP. Swap the transport for a local subprocess and you get the ACPs. Same wire, every time.
02 — the api

An API is just an agreed-on shape for the bytes.

An API is a contract: send a request shaped like this, get a response shaped like that. On the web that usually means REST over HTTP, a method, a path that names a resource, headers (including who's asking), and a JSON body. The server answers with a status code and a body. This is the layer every agent protocol is built on. MCP, both ACPs, and A2A are all, at bottom, structured API calls.

request / response, anatomized
CLIENT the caller POST /v1/messages HTTP/2 Host: api.example.com Authorization: Bearer sk-… ← who Content-Type: application/json { "model": "…", "messages": […] } ← body REQUEST HTTP/2 200 OK ← status Content-Type: application/json { "id": "msg_01…", "content": […] } ← body the caller asked for RESPONSE SERVER the resource
A method and a path name what you want. A header proves who you are. The status code is the verdict.
the status code is the verdict 2xx it worked (200 ok, 201 created). 3xx look elsewhere (301 moved). 4xx your fault: 401 means not authenticated, 403 means authenticated but not allowed, 429 means you're rate limited. 5xx the server's fault (500).
the contrast: REST gives you nouns (resources) and verbs (methods). The agent protocols keep the same envelope and swap in a richer vocabulary: instead of GET /users you'll see tools/call or session/prompt. Learn to read an HTTP exchange and you can read all of them.
03 — mcp · agent ↔ tools

MCP is USB-C for models: one port, many tools.

The Model Context Protocol standardizes how an app gives a model tools, data, and prompts. A host (Claude Desktop, an IDE) runs one or more clients, each holding a 1:1 session with a server. Messages are JSON-RPC 2.0, over stdio (local) or Streamable HTTP (remote). And a server can never read the whole conversation or peek at other servers, the host keeps them isolated.

a tool call, message by message
CLIENT in the host / agent SERVER exposes tools initialize → ← result · capabilities notifications/initialized → tools/list → ← the tools, each with a JSON Schema tools/call → the model chose this ← result · content (or isError) handshake, then a stream of typed calls one stateful session
message 01 / initialize
The client opens the session.
Step through one round trip: a handshake that negotiates capabilities, then the model reaching for a tool.
on the wire · the tool call
// the model decided to call a tool → the client sends:
{"jsonrpc":"2.0", "id":2, "method":"tools/call",
 "params":{"name":"get_weather", "arguments":{"city":"NYC"}}}

// the server runs it and replies with the same id:
{"jsonrpc":"2.0", "id":2,
 "result":{"content":[{"type":"text", "text":"72°F, clear"}]}}

A server exposes three primitives, split by who decides to use them:

model-controlled

Tools

what
Functions the model can invoke: query a DB, call an API, run code.
how
tools/list, then tools/call. Each carries a JSON Schema.
app-driven

Resources

what
Readable context: files, DB schemas, records. Each has a URI.
how
resources/read; the app decides what to attach.
user-controlled

Prompts

what
Reusable templates, surfaced as slash commands.
how
prompts/get when the user picks one.
the two transports stdio runs the server as a local subprocess, newline-delimited JSON-RPC over the pipe. Streamable HTTP is for remote servers: a single endpoint, POST for messages plus optional SSE streaming. It replaced the older HTTP+SSE transport in 2025. The client also has its own tricks: sampling, roots, and elicitation.
the contrast: An API call is one shot: request, response, done. MCP is a session, a capability handshake, then a stream of typed requests and notifications, with the host free to demand consent at every single tool call.
04 — acp · editor ↔ agent

The first ACP: LSP, but for coding agents.

Two unrelated protocols share the initials ACP. The first is Zed's Agent Client Protocol. It standardizes the seam between a code editor (the client) and a coding agent like Claude Code or Gemini CLI (the agent), the same way the Language Server Protocol let any editor talk to any language. The agent runs as a subprocess; they speak JSON-RPC 2.0 over stdio. The editor owns the UI and the files; the agent does the thinking and asks permission before it acts.

acp on the left, mcp on the right: the agent wears both hats
EDITOR the ACP client owns UI + files CODING AGENT ACP server · MCP client does the thinking MCP SERVER files · git MCP SERVER search · db ACP · stdio MCP
The agent is an ACP server to the editor on its left, and an MCP client to its tools on the right. Same process, two hats.
on the wire · an acp session
editor → agent   initialize            # negotiate versions + auth methods
editor → agent   session/new           # a fresh conversation, its own history
editor → agent   session/prompt        # "fix the failing test"
agent  → editor  session/update        # stream tokens + tool calls back to the UI
agent  → editor  session/request_permission  # may I run this command?
agent  → editor  fs/read_text_file     # the editor owns the files; the agent asks
the contrast: Notice the inversion. In MCP the agent is a client of its tool servers. In ACP-Zed the agent is the server, and the editor is the client. Same word, opposite role. And ACP deliberately reuses MCP's JSON shapes, so the two nest cleanly: an ACP session wrapping a fistful of MCP sessions.
05 — acp · agent ↔ agent

The second ACP lost its own name.

The other ACP is IBM's Agent Communication Protocol (from IBM Research and BeeAI, March 2025). Different problem: agent-to-agent. Different wire: plain REST over HTTP, deliberately not JSON-RPC, so you can drive it with curl. Then the twist. A month later Google shipped A2A, solving the same problem. Rather than split the ecosystem, in August 2025 IBM's ACP wound down and merged into A2A under the Linux Foundation. Of the two ACPs, one is alive and independent (Zed's). The other is now a chapter in A2A's history.

how the agent-to-agent standard consolidated
ACP · IBM Mar 2025 REST · BeeAI A2A · Google Apr 2025 same problem ACP → A2A Aug 2025 ACP winds down A2A lives now Linux Foundation
Two competing agent-to-agent standards collapsed into one. The survivor is A2A.

A2A works like this: each agent publishes an Agent Card at /.well-known/agent-card.json, a machine-readable business card listing its skills and accepted auth schemes. Peers then exchange Tasks, Messages, and Artifacts over JSON-RPC (or gRPC, or REST), with SSE for streaming and webhooks for push.

alive · independent

ACP · Zed

agent client protocol
editor ↔ agent
wire
JSON-RPC over stdio
status
Active, its own thing.
wound down

ACP · IBM

agent communication protocol
agent ↔ agent
wire
REST over HTTP
status
Merged into A2A, Aug 2025.
current standard

A2A · Google

agent2agent
agent ↔ agent
wire
JSON-RPC / gRPC / REST
status
Active, Linux Foundation.
why two ACPs? Pure coincidence. Zed's Agent Client Protocol (editor ↔ agent) and IBM's Agent Communication Protocol (agent ↔ agent) were named independently and share nothing but the three letters. When someone says “ACP,” ask which seam they mean.
the contrast: MCP connects an agent to its tools. A2A connects an agent to its peers. ACP-Zed connects an editor to its agent. Three seams in one system, and a single prompt can cross all three.
06 — the whole conversation

One prompt can cross every seam.

Put it together. You type a request to a coding agent inside your editor. That single intent can ripple through all four protocols, each at its own seam, each ultimately bytes on the wire. Step the trace and watch the words client and server change meaning at every boundary.

one request, traced through four protocols
YOU the user EDITOR ACP client AGENT server to the editor, client to its tools, peer to other agents MCP SERVER tools EXTERNAL API someone else's server PEER AGENT another team's agent DNS · TLS · HTTP for remote hops; local hops use stdio type ACP · session/prompt · stdio MCP · tools/call HTTPS A2A · message/send session/update ↩
hop 00 / you → editor
You type.
Step the trace to follow one prompt across all four protocols, and watch who counts as “client” and “server” at each seam.
the contrast: “Client” and “server” aren't properties of the software. They're roles relative to a seam. The agent is a server to the editor, a client to its tools, and a peer to other agents, all at once, all in one process. Learn to ask “which seam?” and the confusion dissolves.
07 — trust boundaries

Every seam is also a place to get fooled.

Each protocol boundary is a trust boundary: a line where data of one trust level meets authority of another. Get the boundary wrong and a low-privilege input borrows a high-privilege capability. The most consequential boundary in any agent is between its trusted instructions and the untrusted data it reads. Three failure modes recur.

the lethal trifecta · dangerous only when all three meet
AGENT one context ① PRIVATE DATA repos, secrets, mail ② UNTRUSTED INPUT web pages, tool output trust boundary ③ EXFIL CHANNEL a way to send data out private data + untrusted input + a way out = an attacker reads your secrets
A poisoned web page or tool result is just text. The model can't tell it apart from your instructions, so it follows it. Remove any one leg and the trap springs shut.
borrowed authority

Confused deputy

what
A privileged middleman (a gateway, an MCP proxy) tricked into using its authority for a caller who lacks it.
where it bites
OAuth proxies with a shared client ID and a cached consent cookie. CSRF is the web's classic case.
the fix
Per-client consent; exact redirect_uri matching. Never act on ambient authority.
crossed boundary

Prompt injection

what
Untrusted text carries instructions; the model obeys them. Same root cause as SQL injection: trusted and untrusted mixed in one stream.
where it bites
Any tool result or fetched page that reaches the context. The lethal trifecta is the worst case.
the fix
Cut one leg of the trifecta. Treat tool metadata as untrusted unless the server is trusted.
wrong audience

Token passthrough

what
A server forwards a token it received straight to a downstream API, instead of getting its own.
where it bites
Breaks rate-limits, audit trails, and trust boundaries; a stolen token turns the server into a proxy.
the fix
Audience-bind every token. MCP's spec forbids passthrough outright.
in essence
# the lethal trifecta: risky only when all three are true at once
danger = has_private_data and reads_untrusted_input and can_exfiltrate
# there is no 95%-reliable filter. the only sturdy defense is structural:
assert not danger   # remove a capability until one leg is gone (least privilege)
the contrast: The protocols don't make you safe; they give you the seams where safety can be enforced. MCP can demand consent at each tool call, scope a token to one audience, and isolate one server from another, but the spec says plainly it “cannot enforce these security principles at the protocol level.” That part is the host's job.
08 — who are you

Four ways to prove you're allowed.

Every request so far carried proof of identity in the same place: the Authorization header, or, for mTLS, the TLS handshake itself. Here are the four schemes you'll meet, in rough order of strength and ceremony. Pick one and see how it travels.

authentication · pick a scheme
api key
A static secret in a header.
in essence · oauth 2.1 authorization code + pkce
verifier  = random()                 # the client's one-time secret
challenge = sha256(verifier)         # sent up front, so the code can't be stolen mid-flight
code  = authorize(challenge, scope, redirect)   # the user consents in a browser
token = exchange(code, verifier)     # proves it's the same client → access token (+ refresh)
call(api, auth=f"Bearer {token}")   # token is audience-scoped to this one api

At a glance, weakest secret to strongest identity:

a shared secret

API key

proves
which app is calling
lifetime
long; rotate to revoke
risk
copyable; leak = full access
a signed claim

Bearer · JWT

proves
signed claims (aud, exp)
lifetime
short; expiry is the leash
risk
still bearer; needs TLS
delegated consent

OAuth 2.1

proves
the user said yes, to this scope
lifetime
short token, refreshable
risk
more parts; auth server is the prize
a held key

mTLS

proves
possession of a private key
lifetime
the certificate's validity
risk
heavy cert lifecycle
the contrast: The arc runs from “a secret anyone could copy” to “a key only you hold.” API keys are bearer secrets; OAuth narrows them with scope and audience; mTLS binds them to a certificate so a stolen token is useless. MCP chose OAuth 2.1 for its HTTP transport for exactly this reason, and forbids passing the resulting token anywhere it wasn't issued for.
09 — closing

It's contracts all the way down.

A protocol is just an agreement about the shape of a message and the meaning of the words around it. API, MCP, the two ACPs, A2A: each draws a contract at a different seam, between parties that trade the names “client” and “server” depending on where you stand. Underneath, they all dissolve into the same DNS lookup, the same TLS handshake, the same HTTP request you met at the start.

So the interesting questions were never on the wire. They're at the seams. Who is allowed to ask. Which token proves it. What happens when an untrusted message crosses a trusted boundary. Get those right and the bytes take care of themselves.

Facts here come from the RFCs, the official protocol specs, and the foundations now stewarding them. Where a protocol moves fast (MCP, A2A), I cite the spec version I read; MCP's current revision is 2025-11-25, and IBM's ACP folded into A2A in August 2025.