How marketplace agent apps connect to the Hub MCP server at runtime, and what to verify before dev testing.
The Hub exposes a platform MCP server (not the per-app stdio bridge):
| Endpoint | Method | Auth |
|---|---|---|
/api/mcp |
POST/GET (Streamable HTTP) | Authorization: Bearer <API key> |
The hashed key store is the sole auth authority (SEC-MCP-8). Nothing is seeded at boot: every key
in the store was either created by an operator or provisioned to an app, so revoking one really
retires it. The guard deliberately has no MCP_API_KEY env fallback — that value is derived from
the appliance seed, and a live env compare would be a credential no revoke could retire.
Create a key one of two ways:
- CLI —
cihub api-key create --name "<label>". Prints the raw key once; store it immediately. - UI — Settings → Security. Rotate by creating the new key, rolling it out, then revoking the old one.
cihub mcp setup only toggles MCP_ENABLED; it does not mint or install a credential.
At app install, when hub_integration.mcp_client: true, the Hub injects:
| Env var | Purpose |
|---|---|
HUB_URL |
Hub base URL (health, inference REST) |
HUB_MCP_URL |
Streamable HTTP MCP endpoint — <HUB_URL>/api/mcp |
HUB_MCP_API_KEY |
Per-app managed MCP key, minted by the Hub key store (revoked on uninstall) |
HUB_WAKE_SECRET |
Validates inbound wake webhooks (OpenClaw) |
HUB_MCP_ENABLED |
Set false to skip MCP wiring |
Marketplace entries for ci-hermes and ci-openclaw set mcp_client: true.
The app's UI drives an agent that holds this key, so the Hub publishes an mcp_client app's host port
on 127.0.0.1 only, not on the LAN or tailnet. To reach the app from another device, use Private VPN
or Public web. Apps whose manifest sets hub_integration.edge_auth.default: true get the same bind,
because they have no login of their own.
On its own app a managed key passes every per-app grant check but one: changing which custom domain the
app serves takes an organization owner or admin, so that change is refused with
CUSTOM_DOMAIN_ROLE_REQUIRED at every level. On every other app, the level the key is given in
Settings → Security decides how far it reaches
(CI-Hub#1397). The level is read on
every call, so a change applies from the agent's next call:
| Level | On an app that is not its own |
|---|---|
read |
Read the app: status, logs, config, backups, skill, tools, OpenAPI spec, and operation status. |
write (what a managed key is provisioned with) |
Also start, stop, or restart it, and call its MCP tools (hub_call_app_tool) and its API with any method (hub_call_app_api). |
full |
Everything it may do on its own app. Only an organization owner or admin can give a key full. |
Below full, the rest is refused with APP_ACTION_GRANT_DENIED: installing the app; changing its
configuration (user config, ignored versions, custom-app compose or metadata, availability repair, and
app config); uninstalling, resetting, or updating it; cancelling its operation in progress; and taking,
restoring, or deleting a backup. Cancelling and taking a backup sit with full because both can lose
data: cancelling an install deletes what that install wrote to an app with no earlier data. Each
backup's retention cleanup deletes the app's oldest backups.
These are grant rules, and the key's capability still gates each tool on top of them, on its own app
too. At write the destructive tools stay refused: hub_call_app_tool; a hub_call_app_api call that
does more than read; uninstall, reset and update; restoring or deleting a backup; rewriting user config
or a custom app's compose; an install or config form that names a custom domain; and the bulk update,
stop and restart tools.
The bulk tools apply the same rule to each app. At full, start, stop, restart, and update all act on
every app; below full, the bulk start tool starts every app, and the bulk update tool updates only
the key's own app. If an agent must change other apps, give it an operator key created in
Settings → Security, which acts with the grants and role of the person who created it. A key from
cihub api-key create records no creator, so no one's grants bound it: it may act on every app, up to
its capability, though it cannot change which custom domain an app serves.
The tools that change the whole Hub, or read its log, take a key whose creator is an organization owner
or admin, or the operator who claimed the Hub, asked on every call. They are the model pull, load,
unload, pin and unpin tools, hub_set_cloud_provider, hub_set_auto_updates, hub_perform_update, the
app store add, update, delete and pull tools and hub_get_hub_logs. A managed key and a key from
cihub api-key create have no such person, so Hermes and OpenClaw don't see these tools. On a 2025-era
session their call gets HUB_MANAGE_KEY_REISSUE_REQUIRED. A key a member created gets
HUB_MANAGE_ROLE_REQUIRED there. A 2026-07-28 client never has these tools registered for such a key,
so its call fails as an unknown tool (Tool <name> not found).
Boot order (entrypoint.sh / gateway-entrypoint.sh):
bootstrap-from-hub.sh— pulls inference credentials, writes/opt/data/config.yamland.env.hub-bootstrap.bootstrap-ci-context.sh— Companion Intelligence plugin, memory MCP, then Hub MCP.configure-hub-mcp.py— idempotently addsmcp_servers.hubtoconfig.yaml:
mcp_servers:
hub:
url: "http://ci-hub:5002/api/mcp"
headers:
Authorization: "Bearer <HUB_MCP_API_KEY>"
timeout: 180
connect_timeout: 60There is deliberately no transport: line — omitting it leaves the Hermes agent on its default
Streamable HTTP client, which is what /api/mcp speaks. Hermes reaches Hub tools through that native
client; no hand-rolled protocol code is on the hot path.
Tests: ci-hermes/tests/configure-hub-mcp.test.sh, ci-hermes/tests/hub-mcp-smoke.sh
Boot order (entrypoint.sh):
bootstrap-from-hub.sh— inference + Ollama env.bootstrap-ci-memory.sh— CI-Server memory plugin.bootstrap-ci-hub-mcp.sh— registers bundled plugin path inopenclaw.jsonwhen creds present.server.cjs— on setup/onboarding,ensureOpenClawPlugin('ci-hub', …)enables the entry.
The bundled plugin lives at openclaw-context/plugins/ci-hub/:
index.js— OpenClaw entry; reads env only (no secrets inopenclaw.json).hub-plugin.mjs— bundled fromci-hub/packages/openclaw-plugin(pnpm bundlewithOPENCLAW_PLUGIN_OUT_DIR).
Plugin behavior:
- Exposes
POST /hooks/hub-wakefor Hub → agent wake notifications. - Optional SSE listener (off by default).
It does not register Hub tools or an inference provider. Tools load through OpenClaw's own MCP
client via the mcp.servers.ci-hub entry in openclaw.json; provider registration was removed
deliberately (CI-Hub#895) and must not be reintroduced.
Re-bundle after plugin source changes:
cd ci-hub/packages/openclaw-plugin
OPENCLAW_PLUGIN_OUT_DIR=/path/to/ci-openclaw/openclaw-context/plugins/ci-hub pnpm bundleThese are separate channels:
| Channel | Hermes | OpenClaw |
|---|---|---|
| Chat inference | OPENAI_API_* from Hub bootstrap |
ci-hub Ollama provider in openclaw.json |
| Memory MCP | CI-Server via configure-ci-server.py |
ci-memory plugin slot |
| Hub platform MCP | mcp_servers.hub in config.yaml |
ci-hub OpenClaw plugin |
Intent catalog plugins (companionintelligence) from upstream master are orthogonal — they add skills/intents, not Hub MCP wiring.
- MCP enabled (
cihub mcp setup) and anmcp-scoped key created (cihub api-key create) -
GET /api/healthOK -
scripts/qa-mcp-bridge.tsor e2epnpm e2e:mcppasses
- Install or restart
ci-hermesandci-openclawfrom marketplace - Container logs show Hub MCP registration (Hermes:
[Hub MCP] Server registered; OpenClaw:CI-Hub plugin initializing)
-
config.yamlcontainsmcp_servers.hubwith the/api/mcpURL, Bearer header, and notransport:line - Agent can list/call a Hub tool (e.g.
hub_get_inference_status)
-
openclaw.jsonhasplugins.entries.ci-hub.enabled: trueand anmcp.servers.ci-hubentry - Gateway logs show
Hub MCP tools served via native OpenClaw mcp.servers.ci-hub config -
POST /hooks/hub-wakeaccepts signed payloads when wake is configured
- Registration, onboarding, AI settings, app store install flows
- Hermes/OpenClaw install injects env (inspect app container env in Hub UI or
docker inspect)
See also: MCP_TESTING_STRATEGY.md