METRA MCP Topology
Home Flow Components Stats Launch Copilot
Architecture · one question, end to end

What happens when you ask the Copilot a question.

Every Copilot answer crosses five systems: a Cloudflare tunnel, this origin, an inline AI gateway, Anthropic's API — and then Anthropic calls back into the MCP server to run the tools. That callback does not go through the gateway, which is the one thing worth knowing about this diagram.

The path

Arrows show request direction · responses stream back the same way
Metra MCP Copilot topology A visitor's browser reaches Cloudflare, which forwards over a cloudflared tunnel to the metra-mcp origin. The origin's chat proxy posts to the Netskope AI Gateway, which forwards to the Anthropic API. Anthropic's MCP connector calls the public MCP endpoint back through Cloudflare and the tunnel, bypassing the gateway. The MCP server reads Metra's GTFS feeds directly outbound. 01 Visitor browser → /copilot MCP client → /mcp 02 Cloudflare edge metra.remote-mcp.dev TLS · asset cache 03 cloudflared outbound tunnel no inbound ports 04 metra-mcp origin Starlette + uvicorn · :8080 COPILOT WEB / · /copilot · /stats · /api/chat MCP SERVER /mcp · 10 GTFS tools 05 Netskope AI GW ai-gw · /v1/messages slug + tenant JWT 06 Anthropic API claude-sonnet-5 streaming + MCP 07 Metra GTFS feeds gtfspublic.metrarr.com schedules.metrarail.com HTTPS TUNNEL :8080 /api/chat PROXY POST /v1/messages MODEL MCP connector calls metra.remote-mcp.dev/mcp directly — the tool traffic never reaches the gateway outbound HTTPS, read per tool call LEGEND Browser and MCP request path Model traffic — inspected by the gateway Tool callback — bypasses the gateway Direct egress to the data source
Responses stream back hop by hop as server-sent events — nothing buffers.

Seven hops

Ask “is the BNSF on time?” and this is what happens
01
Visitor
A browser opens /copilot — a small React app served from this origin, with React and DOMPurify vendored locally so the page runs under script-src 'self'. The same hostname also serves /mcp to MCP clients like Claude Desktop, Claude Code and claude.ai, which skip the chat UI entirely.
02
Cloudflare edge
Terminates TLS for metra.remote-mcp.dev and passes the client IP in cf-connecting-ip. It caches .css and .js on its own schedule regardless of the origin's no-cache, so each HTML page stamps its asset URLs with the file's mtime — a deploy changes the URL and the edge has to refetch.
03
cloudflared tunnel
A connector on the lab network holds an outbound tunnel to Cloudflare and forwards to port 8080. The origin publishes no inbound ports and is the only trusted proxy the server will believe forwarding headers from; anything else gets its raw peer address recorded instead.
04
metra-mcp origin
One Starlette process serves the three pages, the MCP endpoint and the /api/chat proxy. The proxy is the part that spends money, so it is fenced: per-IP and global daily request caps, a body-size and message-count limit, a model allowlist, and a server-side system prompt the client cannot touch.
05
Netskope AI Gateway
Instead of api.anthropic.com, the proxy posts to the gateway's /v1/messages with a routing slug and a tenant JWT; the Anthropic key rides along in x-api-key for the gateway to forward upstream. It has to pass SSE through unbuffered — a gateway that holds the response would trip Cloudflare's first-byte timeout on tool-heavy questions.
06
Anthropic API
The request carries the MCP connector beta and an mcp_servers block naming this server's public URL. Claude decides which tools to call and calls them itself — back in over Cloudflare and the tunnel, not back down the gateway connection. The gateway therefore sees the prompt, the answer and the tool-use blocks in the stream, but not the tool traffic.
07
Metra GTFS feeds
Tool calls read Metra's public feeds directly outbound: realtime positions, trip updates and alerts per call (briefly cached), and the static schedule from the published GTFS zip, refreshed on a timer. That egress leaves the origin on its own — it crosses neither Cloudflare nor the gateway.
→
And back
Tokens stream home along the same chain and the proxy relays the SSE bytes untouched. The browser renders the answer through DOMPurify with style attributes stripped, so model output can only ever paint itself in the site's own class vocabulary. Anything the gateway rejects arrives as an SSE error event and is shown as-is.

Components

What runs where
Component Runs on Role
Cloudflare edgeremote-mcp.dev zoneTLS termination, DDoS absorption, edge caching of static assets
cloudflaredDocker host, lab networkOutbound-only tunnel to the edge; the origin's single trusted proxy
Copilot web servermetra-mcp container, :8080Serves /, /copilot, /stats, /topo and the /api/chat proxy
Metra MCP serverSame process, /mcpTen GTFS tools over streamable HTTP, with a Host allowlist
Netskope AI GatewayLab VM, ai-gwInline inspection of the Anthropic Messages traffic; slug routing, tenant auth
Anthropic APIapi.anthropic.comRuns the model, drives the MCP connector callbacks
Metra GTFS feedsmetrarr.com / metrarail.comRealtime vehicle, trip and alert feeds; daily static schedule
Internal addressing is deliberately omitted — this page is public.

What the gateway sees

Everything on the model connection: the system prompt, the visitor's question, the model's answer, and the tool_use blocks Claude emits as it decides what to call. That is the surface DLP and model policy apply to.

Upstream/v1/messages
TransportSSE, unbuffered

What it doesn't

The tool calls themselves. Anthropic's MCP connector opens its own connection to the public /mcp URL, so arguments and results travel Anthropic → Cloudflare → tunnel → origin and never touch the gateway. The origin's reads of Metra's feeds are outside it too.

Callback/mcp
Feed egressdirect
Five systems. One answer.
The answer goes out through the gateway.
The tools come back in the front door.
Try it See the traffic
Metra MCP Server · Docs · Stats · Model Context Protocol Unofficial · not affiliated with Metra