Obot MCP Gateway
How OpenCrane runs and governs MCP (Model Context Protocol) servers for tenant agents. Obot is the in-cluster runtime gateway; the control plane is the source of truth for the catalog and per-tenant entitlements.
See also: skills-registry.md (the sibling delivery plane), auth.md (token audiences), and hosting-architecture.md.
What Obot is
Obot is the upstream obot-platform/obot MCP gateway, deployed as a managed in-cluster plane. Tenant agents (OpenClaw) reach their MCP tools through Obot rather than connecting to each MCP server directly; Obot holds the live server connections and routes calls.
Crucially, Obot does not own the list of servers. It is config-slaved to the control plane: it polls the control-plane registry and serves whatever the control plane has published. The direction of truth is always control plane → Obot.
oc CLI / API ──▶ Control plane (McpServer rows + grants)
│ GET /api/internal/obot-registry ◀── Obot polls (OBOT_SERVER_PROVIDER_REGISTRIES)
▼
Obot MCP Gateway ──routes──▶ MCP servers
▲
tenant pod (OpenClaw) ──aud=obot-gateway projected token──┘ (in-cluster only)Deployment & network posture
- Workload:
platform/helm/templates/obot-mcp-gateway-deployment.yaml+mcp-gateway-service.yaml; configured by themcpGatewayblock inplatform/helm/values.yaml(imageghcr.io/obot-platform/obot, 1 replica, port 8080). Requires a per-instance, release-prefixed<release>-obotSecret (resolved by theopencrane.obotSecretNameHelm helper) with keydsnfor Obot's own Postgres. - Auth disabled, network-gated.
OBOT_SERVER_ENABLE_AUTHENTICATION=false— Obot itself runs no auth. Access is enforced at the network layer: themcp-gateway-ingresspolicy inplatform/helm/templates/networkpolicy-planes.yamladmits port 8080 only from tenant, control-plane, and operator pods. There is no external ingress; the browser never reaches Obot. - Kubernetes runtime backend.
OBOT_SERVER_MCPRUNTIME_BACKEND=kubernetes— Obot spawns MCP servers as in-cluster pods.
Catalog sync (control plane → Obot)
- The control plane owns the
McpServertable (Prisma model inapps/control-plane/prisma/schema.prisma):id,name(unique),description,endpoint,transport,scope,status,capabilities, plus optionalsourceIdlinking to aThirdPartySource(MCP registry / git / manual upload). - It exposes the Obot-wire catalog at
GET /api/internal/obot-registry(obot-registry.ts), serving onlystatus = Activeservers, ordered by name. The endpoint is not behind___AuthMiddleware; NetworkPolicy is its access control. - Obot is pointed at it via
OBOT_SERVER_PROVIDER_REGISTRIESand polls to sync. - Management surface: CRUD lives at
/api/v1/mcp-servers(mcp-servers.ts) and viaoc mcp …. Third-party sources are ingested through the fetch → scan → validate → register → entitle pipeline.
How a tenant pod reaches Obot
The operator injects a projected ServiceAccount token with audience obot-gateway into every tenant pod at /var/run/opencrane/tokens/obot-gateway.token, alongside OPENCRANE_MCP_GATEWAY_URL (3-deployment.ts). The pod (OpenClaw) calls Obot server-side with that token. This token is workload identity — it is never handed to a browser. (The browser's path to the pod is the separate pairing-link broker; see auth.md.)
MCP policy: three enforcement points, one decision
A grant decision in the control plane fans out to three consumers, all derived from the same grant-compiler output — so they cannot disagree by construction:
- Obot catalog — synced from the registry; determines which servers the gateway will route at all.
- Runtime contract policy —
policy.mcpServers.allow/denyin the effective contract, re-pulled by the pod (tenant-contract.ts). - In-pod enforcement —
entrypoint.sh_load_mcp_policy/_mcp_server_is_enabledevaluate, in precedence order: tenant-CRDmcpPolicy.deny(always wins) → tenant-CRDmcpPolicy.allow→ AccessPolicy deny → AccessPolicy allow.
The agent's awareness of its tools (TOOLS.md, see skills-registry.md and the contract's workspace block) is derived from the same allow-set — so what the agent thinks it can use stays aligned with what IAM lets it use. Awareness is descriptive; Obot + the runtime policy are the enforcement boundary.
Keeping Obot from drifting
The operator's runtime-plane drift repairer (drift-repairer.ts) runs on a ~60s interval and re-patches Obot's critical env (OBOT_SERVER_PROVIDER_REGISTRIES, OBOT_SERVER_ENABLE_AUTHENTICATION, OBOT_SERVER_MCPRUNTIME_BACKEND) in place if it drifts from control-plane intent — without a pod restart, preserving valueFrom references. Image/replica/resource changes are not reconciled here; use Helm.
Current state & gaps
- ✅ Obot deployed as a config-slaved plane; catalog sync + drift repair live.
- ✅ Control-plane
McpServerCRUD + grants; per-tenant allow/deny compiled into the contract and enforced in-pod. - 🔶 Credential brokering for downstream MCP auth (per-user creds, encryption at rest) is not in this phase —
OBOT_SERVER_ENCRYPTION_PROVIDER=none. The earlier "RFC 8693 credential shim" remains a target, not current behaviour.