Kcalbin LLC

ACP vs MCP: What Each Protocol Actually Does

They are not competitors, and they do not answer the same question. One connects a model to your tool. The other moves money between two agents.

14 August 2026 · Kcalbin LLC · Written from running both in production on the same box, on the day of writing.

Most of what is written about these two protocols compares them as if you have to pick one. You do not. They sit at different layers and solve different problems, and the fastest way to see it is to look at what each one forced us to build — and, more tellingly, what each one left entirely to us.

Kcalbin runs both today: two Model Context Protocol servers and five Agent Commerce Protocol seller processes, against the same underlying data. That is the whole basis for this post. Every mechanism below is one we implemented, and every failure below is one of ours.

First, which ACP

The acronym is contested. Throughout this post ACP means the Agent Commerce Protocol published by Virtuals Protocol — the one our seller agents actually speak. There are other proposals in the agent-payments space with their own acronyms, and they are not what this post is about. If you are comparing those, the distinction below still applies at the layer level, but the specifics will not.

MCP is the Model Context Protocol, published by Anthropic. Our servers are built on its official SDK.

The one-line version

MCP is a capability protocol. ACP is a commerce protocol.

MCP is how a model reaches your tool and learns what it can do. It is exhaustive about describing capability and says essentially nothing about who is calling or whether they paid.

ACP is how two agents complete a transaction with money in escrow. It is specific about the money and comparatively uninterested in how you computed the answer.

If that sounds like they compose rather than compete, that is because they do.

What MCP actually does

An MCP server declares tools. A tool is a name, a description written for a model rather than a human, a typed parameter schema, and a handler. Our commercial server registers its Chandler tool with a description that spells out the free tier and the paid tier in prose, because the model reads that description to decide whether to call it and what it will get back. Writing tool descriptions is closer to writing product copy than to writing API docs, and that surprises people.

Transport is HTTP. We use the SDK's streamable HTTP transport, mounted on a normal Express app alongside an ordinary health endpoint.

Now the part that matters more than any of it — the list of things MCP does not give you, all of which we had to build ourselves:

  • Identity. We invented it. A bearer token in the Authorization header, falling back to a query-string token, validated against our own database.
  • Authorization tiers. Ours. The server decides per-connection which tools even exist for that caller — a free caller and a paying caller are handed different tool sets by the same process.
  • Metering and rate limits. Ours. We count requests per customer per day against a per-customer limit and increment it ourselves on each call.
  • Payment. Entirely outside the protocol. MCP never learns that money exists. Billing is a subscription handled elsewhere.

None of that is a criticism. It is the correct scope for a protocol whose job is to let a model use a tool. But if you are evaluating MCP as a way to sell something, understand that the protocol contributes nothing to the selling. You are building a normal SaaS product that happens to speak MCP at the edge.

The MCP gotcha that cost us real time

The SDK's streamable HTTP transport rejects with 406 Not Acceptable any request whose Accept header does not list both application/json and text/event-stream. Clients that send neither, or narrow it to one, are simply unreachable — and the failure looks like a broken server rather than a picky handshake.

The fix is to normalize the header. The trap is where: the transport rebuilds the request headers from Node's req.rawHeaders — the flat array — not from req.headers. Setting req.headers.accept looks correct, changes nothing, and leaves you debugging the wrong layer. You have to mutate the raw array in place. We hit this on the vault server first and then again on the commercial one, because they share the SDK.

What ACP actually does

ACP inverts the emphasis. The money is the protocol.

A seller registers an agent and publishes an offering — a named product with a price. A buyer agent opens a job against it. From there the provider has two distinct turns, and they are not interchangeable:

  • job.createdset the budget. You price the job so the buyer can fund it. You have not delivered anything yet.
  • job.fundedsubmit the deliverable. The money is now in escrow. This is the only point at which you hand over the goods.
  • job.completed → notification only. Nothing for you to do.

The price you set at the first turn must match the registered offering. If it does not, the buyer funds an amount the offering does not agree with, and you have a settlement problem rather than a sale.

The ACP bug that settled ten jobs for zero

Our first implementation did not branch on the event type. The SDK tells you whether it is your turn to respond, and we treated "it is your turn" as "deliver now" — so we called submit on every approved entry, including job.created and the notification-only job.completed.

Two things followed, and they are the reason we now write the lifecycle out in a comment above the handler:

  • Ten jobs settled for 0. A budget was never set, so the buyer's funding step never fired. We delivered the data and collected nothing.
  • Nine of those ten were delivered twice. Same root cause, from the other side: multiple events each triggering a delivery.

