5.2. MCP Gateway
How is the MCP route secured?
The shipped configuration has one route, one fail-closed MCP target, and an explicit allowlist:
- port: 3000
listeners:
- name: mcp
routes:
- policies:
localRateLimit:
- maxTokens: 120
tokensPerFill: 120
fillInterval: 60s
mcpAuthorization:
rules:
- 'mcp.tool.name == "list_incidents"'
- 'mcp.tool.name == "get_incident"'
- 'mcp.tool.name == "get_service_status"'
- 'mcp.tool.name == "search_service_logs"'
- 'mcp.tool.name == "get_runbook"'
- 'mcp.tool.name == "search_runbooks"'
backends:
- mcp:
failureMode: failClosed
The host file targets localhost:8000/mcp; k3d/GKE target the internal agentops-mcp service. A backend failure denies the call instead of silently bypassing the policy. The MCP server independently validates the forwarded Host authority, so an unexpected authority is rejected with 421 before protocol handling.
Why are write tools absent?
restart_service and resolve_incident depend on ADK confirmation and audit identity. They remain in the agent process and cannot be discovered through MCP. The gateway allowlist therefore matches the six functions exported by mcp_server.py exactly.
How does the deployed agent select this path?
root_agent conditionally replaces its local read/knowledge functions with ops_mcp_toolset(). Guarded writes and the instruction-only skill toolset remain local.
How do you list tools through the gateway?
With the host stack running, execute from agents/python/:
uv run python - <<'PY'
import asyncio
import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
URL = "http://127.0.0.1:3000/mcp"
async def main() -> None:
async with httpx.AsyncClient() as client:
async with streamable_http_client(
URL,
http_client=client,
terminate_on_close=False,
) as (read, write, get_session_id):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.list_tools()
print("\n".join(sorted(tool.name for tool in result.tools)))
session_id = get_session_id()
if session_id:
response = await client.delete(
URL,
headers={"Mcp-Session-Id": session_id},
)
if response.status_code not in {200, 202, 204}:
response.raise_for_status()
asyncio.run(main())
PY
Expected names are the six read/runbook tools in the allowlist. No write action should appear.
The explicit DELETE checks session cleanup instead of hiding it. agentgateway 1.3.1 accepts termination with 202; the current MCP Python client's automatic closer recognizes only 200/204 and otherwise prints a misleading warning after a successful request.
What does the rate limit guarantee?
It limits requests per gateway instance with a local token bucket. It is not a user quota or cluster-wide distributed budget. A production multi-replica design needs authenticated identity and a shared policy/quota backend.
What is the MCP checkpoint?
Stop the raw MCP process and repeat the list request: the gateway must fail closed. Restart it, confirm the six tools return, and inspect gateway logs/metrics for both outcomes.