Skip to main content
agentgateway is an open-source gateway for agent and MCP traffic. If you front your MCP tools with it, you can make the gateway refuse a sensitive tool call unless the caller presents proof that a real person is behind it. The gateway checks a signed JWT against a public key set and evaluates a one-line rule. No BotShield code runs in the gateway, and your MCP server does not change.
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:
  • jwtAuth verifies the bearer token on each MCP request: signature (against a JWKS), issuer, audience, and expiry.
  • mcpAuthorization is a list of CEL allow rules. A rule can read the MCP request (mcp.tool.name, mcp.tool.target, …) and the claims of the verified token (jwt.<claim>).
There are two tokens a rule can rely on. They come from different issuers and carry different claims, so be exact about which one a rule reads. 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 BotShield Gate attestation token (the one <botshield-verify> produces) has no aud claim. A jwtAuth provider that checks audiences rejects it. Use the two tokens above at the gateway, and verify gate tokens on your server as described in Verify on your server.
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

Or download a release from the agentgateway releases page.
2

Start the link server

This is the run command in the pack’s own 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

MCP traffic is served on port 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.
With the generated rules, the tools you named run only for a linked human. Your MCP server then asks that human to confirm each sensitive action: 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. 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.
The link server has no login of its own, and its dynamic client registration endpoint (/oauth/register) is open: any client can register and start a link. What stops a stranger is the link itself, which needs a person to confirm in the BotShield app with their biometric. If only known users or known clients should be able to start a link, put your own authentication in front of the link server at your reverse proxy.PKCE protects the token in transit. It does not tell your tool which action a caller is allowed to run. Keep checking jti and the action details in your tool, as described in Check the proof again in your tool.

Write policies yourself

You do not need the policy pack. Any agentgateway configuration can trust BotShield with one jwtAuth provider and a few rules.

Worked example: Meridian Airlines

Meridian Airlines fronts one MCP server with three tools:
  • search_flights is open to any caller.
  • hold_seat requires a linked human.
  • issue_refund requires a confirmed Proof of Resolution.
agentgateway.yaml
Replace the agent id with your own Agent ID, which you copy from the agent’s row under Agents Ask → Trusted Agents in the Console. If you do not use the link server, remove the second provider and the rules that read 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 on meridian stays closed until you add it.
Require a linked human for every tool on a target. Drop the open-tools rule and use one rule for the whole target.
Put several tools behind the same proof. Use a list instead of one rule per tool.
Gate resources with either proof. Two rules for the same target act as “or”.
Accept only link JWTs from the policy pack’s link server. The link JWT carries a link claim you can pin.

Present the proof

The caller sends the token as a standard bearer on the MCP request:
One request carries one bearer. That decides which pattern fits your caller:
  • 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_jwt as the bearer. Gate those tools on jwt.verdict == "approve".

Check the proof again in your tool

A Proof of Resolution lives for 24 hours and is bound to one action: its jti 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:
Use a development agent while you test. Development agents are served by the development API host in the table above.

Troubleshooting

The Proof of Resolution 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:
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 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.
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 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.
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.

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.