The fix is to branch on event type and never submit before funding, plus an idempotency set per job. One detail there is worth stealing: claim the job id in the set before the await, not after. Claiming afterwards leaves a window in which a second event arrives while the first is still in flight and both proceed. If the call then throws, release the claim so a retry can re-price.

To be exact about what those ten jobs were: that seller runs against the Virtuals sandbox. No real money was lost, because there was no real money. We are counting it as a bug caught in testing, which is precisely where you want to find a settlement bug.

The ACP gotcha that was worse, because it was silent

The buyer's parameters do not necessarily arrive on the event you expect. Our Chandler seller takes a US state filter. That filter does not arrive on job.created — it comes as a separate room message with a requirement content type.

The original code read it off the job event, where it is always undefined. Nothing errored. Nothing appeared in a log. Every job silently fell back to national data and returned a perfectly well-formed, confidently wrong answer to a buyer who asked about one state.

That is the failure mode worth internalising from the whole exercise. A settlement bug is loud and shows up in the numbers. A parameter that silently defaults produces a plausible deliverable every single time, and the only way you find it is by checking that the answer changes when the question does.

Side by side

QuestionMCPACP
What layer is it?Capability — model reaches toolCommerce — agent pays agent
Who is the client?A model, usually driven by a humanAnother agent, buying autonomously
Does it carry identity?No — you build itYes — agents are registered identities
Does it carry payment?No — entirely out of bandYes — escrow is the core of it
Does it carry a catalogue?Tools, described for a modelOfferings, priced for a buyer
Who does metering?You doPer-job pricing is built in
TransportHTTP (streamable), stdio for localSDK over the protocol's own network
Where the hard part isAuth, tiering, rate limits, billingGetting the job lifecycle exactly right
Typical failureLoud — a 406, a rejected handshakeSilent — a wrong default, a job that settles for nothing

Which one do you actually need

The decision is not really about the protocols. It is about who your buyer is.

Build MCP if a human, working through an assistant, is the customer. They have a subscription or an API key, they want your capability inside the tool they already use, and the billing relationship is an ordinary one you already know how to run. Everything hard is the boring, well-understood SaaS part.

Build ACP if another agent is the customer and you want the transaction to complete without a human approving each one. This is the bet on autonomous purchasing. The protocol genuinely does the hard part — escrow, settlement, a buyer who can discover you — and in exchange you must get the lifecycle exactly right, because the failures are quiet.

Build both if the same data has both kinds of buyer, which is our situation. The data functions underneath are shared; only the edge differs. That is the argument for keeping your actual product logic strictly separate from whichever protocol is exposing it — we can put the same county data behind an MCP tool and an ACP offering because neither protocol reaches into the part that computes the answer.

What neither one gives you

Demand.

Both protocols solve plumbing. Neither creates a buyer. It is worth being blunt about this because a great deal of writing in this space quietly implies that shipping the integration is the same as having a market.

As of today, four of our twenty agents are fully live — registered, with a visible offering, and a seller process actually running — and Kcalbin has not completed a sale through the agent marketplace. The ten ACP jobs described above were sandbox jobs. We are not going to round any of that up.

The honest reason to build now is positioning: being present and working in a market before it is contested. That is a real reason. It is not a revenue forecast, and anyone giving you a confident number for agent-commerce demand in 2026 is guessing.

If you want this done rather than learned

All of the above is reproducible. Both protocols are open, both SDKs are public, and a capable engineer will get there. What you are buying, if you buy anything, is not the knowledge — it is not paying for the ten jobs that settled for nothing, the header you mutated in the wrong place, and the parameter that silently defaulted for weeks while returning confident answers.

We have already paid those. We build, register, verify, sell and host agents for other people — flat build pricing plus monthly hosting, no revenue share, and you own the agent, the Stripe account and the registration. The catalogue of what we run for ourselves is public and marks precisely which agents are live, which are registered but not yet sellable, and why.

If you would rather see the full build-to-first-sale path instead of the protocol comparison, that is the previous post.

Agent build & operate — pricing See the agents we run

Mechanisms and failures described here are from Kcalbin's own MCP servers and ACP seller agents as they ran on 14 August 2026, and reflect the SDK versions we are on. The Agent Commerce Protocol is published by Virtuals Protocol and the Model Context Protocol by Anthropic; Kcalbin is not affiliated with either.