Skip to main content

Agent protocols as plugins

Besides the runtime protocols a client uses to talk to an agent (AG-UI, Vercel AI, ACP, A2A), an agent speaks agent protocols with the world: how it calls tools and other agents (MCP, A2A), who it is (Web Bot Auth), what a person consented to (PAP, PACT), how it buys (UCP, ACP — the Agentic Commerce Protocol, named agentic_commerce in code so it never collides with the Agent Client Protocol). The map is poppy.md/compare.

The map​

poppy.md/compare reads an agent's visit to a business as four questions, top to bottom: which agent is this (identity), has the person let it act and how far (consent), how it talks and calls tools (transport), how it buys (commerce).

LayerProtocolPublished byIn agent-runtimesLibraries reused
TransportMCP, Model Context ProtocolAnthropicclient, Python (MCP and A2A)mcp, fastmcp, pydantic-ai
TransportA2A, Agent2AgentGoogle, now the Linux Foundationserver (Python) and client (both) (MCP and A2A)fasta2a, @a2a-js/sdk
ConsentPAP, Personal Agent ProtocolSierra, Metaboth roles, through the PAP SDK (Personal Agent Protocol)personal-agent-protocol, @datalayer/personal-agent-protocol
ConsentPACT, Personal Agent Consent & TrustDecagon, Instinctthe personal agent, both languages; a local Provider (PACT, PAP and PACT)PyJWT, cryptography, jose
IdentityWeb Bot AuthIETF draft (Cloudflare and others)signing, the key directory, verifying (Web Bot Auth)http-sfv, cryptography; Cloudflare's web-bot-auth
IdentityTAP, Trusted Agent ProtocolVisaa draft profile over Web Bot Auth, from Visa's public documentation, not confirmed by Visa (TAP)Cloudflare's http-message-sig
CommerceUCP, Universal Commerce ProtocolGoogle, with Shopify and retailersthe shopper, a test merchant (UCP)ucp-sdk, @ucp-js/sdk
CommerceACP, Agentic Commerce ProtocolOpenAI, Stripethe buyer, a test seller (Agentic Commerce)clients generated from its JSON Schema

Every protocol is a Reactor plugin​

