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 remote guardrails
Verified : Code examples on this page have been automatically tested and verified.Gate and mutate MCP method calls with an external ExtMCP policy server.
Inspect, rewrite, or reject Model Context Protocol (MCP) calls with an external ExtMCP policy server.
For an overview, see Guardrail processors. To run guardrails inside agentgateway, see Set up CEL 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 remote 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 gcr.io/solo-public/docs/testbox:latestNote
Build 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 and implement both methods. 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 Guardrail processors.
In another terminal, create a
config.yamlfile. The MCP backend exposes a local stdio MCP server, and themcpGuardrailspolicy on the route sends selected MCP methods through the ExtMCP server.# yaml-language-server: $schema=https://agentgateway.dev/schema/config gateways: default: port: 3000 routes: - policies: cors: allowOrigins: - "*" allowHeaders: - mcp-protocol-version - content-type - mcp-session-id exposeHeaders: - "Mcp-Session-Id" mcpGuardrails: processors: - kind: remote host: "localhost:9001" failureMode: failClosed methods: tools/call: request tools/list: response backends: - mcp: targets: - name: everything stdio: cmd: npx args: ["@modelcontextprotocol/server-everything"]Review 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.methodsMCP methods and phases to send to the policy server. tools/call: requestchecks tool calls before forwarding them.tools/list: responsefilters or annotates tool lists after the backend responds. See Methods and phases.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