Connect an agent to an MCP server and it gets every tool that server exposes. The GitHub server brings repo deletion along with issue search. A database server brings DROP along with SELECT. The usual defense is a line in the system prompt asking the model not to use certain tools, which works until a prompt injection in a README asks it to.
A note on how this was made: I wrote it with help from Claude, Anthropic’s AI model, and checked each config against the agentgateway documentation. The images were generated with ChatGPT.
In part 1 of this series I made forbidden tools unrepresentable inside one Rust agent, using a single enum for both the prompt and the dispatcher. That works when you own the agent’s code. When you’re running Claude Code, Codex and a couple of in-house agents against shared MCP servers, the check has to live somewhere all of them pass through.
agentgateway can be that place. It’s a Rust proxy, hosted by the Linux Foundation and licensed Apache-2.0, that sits between agents and MCP servers (and A2A agents, and LLM APIs). It was on GitHub’s Rust trending list this week with 5,844 stars. This post covers the MCP part: putting servers behind it, then deciding per tool who can see and call what.
Agents connect to one endpoint, agentgateway’s /mcp. Behind it you list targets, which are the real MCP servers, either launched over stdio or reached over HTTP. agentgateway merges their tools into one list, and on every tools/list and tools/call it evaluates your authorization rules.
Tools that no rule allows are filtered out of the tools/list response. The agent never learns they exist, so a prompt injection has nothing to point it at, and a call to a hidden tool by name is blocked too.
The quickstart installs a single binary:
curl -sL https://agentgateway.dev/install | bash
For a test server, the quickstart uses the MCP reference server server-everything, which exposes sample tools like echo, add and printEnv:
PORT=3005 npx -y @modelcontextprotocol/server-everything streamableHttp
Then a minimal config.yaml:
Start it:
agentgateway -f config.yaml
Agents now connect to http://localhost:4000/mcp. The ui: {} line turns on a web UI at http://localhost:4000/ui/, which has a Tool Playground: click Initialize, pick a tool, call it, and see the result. It's the quickest way to check what an agent would see.
Authorization goes in an mcpAuthorization policy. Each rule is a CEL expression, and a tool is allowed if any rule matches. This is the example from the docs, using a stdio target this time:
With this config, an anonymous client lists tools and sees echo. add shows up only for a token whose subject is test-user. Every other tool on the server is gone from the list for everyone.
The variables you can use in these rules:
Prompts and resources are covered too, so you can hide a resource the same way as a tool.
The jwt.* rules need to know who the caller is. agentgateway's mcpAuthentication policy validates tokens against your identity provider's JWKS and publishes OAuth protected-resource metadata, so MCP clients that support OAuth can discover where to log in.
mcp: port: 3000 policies: mcpAuthentication: issuer: http://localhost:9000 audiences: - http://localhost:3000/mcp jwks: url: http://localhost:9000/.well-known/jwks.json resourceMetadata: resource: http://localhost:3000/mcp scopesSupported: - read:all bearerMethodsSupported: - header
Give each agent its own identity, a service account or a client ID, and the rules can tell Claude Code on a developer laptop apart from a nightly batch agent.
The pattern I’d copy first splits servers by risk. Read-only tools go on one target, tools that change things on another, and the rules key on the target name:
mcpAuthorization: rules: # Allow anyone to access tools on the public-tools target - 'mcp.tool.target == "public-tools"' # Only authenticated admins can access tools on the admin-tools target - 'mcp.tool.target == "admin-tools" && has(jwt.sub) && "admin" in jwt.roles' targets: - name: public-tools stdio: cmd: npx args: ["@modelcontextprotocol/server-everything"] - name: admin-tools stdio: cmd: npx args: ["@mycompany/admin-server"]
The docs point out one subtlety. Per-target rules filter tools after each target responds, so agentgateway still starts admin-tools and lists its tools even for a caller without the admin role, then hides them. If the server shouldn't be touched at all for those callers, use a target condition, which excludes the whole target.
mcp.tool.arguments is not available inside mcpAuthorization rules. You can say "this agent may call run_query," but not "only with a SELECT." The docs direct you to a route-level authorization policy for decisions based on arguments. I'd also keep argument checks inside the MCP server itself, since it understands its own inputs better than a proxy does.
You can at least record arguments. An access-log field captures them after the call:
frontendPolicies: accessLog: add: tool_args: 'mcp.tool.arguments'
With that you have a record of which agent called which tool with which arguments, and it’s where I’d look first when an agent does something odd.
The same binary handles LLM traffic (one OpenAI-compatible endpoint with failover, budgets and spend limits), A2A traffic between agents, rate limiting, guardrails and OpenTelemetry traces. If you read my last post on running Claude Code against a local mistral.rs model, agentgateway can also be the reverse proxy the mistral.rs docs recommend in front of a shared server, since mistral.rs doesn’t check API keys.
I wouldn’t write a rule for every tool on day one. Put your MCP servers behind agentgateway with an access log and no authorization, and watch which tools your agents actually call for a week. Then write allow rules for that list and let everything else disappear from tools/list. Agents will lose tools they weren't using anyway, and the ones that change data end up behind a role.
This is part of my Rust for AI series. Next up: a Rust embedding service and a Python one side by side, measuring what the Rust version saves.
Your AI Agents See Every MCP Tool. Agentgateway Lets You Hide Them was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.