In Reactor's terms:

  • every protocol is a Plugin, in Python and in TypeScript, with a manifest and the Contribution Points it declares, named <protocol>.<point>: its stores, its signer, its egress;
  • its defaults are Contributions at order 100; a host replaces one by contributing at a lower order (on Datalayer: IAM custody, the MCP gateway's egress, Vault transit signing);
  • every extension a protocol defines is a Contribution Point of its plugin, <protocol>.extension.<name>; an implementation of the extension is a Contribution to it, and the protocol negotiates what it offers from what is contributed there;
  • the agent-protocols Reactor Extension groups the plugins for delivery (Python entry point datalayer.reactor.extensions), and decides nothing;
  • each plugin says what it is on protocols.description.
from agent_runtimes.protocols import build_protocols_reactor, list_protocols

platform = build_protocols_reactor(plugins=[(host_manifest, host_plugin)])
for protocol in list_protocols(platform):
print(protocol.id, protocol.version, protocol.roles, protocol.points)
import { buildProtocolsReactor } from '@datalayer/agent-runtimes/lib/protocols/agentProtocols';
import { listProtocols } from '@datalayer/agent-runtimes/lib/protocols/core';

const reactor = buildProtocolsReactor([hostPlugin]);
listProtocols(reactor);

What is implemented​

ProtocolPluginVersion implementedPoints
MCPagent-runtimes.protocols.mcpPython mcp 2.1: 2026-07-28; TypeScript SDK 1.32: 2025-11-25none yet: its optional capabilities and extensions become mcp.extension.* when the client and server read them from contributions — what is implemented
A2Aagent-runtimes.protocols.a2a1.0a2a.extension.orchestration, a2a.extension.face (Datalayer's), a2a.extension.other (any, by URI) — what is implemented
Web Bot Authagent-runtimes.protocols.web-bot-authdraft-ietf-webbotauth-httpsig-protocol-00web_bot_auth.signer, web_bot_auth.nonceStore, web_bot_auth.extension.{directory,jwks_uri,cimd} — its page
PAPagent-runtimes.protocols.pap; its own points are the PAP SDK's plugins' (pap.*, pap.company.*), on the PAP Reactor each personal agent or company builds0.2pap.company.operationTickets (Python: where a perform turn redeems its ticket; Datalayer's company service by default, a local PapCompanyCapability's own) — see Personal Agent Protocol
UCPagent-runtimes.protocols.ucp2026-08-25ucp.approver, ucp.paymentInstruments, ucp.proposalStore, ucp.extension.{fulfillment,discount,buyer_consent} — its page
Agentic Commerce (ACP)agent-runtimes.protocols.agentic-commerce2026-04-17agentic_commerce.approver, .paymentData, .credentials, .proposalStore, agentic_commerce.extension.{discount,other} — its page
PACTagent-runtimes.protocols.pact1.0pact.signer, pact.delegationStore, pact.receiptStore, pact.extension.delegated — its page

Each protocol's points are listed in its reactor-points.json beside its Python package; a table-driven test in each language holds the plugin to it: exported under its id, declared by the plugin, its default at order 100 (or none), replaced by a lower order, gone with its plugin, two at the same winning order failing closed.

Conformance​

Each protocol is checked in both languages against the same fixtures, so the two packages cannot drift apart:

ProtocolShared fixturePythonTypeScriptSecrets never seen by a model
Web Bot Authweb_bot_auth/fixtures/vectors.json: the draft's Ed25519 vectors E.2.1, E.2.2, E.2.3, reproduced byte for bytetest_protocols_web_bot_auth.pywebBotAuth.unit.test.tsthe private key: never in a header, the directory or a repr
TAP (draft)web_bot_auth/fixtures/tap.json: a browsing and a paying request under the RFC 9421 Ed25519 test key, computed here (Visa publishes none)test_protocols_web_bot_auth_tap.pytap.unit.test.ts (Cloudflare's http-message-sig)—
UCP 2026-08-25ucp/fixtures/checkout.json: a checkout and its revision digesttest_protocols_ucp.py (against the test merchant)ucp.unit.test.tsthe payment token
Agentic Commerce 2026-04-17agentic_commerce/fixtures/session.json: a session, the discovery document, the revision digesttest_protocols_agentic_commerce.py (against the test seller)agenticCommerce.unit.test.tsthe payment token and the seller bearer
PACT 1.0pact/fixtures/card.json: a Brand's card and how it is readtest_protocols_pact.py (against the local Provider)pact.unit.test.tsthe device code and the delegation token
PAP and PACT in one task—test_protocols_pap_and_pact.py (the PAP SDK's demo world and the local Provider)—both user codes and the delegation token, and any compact JWT
A2A 1.0 extensionsa2a/fixtures/extensions.json: a card's extensions, the one found, those required and not supported, and cards both refusetest_protocols_a2a_extensions.py, test_protocols_a2a_signed_requests.pya2aExtensions.unit.test.ts—
Every protocol's journey—test_protocols_secrets.py: every logger at DEBUG and every URL requested—the payment tokens, the seller's bearer, the device code and the delegation token, the private key: in no log line and no URL (MCP's and A2A's: test_credentials_never_shown.py, test_delegated_credential.py)
Every pluginits reactor-points.json; its entry in the agentspecs catalogue and the version it saystest_protocols_reactor_points.pyreactorPoints.unit.test.ts—
MCPmcp/fixtures/session.json: one server's answers on the wire (tools, calls, a refusal, a resource, a prompt) and what a client reads from each, the specification's defaults for missing tool annotationstest_protocols_mcp.py (the mcp SDK's types and its Client at the fixture's server), test_protocols_mcp_two_languages.py (the TypeScript McpClient at the Python server over Streamable HTTP, 2025-11-25 agreed)mcpClient.unit.test.ts (@modelcontextprotocol/sdk's schemas and a server of the SDK in memory), mcpServer.integration.test.tstest_credentials_never_shown.py

Examples​

Every example runs locally, in one process, with no network and no model, against a local test counterpart; none needs an account.

ExampleCommandShowsPage
EX-01, EX-02python examples/protocols/web_bot_auth/signed_agent.pysigned requests, the key directory, replay refused, rotationWeb Bot Auth
EX-03python examples/protocols/a2a/card_extensions.pycard extensions published and negotiatedbelow
EX-04python examples/protocols/ucp/shopping.py --yesa UCP purchase approved onceUCP
EX-05python examples/protocols/agentic_commerce/checkout.py --yesan ACP checkout approved onceAgentic Commerce
EX-06python examples/protocols/pact/personal_agent.pya personal agent at a Brand, both profilesPACT
EX-07python examples/protocols/pap_and_pact/app.pya PAP company and a PACT Brand, one recordPAP and PACT
EX-08loop apps protocols agentspecs/agentspecs/apps/accounting.yamlwhat the runtime speaks, and what an application usesbelow
EX-09the fixtures of Conformance abovethe same bytes in both languagesabove
EX-10EXAMPLE=ProtocolSignedRequestsExample npm run dev, EXAMPLE=ProtocolUcpCheckoutExample npm run devthe gallery's Agent protocols: a request signed in the browser, a UCP checkout approved on a cardWeb Bot Auth, UCP

loop apps protocols [APP] (and GET /api/v1/protocols, listProtocols() in TypeScript) prints each plugin's description — name, id, version, layer, roles, state — and, given an Appspec or an app.py, marks what the application uses with a sentence each: the MCP servers it reaches, the PAP companies it works with or that it is a company, the PACT Brands it is a personal agent at, the A2A card extensions it publishes:

· Agent2Agent (a2a) 1.0 — transport, client, server, implemented
· Agentic Commerce Protocol (agentic_commerce) 2026-04-17 — commerce, buyer, partial
● Model Context Protocol (mcp) 2026-07-28 — transport, client, server, implemented
it reaches 1 connection(s): odoo-accounting:0.0.1
· Personal Agent Consent & Trust (pact) 1.0 — consent, personal-agent, provider, partial
· Universal Commerce Protocol (ucp) 2026-08-25 — commerce, shopper, partial
· Web Bot Auth (…) (web_bot_auth) draft-ietf-webbotauth-httpsig-protocol-00 — identity, signer, verifier, directory, implemented

Each description also names its entry in the agentspecs protocols catalogue (agentspecs/protocols; catalogue: web-bot-auth, acp…), and implements the version the catalogue says (both held by test_protocols_reactor_points.py and reactorPoints.unit.test.ts). Studio's Protocols page (/agentspecs/protocols) reads GET /api/v1/protocols of a runtime of the person's against the catalogue: which protocols that runtime speaks, in which version and roles, and which the catalogue lists that it does not describe.

A2A card extensions​

What an agent card publishes under capabilities.extensions is what the A2A plugin's extension points hold, then what the card itself declares (A2AAgentCard.extensions); a URI published twice is refused. A client negotiates with an agent from the same contributions: it activates the extensions of the card it implements, and refuses to call an agent that requires one it does not.

from agent_runtimes.protocols.a2a import negotiate_extensions

activated = negotiate_extensions(card, platform) # ValueError if a required one is missing

examples/protocols/a2a/card_extensions.py shows a card's extensions published from the plugin's points, a third party's extension added by its own plugin, and a client refusing an agent that requires one it lacks.

An application declares its own extensions in its Appspec, under a2a; the runtime publishes them on the application's card after its own (orchestration, the face), in the order written:

a2a:
extensions:
- uri: https://shop.example/extensions/gift-wrap/v1
description: Wraps an order as a gift.
required: false
params:
papers: [red, gold]

An extension is named by an https:// URL or a urn:; Datalayer's own (https://datalayer.ai/extensions/…) are the runtime's and refused here. The params are published as they are, so a name that looks like a credential (token, api_key, password, …) is refused: a card is public. See the Appspec reference (a2a).