The cluster deployment of Context Forge, the MCP (Model Context Protocol) gateway in front of the monolith's tool surface. It is the entry point that lets Claude.ai connectors and Claude Code reach the monolith's tools with one OAuth login, and it filters that catalogue per caller identity.
How the whole surface fits together, including the parts that are not in this directory: ARCHITECTURE.md. That is the current-state document and the thing to link to. This README stays the operator's view of the deployment.
Two facts to hold before changing anything here:
- Context Forge is one of two MCP entry points. Ember guests never reach it; they talk to the monolith-agents tier through the egress sidecar. See Topology.
- Its removal is decided but not executed. ADR agents/059 (Draft) supersedes agents/020 and keeps the same end state, the monolith serving
mcp.jomcgi.devdirectly; #3832 (cutover) and #3833 (decommission) carry the work, and everything below is still the deployed reality. The decision history preserves that rationale.
What it is
Context Forge is the IBM mcp-context-forge gateway, deployed as an ArgoCD Application in the mcp namespace of the GKE hub. It exposes registered backends as virtual MCP tools over streamable HTTP. Two backends are registered and only one is enabled: the monolith's /mcp mount. The GitHub registration is disabled and carries no tools.
External access (Claude.ai, Claude Code) goes through Cloudflare Tunnel to mcp.jomcgi.dev, where authentik is the IdP and Context Forge filters the catalogue per identity. The only in-cluster caller is the chart's own team-mapping CronJob, which uses the REST API; in-cluster agents do not use this gateway.
The property the authorization model rests on is that Context Forge's ACL is tool-granular: it decides whether you may call search_knowledge, never what search_knowledge returns. Result-level scoping has to come from the backend, which is why the caller's token is forwarded to the monolith (see Token forwarding).
Directory layout
projects/mcp/
└── context-forge-gateway/
├── chart/ # Custom Helm chart wrapping the upstream mcp-stack subchart
│ ├── Chart.yaml # Chart metadata; declares mcp-stack + homelab-library deps
│ ├── values.yaml # Chart defaults (feature flags off, auth flags, inert teamMapping)
│ ├── files/reconcile_team_mapping.py # Team-mapping reconciler run by the CronJob
│ └── templates/
│ ├── httproute.yaml # mcp.jomcgi.dev: RFC 9728 discovery + /mcp rewrite
│ ├── httproute-scoped.yaml # Per-tier hostnames (friends.jomcgi.dev), allowlist routes
│ ├── httproute-preview.yaml # Browser-authenticated preview lane, not MCP
│ ├── team-mapping-cronjob.yaml # Reconciles authentik group -> CF team from values
│ ├── postgres-cnpg.yaml # CloudNativePG cluster and scheduled GCS backup
│ ├── cnpg-backup-gcs-secret.yaml
│ ├── onepassworditem.yaml # 1Password secret sync (JWT_SECRET_KEY, etc.)
│ └── networkpolicy.yaml # Cross-namespace ingress policy (disabled; see below)
├── scripts/provision-mcp-auth.sh # Creates the sso_providers row after a DB rebuild
└── deploy/
├── application.yaml # Home-cluster Application shape (no root references it)
├── kustomization.yaml
├── values.yaml # Production overrides (extraEnv auth flags, routes, teams)
├── values-gke.yaml # Hub overrides (CNPG recovery bootstrap, storage class)
└── cnpg-gcs-backup-secret.md
The hub's Application lives in projects/gke-apps/context-forge-gateway/ and layers the three values files in that order.
How it builds and deploys
The chart wraps the upstream mcp-stack chart (mirrored to ghcr.io/jomcgi/homelab/charts). There is no custom container image in this repo; the gateway runs the upstream ghcr.io/ibm/mcp-context-forge image, pinned by tag in chart/values.yaml.
ArgoCD reads the chart directly from the Git repository path (projects/mcp/context-forge-gateway/chart) at HEAD and deep-merges deploy/values.yaml and deploy/values-gke.yaml on top. No OCI chart push is needed for this service: merging is the deploy.
To update the bundled upstream chart version:
# Clone the desired tag, package, mirror to GHCR, then update Chart.yaml + re-run:
helm dependency update projects/mcp/context-forge-gateway/chart
Key configuration notes
- Auth: read
deploy/values.yamlfor the effective values, neverchart/values.yaml. Everything deliberately set lives inmcpContextForge.extraEnv, which outranks everyenvFromsource, and the two files disagree on purpose:MCP_REQUIRE_AUTHis"false"in the chart defaults and"true"indeploy/values.yaml, so the deployed value is true and the JSON-RPC endpoint authenticates. External-IdP bearer auth requires bothSSO_ENABLED(mounts the/auth/ssorouter) andSSO_API_TOKEN_AUTH_ENABLED(lets a trusted provider's token authenticate an MCP request), plus atrusted_for_api_authsso_providersrow; no one of the three is sufficient alone. EachextraEnventry carries a comment explaining why it is set; read it before changing one. The full effective table is in Authentication. - Secrets: A
OnePasswordItemnamedcontext-forge(item path indeploy/values.yaml) syncsJWT_SECRET_KEY,AUTH_ENCRYPTION_SECRET,PLATFORM_ADMIN_EMAILandPLATFORM_ADMIN_PASSWORDinto a Kubernetes secret. No backend credential is injected: the monolith registration has no gateway-level auth, which is what lets the caller's own token pass through. - NetworkPolicy disabled: auth is enforced at the application layer instead.
- In-cluster access: the gateway Service's
/mcppath inside themcpnamespace (the hostname is whatever the release renders; read it from the Service, never hardcode it). This is no longer an unauthenticated path:MCP_REQUIRE_AUTHis gateway-wide rather than per virtual server, so in-cluster callers authenticate too. The one in-cluster caller, the team-mapping CronJob, mints a short-lived admin JWT from the synced secret and calls the REST API, not/mcp. - UI and catalog: disabled by default in
chart/values.yaml; re-enabled indeploy/values.yamlfor the production cluster. - Tool catalog refresh: The monolith registration lives in Context Forge's Postgres, not git, and is set by hand to
STREAMABLEHTTP. Context Forge's health-check loop is now the only catalog refresh path, withAUTO_REFRESH_SERVERS=trueandGATEWAY_AUTO_REFRESH_INTERVAL=600. Failure reporting is warning only, and a tool rejected by the XSS validator is dropped silently, so checklast_refresh_aton the monolithgatewaysrow and compare the tool count. Refresh never publishes a new tool: the association and visibility rows are hand edits, listed in Tool catalogue refresh. - SSRF allowlist:
SSRF_ALLOWED_NETWORKSis checked when a gateway row is created or updated, so it only bites on the next registration. It still names the home cluster's ranges; see Configuration discipline.
GitHub access for agents
ADR agents/055 put GitHub tool mediation on this gateway. ADR agents/059 superseded it: mediation moves to the monolith's own GitHub mutation broker (#4946), enforced against a Principal's delegation rather than a group's PAT, and nothing of 055's design was built here. The GitHub gateway registration in Context Forge is disabled and carries no tools. Agent guests reach GitHub through the EmberVM egress lane, which is that domain's concern.