MAQPNADocs

MCP servers, tool pinning and A2A#

Agents act through two protocols, and MAQPNA governs both at the gateway:

  • the Model Context Protocol (MCP), which agents use to call tools offered by MCP servers;
  • agent-to-agent (A2A), which agents use to send tasks to other agents.

MCP servers#

An MCP server is registered with the kind MCPServer (short name mcps). Its name is a DNS label because it becomes both the gateway path and the scope: an agent calls POST {gateway}/mcp/<name> with a token holding tools:<name>. The gateway speaks MCP Streamable HTTP and supports protocol revisions 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28 on the same endpoint.

Field Meaning
url, protocolVersions, timeoutSeconds, caBundleRef Where and how to reach the server
trust (trusted / untrusted, default untrusted) Untrusted servers taint the sessions that call them
dataClass (public, internal, confidential, restricted) Confidential and restricted servers add a data-class:<class> taint
jurisdiction Checked against the sovereignty policy
auth The gateway's own credential: bearerSecret, headerSecret, oauthClientCredentials, tokenExchange or mtls; or perUser to inject the user's own token from the vault
shared, allowedNamespaces Offer the server to other namespaces
toolPins, toolLabels, dlpProfile Pinning, taint labels and DLP for this server's traffic

The operator renders every MCPServer into the ConfigMap maqpna-upstreams and copies the referenced Secrets into a gateway-only Secret. Credentials never reach the sandbox, a ConfigMap, the ledger or the admin API. A namespace can only reach another namespace's server when the owner shares it, and a tenant's server is never offered to another tenant unless sharing allows it.

The gateway resolves /mcp/<name> for the verified caller in this order: the agent's own binding (serverRef), the caller namespace's server, exactly one server shared with the namespace (more than one is ambiguous_server), static upstreams, then the deprecated upstreamURL. Anything else is unknown_server.

Tool pinning#

A tool definition can change after you reviewed it: a server update, or a malicious "rug pull" that rewrites a tool's description to steer the model. Tool pinning records a hash of each tool's definition. A pin is that hash:

sha256:<hex SHA-256 of the canonical JSON of name, title, description, inputSchema, outputSchema, annotations>

Drift is a tool whose definition changed after it was pinned.

mode Effect
off No pinning
learn (default) Record first sightings and changes in the ledger (decision: observe); never block
enforce A changed, unpinned or never-seen tool is handled per onChange
onChange (enforce) Effect
hide (default) Dropped from tools/list; a call is denied (tool_definition_changed, tool_unpinned, tool_definition_unverified)
approve Visible; an otherwise allowed call needs approval
alert Allowed; the record carries the pin status

Pins come from ToolPolicy.spec.toolPins and MCPServer.spec.toolPins, merged strictest wins. The GitOps workflow: run in learn, review, export the observed hashes (maqpna mcp pin), commit them with mode: enforce. The gateway reports what it observed back onto the server's status (observedTools, driftedTools, condition Pinned: Verified, Drifted, NotPinned).

A2A#

A hosted agent exposes itself with Agent.spec.a2a.expose and an in-cluster endpoint; a remote agent is registered as an A2A peer (kind A2APeer, short name a2ap). Both are reachable through the gateway at /a2a/<namespace>/<name> in the JSON-RPC and HTTP+JSON bindings of A2A v1.0. Callers need the scope agents:<name> (or agents:<ns>/<name> across namespaces); tools:* never grants it.

The gateway governs an A2A call like a tool call (policy server a2a/<agent>, tool SendMessage or skill:<id>), plus:

  1. the target's allowedCallers (empty means the target's own namespace);
  2. delegation depth (default 5) and loops;
  3. the requested skill must be declared by the hosted agent, or listed in the verified card of the peer;
  4. a remote peer's Agent Card must verify against pinned keys (requireSignedCard, default on), or calls fail closed (peer_card_unverified);
  5. answers from untrusted targets (peers default to untrusted) taint the caller's session;
  6. both hops are audited: the caller's record (ext.a2aHop=outbound) and, for hosted callees, the callee's record (inbound), correlated by ext.a2aCall.
flowchart LR
    A["Agent planner<br/>(sandbox)"] -- "/mcp/github<br/>scope tools:github" --> GW["Gateway"]
    A -- "/a2a/team-a/coder<br/>scope agents:coder" --> GW
    GW -- "credential injected,<br/>pins checked" --> M["MCPServer github"]
    GW -- "Txn-Token,<br/>identity headers" --> B["Agent coder<br/>(hosted, spec.a2a.expose)"]
    GW -- "signed card verified,<br/>no identity headers" --> P["A2APeer partner-agent<br/>(remote, untrusted)"]

What you see#

maqpna mcp list shows the registry, maqpna mcp tools a server's observed tools with their pin status, and maqpna mcp pin exports a toolPins: fragment. maqpna a2a routes prints TARGET KIND URL TRUST JURISDICTION CALLERS CARD ERROR. See maqpna mcp list, maqpna mcp tools, maqpna mcp pin and maqpna a2a routes.