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.
/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.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./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.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.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.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.| Component | Runs on | Role |
|---|---|---|
| Cloudflare edge | remote-mcp.dev zone | TLS termination, DDoS absorption, edge caching of static assets |
| cloudflared | Docker host, lab network | Outbound-only tunnel to the edge; the origin's single trusted proxy |
| Copilot web server | metra-mcp container, :8080 | Serves /, /copilot, /stats, /topo and the /api/chat proxy |
| Metra MCP server | Same process, /mcp | Ten GTFS tools over streamable HTTP, with a Host allowlist |
| Netskope AI Gateway | Lab VM, ai-gw | Inline inspection of the Anthropic Messages traffic; slug routing, tenant auth |
| Anthropic API | api.anthropic.com | Runs the model, drives the MCP connector callbacks |
| Metra GTFS feeds | metrarr.com / metrarail.com | Realtime vehicle, trip and alert feeds; daily static schedule |
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.
/v1/messagesSSE, unbufferedThe 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.
/mcpdirect