You run agentgateway yourself. BotShield does not provide or host a gateway. BotShield supplies the signed proofs, the public keys to check them with, and a generated starting configuration. The configurations on this page were validated against agentgateway v1.4.1.
How it works
agentgateway has two policies that do the work:jwtAuthverifies the bearer token on each MCP request: signature (against a JWKS), issuer, audience, and expiry.mcpAuthorizationis a list of CELallowrules. A rule can read the MCP request (mcp.tool.name,mcp.tool.target, …) and the claims of the verified token (jwt.<claim>).
opaque_id exists only on the link JWT. verdict exists only on the Proof of Resolution. The opaque id is pairwise: it identifies one human to one agent and means nothing anywhere else. Neither token carries a name, an email, or any other personal data.
The gateway cannot pause a request while a person decides. The pattern is deny, get the confirmation, retry with the proof.
Generate a policy pack in the Console
The BotShield Console builds a working configuration for one agent and one MCP server.1
Register an agent
You need an active agent under Agents Ask → Trusted Agents, and its agent key (
bs_agent_…, shown once when you register the agent or rotate its key). See Register an agent.2
Open the policy pack dialog
In the BotShield Console, go to Settings → Integrations, find the agentgateway card, and select Get policy pack.
3
Fill in the form
4
Download
Select Download policy pack. The browser saves
botshield-agentgateway-<target>-<environment>.zip. The pack is built in your browser and holds no secrets.What is in the zip
The pack targets a different BotShield API host depending on the agent’s environment:
The issuer is
https://api.botshield.ai in both cases.
Run it
1
Install agentgateway
2
Start the link server
README.md. The link server needs Node 20.6 or later, where --env-file loads .env; on an older Node it prints the version it found and exits. Variables that are already set in the process environment take precedence, so a process manager or container can supply them instead of a file.LINK_ORIGIN and BOTSHIELD_AGENT_KEY are required. If either is missing, the server does not start: it prints the list of missing variables with a one-line description of each, repeats the run command, and exits with a non-zero status. The other settings have defaults (BOTSHIELD_API_URL, LINK_AUDIENCE, BRAND_NAME, MCP_RESOURCE_PATH, PORT), and .env.example fills them in for your agent’s environment.The first run writes link-server-key.pem (keep it private) and link-server-jwks.json, which agentgateway.yaml reads as its second trusted issuer. The server listens on port 9099.3
Start the gateway
3000. Run it from the folder that holds link-server-jwks.json.4
Route your public origin
Use any reverse proxy or tunnel in front of both processes:
5
Connect a client
Add
https://<your public origin>/mcp as a connector in your MCP client. The OAuth step shows the link page. After the person links, the client holds a link JWT and sends it as the bearer on every call.POST /agentlink/inquire with your agent key and the opaque_id from the token’s sub, and complete the action after GET /agentlink/check-status returns a confirmed result. See Propose an action.
How the link server handles OAuth
link-server.js is an OAuth 2.0 authorization-code server whose only sign-in method is the BotShield link. It enforces the parts of the flow that protect the link JWT:
Standard MCP clients send PKCE with
S256 by default, so there is nothing to configure on the client.
Write policies yourself
You do not need the policy pack. Any agentgateway configuration can trust BotShield with onejwtAuth provider and a few rules.
Worked example: Meridian Airlines
Meridian Airlines fronts one MCP server with three tools:search_flightsis open to any caller.hold_seatrequires a linked human.issue_refundrequires a confirmed Proof of Resolution.
agentgateway.yaml
jwt.opaque_id.
backendAuth: passthrough forwards the caller’s Authorization header to your MCP server, so your tool can read the same token the gateway verified.
Policy cookbook
Rules are an allow-list: anything no rule allows is denied. These variations use only the variables shown above. Deny by default for a target. Allow named tools and nothing else. A new tool onmeridian stays closed until you add it.
link claim you can pin.
Present the proof
The caller sends the token as a standard bearer on the MCP request:- An MCP client that connects with OAuth holds the link JWT and sends it on every call. It cannot swap in a different token for one call. Gate sensitive tools on
jwt.opaque_id != ""at the gateway, and run the Agents Ask confirmation inside your tool, as the policy pack does. - An agent you build controls its own headers. It proposes the action, waits for the result, and retries the tool call with the
resolution_jwtas the bearer. Gate those tools onjwt.verdict == "approve".
Check the proof again in your tool
A Proof of Resolution lives for 24 hours and is bound to one action: itsjti is the request_id you sent when you proposed that action. The gateway rule does not know which action that was. jwt.verdict == "approve" proves only that some confirmed resolution for your agent is on the request. A caller could present the proof from a small refund while asking for a large one, or present the same proof twice.
Before your tool executes, check the rest:
pending is the record you saved when you proposed the action: the request_id you generated, the opaque_id you asked, and the parameters (amount, booking reference) you will execute. Execute those saved parameters, not the ones on the retried tool call. See Proof of Resolution for every claim.
Test it
1
Validate the file
2
Confirm both key sets are reachable
3
Call through the gateway with and without a proof
Use your MCP client or the playground in the agentgateway admin UI, and set the
Authorization header by hand. Expect these results:Troubleshooting
A valid-looking token is rejected: audience mismatch
A valid-looking token is rejected: audience mismatch
The Proof of Resolution The expected value is the Agent ID shown on the agent’s row under Agents Ask → Trusted Agents in the Console. A pack generated for one agent does not accept proofs for another. Development and production agents have different ids. Rotating an agent’s key does not change its Agent ID, so a rotation never requires a new pack; update
aud is the id of the agent that proposed the action. It must equal an entry in the provider’s audiences. Decode the token and compare:BOTSHIELD_AGENT_KEY in the link server’s .env and restart it. For the link JWT, aud must equal LINK_AUDIENCE in the link server’s .env (default mcp), and iss must equal LINK_ORIGIN exactly, with no trailing slash.HTTP 401: expired token
HTTP 401: expired token
Both tokens expire 24 hours after they are issued. For a link JWT, the person reconnects the client to link again. For a Proof of Resolution, propose the action again with a new
request_id. An action that expires before the person responds produces no proof at all.The gateway cannot fetch the JWKS
The gateway cannot fetch the JWKS
The gateway host needs outbound HTTPS to the JWKS URL. Check it from that host with
curl. If BotShield cannot serve its keys, the endpoint answers HTTP 503 with {"keys":[],"error":"jwks_unavailable"}; retry. Point jwks.url at the endpoint instead of a saved copy, because tokens name their signing key in the kid header and the key set can change. For a development agent, use the development JWKS URL. For the link server, jwks.file is relative to the folder you start agentgateway from, and the file exists only after link-server.js has run once.Open tools stopped working, or gated tools are open: jwtAuth.mode
Open tools stopped working, or gated tools are open: jwtAuth.mode
mode: optional lets a request with no bearer continue to the rules, which is what keeps search_flights open. mode: strict requires a valid bearer on every request, so open tools also need a token. In either mode, only the mcpAuthorization rules protect a tool. If a gated tool is reachable without a proof, look for a broader rule that also matches it, such as an open-tools rule whose list is missing the tool name. Tool names are case-sensitive.The link page never completes
The link page never completes
The link server calls BotShield with your agent key. It does not start without
BOTSHIELD_AGENT_KEY, so a running server has a key; check that it is the agent’s current key (a key that was revoked, or replaced with Rotate key, makes the link page report Could not start: bind_failed), and that BOTSHIELD_API_URL matches the agent’s environment. A production key does not work against the development host, and the reverse. Link codes expire after 10 minutes, and the sign-in itself expires after 15 minutes; when it does, the page asks the person to go back to their client and connect again.Next steps
Register an agent
Create the agent and key the policy pack is built for.
Link a human
How a person links to your agent and where the opaque id comes from.
Propose an action
Ask a linked human to confirm one action.
Proof of Resolution
Every claim in the signed result, and how to verify it.
