Skip to main content

ADR-0165: MCP servers hosted in the Navin Manager and isolation per project of the MCP gateway

Context​

Most MCP servers of the Navin library are local commands (stdio: npx, uvx). The MCP gateway of the Control Plane only speaks the streamable HTTP transport: an infrastructure must host those commands, expose them over HTTP and isolate them per tenant and project. In the platform, an MCP server attached to a project also had to be usable from that project only.

Decision​

  1. Runtime navin-mcp-host (navin-manager/mcp-host, Python standard library only): one child process per server, started without a shell, clean environment (nothing inherited from the host), working folder <state_dir>/<tenant>/<project>/<name> in 0700, memory, file and core limits, stop after idle time, restart after a crash and failing state beyond 5 crashes per minute. Each server is exposed over streamable HTTP at /t/{tenant}/p/{project}/s/{name}/mcp with its own token (only the sha256 digest is on the host); the token of another server or project gets 401 with no hint about the server. The allowed tools are enforced on the host too. /healthz and /metrics (Prometheus) serve monitoring.
  2. Catalogue service mcp-host of the Manager: installation, single and replicated modes (a server is placed on one instance by its id), preflight checks (npx, uvx). The MCP page of the Manager hosts, removes and renews the token of a stdio server of a project ("..." menu); the Manager writes servers.json on the instance, reloaded live by the runtime.
  3. Publication: the Manager lists the hosted servers of a tenant (permission mcp:publish, admin role only). POST /v1/mcp/hosted/sync of the Control Plane publishes them in their project: token kept in the Credential Broker (openbao://mcp/hosted/<id>), server of the project created or updated (hosted_ref, V198), servers no longer hosted disabled. No answer and no audit event holds a token. The address of the mcp-host instances must be named in EAI_EGRESS_PRIVATE_ALLOWLIST.
  4. Isolation per project in the MCP gateway: a server attached to a project is listed and called only from that project (X-Project header or an API key bound to the project). From another project the gateway answers a JSON-RPC error mcp_server_project_mismatch with no upstream call, and the tools, discovery, status, catalogue and delete routes answer 403; every refusal is audited. A server without a project stays shared by the tenant; tenant_admin and security_officer without a project context keep the view of every project.

Alternatives considered​

  • Third party proxies (mcp-proxy, supergateway): one shared process with no clean environment nor token per server, and one more dependency to maintain on the host.
  • Running the stdio commands in the Control Plane: would mix third party code with the process that holds the authorisations of the platform.

Consequences​

A stdio server of a project becomes usable by the agents of that project only, without a secret leaving the Manager or the Credential Broker. The Manager must be linked to the platform (EAI_PLATFORM_MANAGER_URL, EAI_PLATFORM_MANAGER_TOKEN_REF).

Evidence​

navin-manager/mcp-host/tests, navin-manager/server/tests/test_mcp_hosting.py, services/control-plane/tests/test_mcp_project_isolation.py, services/control-plane/tests/test_mcp_hosted_sync.py, portal/tests/navin-library.test.ts.