Product · Agents and tools

Your MCP servers and agents answer at a door you govern.

The tool call is a request too. An agent is a client that calls a model, calls tools and calls other agents. Each of those is a request on the same path, and pistra fronts each at the same door: a virtual key or an identity-provider token in, the configured credential out, a rule over the call, a budget against it, guardrails over what is said, and a record of what was decided.

the doors
/v1, /mcp/<name>, /a2a/<name>
what a rule reads
the caller at every hop, the call at the hop it names
the credential
the gateway's own, or the caller's exchanged for the upstream
the protocols
A2A 1.0 over JSON-RPC, MCP registry v0.1

pistra is where your agents and tool servers live, from a client's point of view. An MCP server declared once answers at /mcp/<name>, a peer agent at /a2a/<name>, and the client never holds the upstream's address or its credential. A private registry lists the servers a team is allowed, so a client discovers an approved server instead of being configured with one. An agent card is served at the well-known path and signed with ES256, with the interfaces nothing governs stripped out of it.

Governing the call is the other half. A tool call and a message to a peer agent are read by the same rules, drawn against the same budgets and written to the same record as a model request, so there is no second policy to keep in step with the first.

governance platformoff the path: before the call and after it
response cachea hit never reaches the provider
clientkey · model · body · tool call
pistra holds the connection/v1 · /mcp/<name> · /a2a/<name>
an MCP server · a peer agentbehind the same door as the models
recordthe decision, signed; never the prompt
detectorcalled out to with a string
Three doors, one spine. The model route, the MCP door and the A2A door ride the same passthrough: inspect, decide, swap the credential, forward the bytes untouched.

The same door

One door

An MCP server is declared once and answers at /mcp/<name>. An A2A agent is declared once and answers at /a2a/<name>. A client sends its requests there with the credential this deployment accepts. The gateway strips that credential, sends the one configured for the upstream, and forwards the request byte for byte. A cost per call draws on the caller's budget the way a model request does. A suspension names the hop, mcp:<name> or a2a:<name>, and is in force on every node within the raft commit.

A refusal at either door is a JSON-RPC error whose data.code is the bounded reason: a missing credential, a rule, a budget, a suspension. The same reason is written to the record.

What a rule reads at each hop

Access rules are evaluated deployment-wide first and then in the caller's profile. A rule that names no hop runs at every one and may read only who is calling. A rule that names a hop reads that hop's own variables.

hopatthe call's variables
model requestllmmodel, provider, route
MCP tool callmcpserver, method, name, args
A2A messagea2apeer, method, role, task_id, context_id, parts

At the MCP hop args is the tool's arguments, so a rule can refuse a write to an issue tracker from a bare workload, or a lookup whose argument is a customer identifier, before the call leaves. At the A2A hop parts says what each part is, its kind, media type, URL and filename, and never what it says. Guardrails read the content: the tool_args and tool_result segments of a tool call, and each text part, each file part's URL and filename and each data part leaf by leaf of a message. A rule at two hops is compiled against each and may read only what both declare. Front a peer agent, Policy reference.

A server or agent inside your boundary that needs real values takes a restore list naming the entity types it may receive in the clear. It applies only at a hop the gateway dials, because that is the only destination anyone can promise anything about.

Who the agent is

A virtual key is the credential of whatever holds it. A workload that already carries a token its platform rotates for it, or a person's application that signed in against your directory, presents that token instead. The gateway verifies it against the issuer, requires the data plane in its audience, and puts the caller under a profile. Every rule environment carries three variables about the caller, always present, so a condition never guards for absence.

variablefieldsa virtual keyan agent tokena user token
keyname, metadata, profilethe keythe binding's profilethe binding's profile
agentname, issuer, subjectthe key's namethe workloadthe application
username, issuer, subject, emailemptyemptythe person

The record carries the issuer and the subject beside the readable name. A budget under a profile is drawn per caller, by issuer and subject, so a bucket survives a rename. A token cannot be revoked, because it is verified rather than held, so for the window until it expires the gateway refuses the caller instead of the credential: a suspension on the principal, lease-bounded and replicated. Authenticate agents with your identity provider.

The upstream sees the person

At the MCP hop the gateway sends its own credential, so an upstream sees the gateway every time, whoever asked. That is right for a server that should see one service account and wrong for a Jira, a Graph or an internal API with its own idea of what each person may read. A token_exchange credential presents the caller's token back to the issuer that minted it and asks for one minted for the upstream, with the gateway named as the client acting for them. The gateway mints nothing and holds nothing. The provider applies its own policy for that person, that application and that target, and the upstream sees the person.

A client finds the door, not the server

An A2A client discovers an agent by its card. Behind the gateway the address and the accepted credentials are the gateway's to say, so it fetches the agent's own card, rewrites the interface to the one door it fronts, replaces the security schemes with what this deployment accepts, sets push notifications to false, signs the result with ES256 and serves it at the agent's well-known path. A gRPC or HTTP+JSON interface the agent also offers is dropped rather than advertised at an address nothing governs.

An MCP host that takes a registry URL takes the gateway's. The mcp: configuration already is the approved catalog, and the gateway serves it over the registry's frozen v0.1 API with every entry's connect URL pointing at its own door. The upstream URL never appears. A server is listed when its configuration opts in, and a team publishes its own under a namespace that admin policy grants, with the credential named rather than pasted: a raw secret in a document built to be listed is refused. Serve a private MCP registry.

Threat model

The asset is what a tool does with the argument it is handed, and behind it the identity the tool believes is asking. The text is written by the model, prompted by whatever it read on the way, which is why a page an agent fetched can end as a tool call the agent's operator never intended. It arrives at the MCP hop as a tool name and its arguments, and at the A2A hop as a message with parts. The control is a rule at that hop over the caller and the call, a credential swap so the upstream never sees the client's key, guardrails over the arguments and the result, and a registry whose every address is the gateway's. The limit is a tool the gateway does not dial. An agent that reaches a server directly is ungoverned, and nothing here discovers it.

The entry this section expands, and the ten beside it: threats and limits.

What this does not do

  • It does not discover agents it is not in front of. An agent that reaches a server or a model directly is ungoverned, and nothing here finds it. The coverage audit says what can be detected after the fact, and for which providers.
  • Push notifications are refused. An agent that supports them posts task updates to a webhook the client named, directly, around the gateway. The door refuses the four configuration methods rather than advertise a path it does not govern.
  • One protocol version, one binding. A2A 1.0 over JSON-RPC. A client that says nothing is a 0.3 client by the specification's own rule and is refused with the error the specification reserves for it. A method outside 1.0 is refused rather than forwarded.
  • Inline bytes are not read. A file part carried as base64 is not text, so guardrails read its URL and filename and not its content.
  • No virtual key binds to a person yet. The gateway verifies tokens and mints none. Token exchange narrows an identity to one upstream at a time, at the provider's hand and only for MCP servers. There is no way yet to carry a chain of agents that each acted for the one before across more than one hop.
  • The registry is not a package index. One live entry per name, latest wins. A package a host installs and runs itself has no endpoint for the gateway to put policy in front of, and is refused.

The argument

  • The tool result is the channel. An agent reads mail, documents, web pages and tool output. Prompt injection arrives in any of them from an attacker who is not the user. Direct injection comes from the user turn, indirect injection from a tool result. pistra reads both.
  • Who is on the request path. Six kinds of product sit on the path between your application and a model. This series takes them one at a time and asks the same question of each. While it holds the connection, what can it do, and whose record is it?