For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Set up MCP guardrails
Gate and mutate MCP method calls with an external ExtMCP policy server.
Gate and mutate Model Context Protocol (MCP) method calls with an external policy server. For more information about how MCP guardrails work, see About MCP guardrails.
In this guide, you route tools/call and tools/list requests through a sample ExtMCP server that denies any tool whose name contains forbidden and annotates each tool description in tools/list responses.
Before you begin
Install theagentgateway binary.Set up MCP guardrails
Start a sample ExtMCP policy server. This example uses a prebuilt gRPC server that denies
tools/callwhen the tool name containsforbidden, and appends[extmcp]to every tool description intools/listresponses. The server listens on port9001.docker run --rm -p 9001:9001 ghcr.io/agentgateway/testbox:0.0.1Build your own ExtMCP server: The sample server is for demonstration only. To build your own, implement the
ExtMcpgRPC service from the ExtMCP protocol definition. The service has two methods:CheckRequest: Called in the request phase, before the call reaches the MCP backend. Return the request unchanged, return mutatedparams, or return anAuthorizationErrorto deny the call.CheckResponse: Called in the response phase, after the MCP backend returns a result. Return the response unchanged, return a mutatedresult, or return anAuthorizationErrorto deny the call.
Generate gRPC bindings from the proto file in your language, implement the two methods, and serve them over cleartext HTTP/2 (h2c) on the port that agentgateway connects to. For more information about the request and response messages, outcomes, and error codes, see About MCP guardrails.
In another terminal, create a
config.yamlfile. The MCP backend exposes a local stdio MCP server, and themcpGuardrailspolicy on the target routes selected MCP methods through the ExtMCP server.# yaml-language-server: $schema=https://agentgateway.dev/schema/config binds: - port: 3000 listeners: - routes: - policies: cors: allowOrigins: - "*" allowHeaders: - mcp-protocol-version - content-type - mcp-session-id exposeHeaders: - "Mcp-Session-Id" backends: - mcp: targets: - name: everything stdio: cmd: npx args: ["@modelcontextprotocol/server-everything"] policies: mcpGuardrails: processors: - kind: remote host: "localhost:9001" failureMode: failClosed methods: tools/call: request tools/list: responseReview the following table to understand the
mcpGuardrailspolicy.Setting Description kind: remoteUse a remote gRPC ExtMCP server to enforce this processor. hostThe address of the ExtMCP policy server. This example points to the sample server from the previous step. failureMode: failClosedDeny requests if the policy server is unreachable or returns an error. To allow requests instead, set failOpen.methodsThe MCP methods to route through the policy server, and the phase for each. tools/call: requestsends each tool call to the server before it reaches the MCP backend, so the server can allow, mutate, or deny the call.tools/list: responsesends the tool listing to the server after the backend returns it, so the server can filter or annotate the list. For the full list of phases and method matching, see About MCP guardrails.Run agentgateway with the configuration file.
agentgateway -f config.yaml
Verify the guardrails
Verify that the policy server gates tools/call and mutates tools/list responses.
Initialize an MCP session and save the session ID.
export MCP_SESSION_ID=$(curl -s -D - http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}' \ | grep -i "mcp-session-id:" | sed 's/.*: //' | tr -d '\r') echo $MCP_SESSION_IDSend the
notifications/initializednotification to complete the MCP handshake. The MCP server does not answer other requests, such astools/list, until initialization is complete.curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'List the available tools. Verify that each tool description ends with
[extmcp], which the policy server added in the response phase.curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'Example output:
data: {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"echo","description":"Echoes back the input [extmcp]",...}]}}Call a tool whose name contains
forbidden. Verify that the policy server denies the call with a JSON-RPC error.curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"forbidden-tool","arguments":{}}}'Example output:
{"jsonrpc":"2.0","id":3,"error":{"code":-32001,"message":"tool forbidden-tool is not allowed"}}Call an allowed tool, such as
echo. Verify that the call succeeds.curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"echo","arguments":{"message":"hello"}}}'
Cleanup
You can remove the resources that you created in this guide.- Stop the agentgateway process with
Ctrl+C. - Stop the sample ExtMCP server with
Ctrl+Cin its terminal. - Delete the configuration file.
rm config.yaml