MCP Gateway — Client Setup
Operational guide for connecting Claude Code and Claude Desktop to the
self-hosted agentgateway MCP gateway. agentgateway fronts six upstream MCP
servers behind a single host (mcp-gw.fzymgc.house) and exposes each at its own
path. Two auth models run side by side: engram uses Keycloak OAuth with
real RFC 7591 Dynamic Client Registration (DCR), and the SaaS servers use
a per-client virtual key.
Replaces the LiteLLM MCP gateway, which is fully decommissioned (
hl-0srPhase 5;litellm.fzymgc.houseno longer resolves and thelitellmHostMapping is gone). All MCP clients MUST usemcp-gw.fzymgc.house; there is no LiteLLM fallback.
Quick reference
Section titled “Quick reference”| Property | Value |
|---|---|
| Gateway host | https://mcp-gw.fzymgc.house |
| MCP endpoint (per server) | https://mcp-gw.fzymgc.house/mcp/<server> |
| Servers | engram, exa, firecrawl, context7, fal, deepwiki |
| engram auth | OAuth/JWT — Keycloak mcp-public client (PKCE + DCR, no static key) |
| SaaS auth | Virtual key — Authorization: Bearer <vk_claude_code> |
| Client virtual key | Vault secret/fzymgc-house/cluster/agentgateway, property vk_claude_code |
| Admin UI | https://agentgateway.fzymgc.house (shared oauth2-proxy forward-auth) |
| Claude Code support | Native — handles OAuth and custom headers directly |
| Claude Desktop support | engram natively (OAuth connector); SaaS via the mcp-remote bridge |
How authentication works
Section titled “How authentication works”agentgateway gates each route differently, so the client credential depends on the server:
- engram — the route enforces JWT validation (
mcp-engram-oauthpolicy, Keycloakmcp-publicissuer). The client performs the OAuth 2.1 PKCE flow, registering itself dynamically via RFC 7591 DCR (agentgateway proxies the anonymous/registercall to Keycloak — see ADRhl-ll5p), and presents the resulting bearer JWT. No static key is minted or stored on the client. agentgateway validates the token, then re-attaches it to the upstream so engram derives memory ownership from the verifiedemailclaim. - SaaS servers (
exa,firecrawl,context7,fal,deepwiki) — the route enforcesapiKeyAuthentication(Strict) against theagentgateway-vkeysaccept-list. The client sends its own virtual key asAuthorization: Bearer <vk_claude_code>. agentgateway then injects the real per-upstream API key (from Vault) on the way out, so upstream secrets never reach the client.deepwikineeds no upstream key.
Because engram is OAuth and the SaaS servers are virtual-key, they are added to a client as separate entries — engram with no header, the rest with the bearer header.
Getting a client virtual key
Section titled “Getting a client virtual key”Virtual keys are GitOps-governed, not minted via a runtime API. The accept-list
lives in the agentgateway-vkeys ExternalSecret
(argocd/app-configs/agentgateway/secrets.yaml), templated from Vault:
# Read the existing human client key (read-only)vault kv get -field=vk_claude_code secret/fzymgc-house/cluster/agentgatewayTo add a new client key: add a vk_<name> property to Vault
fzymgc-house/cluster/agentgateway, then add a matching accept-list entry to
secrets.yaml (the value is JSON: {"key":"{{ .vk_<name> }}","metadata":{"group":"<group>"}}).
ArgoCD + ESO propagate it. Existing keys: vk_claude_code (human),
vk_octopus (service), vk_engram_embedder (embedder).
Claude Code
Section titled “Claude Code”engram (OAuth — Claude Code runs the PKCE flow and caches the token):
claude mcp add --transport http engram https://mcp-gw.fzymgc.house/mcp/engram --scope userOn first use Claude Code opens the Keycloak login (the mcp-public consent
flow) and stores the token; re-auth is handled automatically on expiry.
Each SaaS server (virtual key in a header). Add only the ones you need:
claude mcp add --transport http exa https://mcp-gw.fzymgc.house/mcp/exa \ --header "Authorization: Bearer <vk_claude_code>" --scope user--scope usermakes the server available across all projects; use--scope localto limit it to the current repo.- Repeat with
firecrawl,context7,fal,deepwiki, swapping the server name and/mcp/<server>path; the samevk_claude_codeworks for all of them. - All flags must precede the server name in the
claude mcp addcommand.
Claude Desktop
Section titled “Claude Desktop”- engram — add as a remote connector pointing at
https://mcp-gw.fzymgc.house/mcp/engram; Claude Desktop’s OAuth connector completes the Keycloak PKCE + DCR flow natively (no bridge needed). - SaaS servers — Claude Desktop has no field for a static auth header on
remote connectors, so the
mcp-remotestdio bridge injects it. Edit~/Library/Application Support/Claude/claude_desktop_config.json:
{ "mcpServers": { "exa": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp-gw.fzymgc.house/mcp/exa", "--header", "Authorization:${AGENTGATEWAY_VK}" ], "env": { "AGENTGATEWAY_VK": "Bearer <vk_claude_code>" } } }}Header-quoting gotcha. Claude Desktop’s argument parser mangles header values that contain spaces (like
Bearer <vk>). Put the value in an env var and write the--headerflag with no space after the colon.
Fully quit Claude Desktop (⌘Q) and relaunch — it reads the config only at startup.
Verify
Section titled “Verify”engram (OAuth) — confirm the connected state and a memory round-trip:
claude mcp get engram # shows connected state + tool count after OAuthA SaaS server — smoke-test the virtual key directly (no client needed):
curl -sS -X POST https://mcp-gw.fzymgc.house/mcp/exa \ -H "Authorization: Bearer <vk_claude_code>" \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' | jq '.result.tools[].name'An unauthenticated request to a gated route returns 401 with a
WWW-Authenticate: Bearer resource_metadata=… header (engram) or a plain 401
(SaaS) — a 200 with tools confirms the key and endpoint, isolating any
remaining fault to client config.
Rollback
Section titled “Rollback”LiteLLM is fully decommissioned (hl-0sr Phase 5); litellm.fzymgc.house no
longer resolves and there is no LiteLLM fallback to roll back to. If an
agentgateway MCP route fails, the rollback is a GitOps revert of the
agentgateway app manifests (ArgoCD), not a client repoint. For the historical
LiteLLM endpoint/key shape, see the git history of this file. New work MUST
NOT target litellm.fzymgc.house.
Troubleshooting
Section titled “Troubleshooting”engram: client never connects / no OAuth prompt
Section titled “engram: client never connects / no OAuth prompt”The route requires a valid mcp-public token. Confirm the server URL is exactly
https://mcp-gw.fzymgc.house/mcp/engram; a wrong path (e.g. /mcp) returns
404 route not found rather than triggering the OAuth flow.
SaaS: 401/403 from the gateway
Section titled “SaaS: 401/403 from the gateway”The virtual key is wrong, not sent, or sent without the Bearer scheme. Re-run
the Verify curl; a 200 with tools confirms the key and endpoint.
Claude Desktop SaaS server fails to authenticate
Section titled “Claude Desktop SaaS server fails to authenticate”Almost always the header-quoting gotcha — confirm the --header arg is
Authorization:${AGENTGATEWAY_VK} (no space after the colon) and that
env.AGENTGATEWAY_VK holds Bearer <vk>. Quit and relaunch after any edit.
Related
Section titled “Related”- AI Tooling — general MCP server configuration
- Services catalog — agentgateway architecture and the canonical
.mcp.jsonblock - Secrets & Vault Paths — Vault path conventions
- agentgateway manifests:
argocd/app-configs/agentgateway/(routes, policies, and theagentgateway-vkeysaccept-list insecrets.yaml)