Skip to main content

Hosting MCP servers

This page explains where an MCP server runs once it is used by the agents of a project, how the platform isolates it per project, and how to start, stop, upgrade and delete it. It is written for developers and tenant administrators; the installation of the hosting services themselves belongs to the operators and is linked at the end.

Three ways to host a server​

The MCP gateway of the Control Plane reaches internal addresses only (ADR-0170). Every server is therefore hosted in one of three places.

HostingWhat runsWho operates itIsolationEntry in the gateway
Internal host of the companyAn HTTP MCP server the company develops and runsThe team that owns the serverThe host named in EAI_MCP_INTERNAL_HOSTS (exact host, host:port, *.suffix or CIDR)Registered with POST /v1/mcp/servers, attached to a project or shared by the tenant
Navin Manager, service mcp-hostA stdio command (npx, uvx) exposed over streamable HTTPThe tenant administrator, from the MCP page of the Navin ManagerOne child process per server and per project, clean environment, own folder in mode 0700, own tokenPublished by POST /v1/mcp/hosted/sync in its project
MCP Runtime (ToolHive Operator)A container image of the company MCP catalogueEach user, from the portalOne instance per user, project and template, in a namespace per projectCreated when the instance is ready, callable by its owner and its agents only

The MCP Runtime is described in MCP Runtime per user and project. This page covers the first two and the rules shared by all three.

note

An online MCP service is never called. A preset of the Navin library marked hosting: online (portal badge "Online service: internal address required") is installed only with the internal address where the company hosts it.

Isolation per project​

A server attached to a project is listed and called only from that project (ADR-0165):

  • The project of a request is the X-Project header, or the project an API key is bound to.
  • From another project, GET /v1/mcp/servers/{server_id}/tools, the discovery, the status, the catalogue and the deletion answer 403 mcp_server_project_mismatch, and the gateway answers a JSON-RPC error with the same code, without calling the server. Every refusal is audited.
  • A request without a project context is refused (project_context_required), except for tenant_admin and security_officer, who keep the view of every project.
  • A server without a project is shared by the tenant and visible from every project.
  • Inside a project, PUT /v1/mcp/servers/{server_id}/access narrows the callers to a list of users and agents.
RequestServers listed by GET /v1/mcp/servers
With a project contextThe servers of that project and the servers shared by the tenant
With ?project=<id-or-slug>The servers of that project only (403 mcp_project_forbidden when it is not the request project)
Tenant wide (tenant_admin, security_officer)Every server of the tenant

Host a server on an internal host​

  1. Deploy the server inside the network of the company. Use the streamable HTTP or the SSE transport and an address whose host the operator lists in EAI_MCP_INTERNAL_HOSTS. Every address the name resolves to must be private: a name that resolves to a public address is refused at connect time.

    Expected result: the server answers initialize from the network of the Control Plane.

    If it fails: 403 mcp_online_forbidden at registration or call; ask the operator to list the internal host.

  2. Register it in its project. Follow MCP Development Guide: POST /v1/mcp/servers with project and an openbao:// credential reference.

    Expected result: 201, the server is listed in its project only.

    If it fails: 403 mcp_project_forbidden when the body names a project other than the request project.

  3. Discover and approve its tools. POST /v1/mcp/servers/{server_id}/discover, then approve the versions.

    Expected result: GET /v1/mcp/catalog shows the tools as approved.

    If it fails: 502 upstream_error; check that the server answers tools/list.

Host a stdio server in the Navin Manager​

The service mcp-host of the Navin Manager (runtime navin-mcp-host, navin-manager/mcp-host) starts each stdio server of a project in its own child process, without a shell, with memory, file and core limits, stops it after its idle time and restarts it after a crash (failed beyond 5 crashes per minute). Each server is exposed at /t/{tenant}/p/{project}/s/{name}/mcp with its own token; the host keeps only the sha256 digest of the token and answers the same 401 to any wrong token. It starts the servers offline (npm, uv and pip without download).

  1. Install the service mcp-host (operator). The operator installs it from the catalogue of the Navin Manager, in single or replicated mode, and lists the address of its instances in EAI_EGRESS_PRIVATE_ALLOWLIST of the Control Plane. See MCP hosting service.

    Expected result: the preflight checks (npx, uvx) pass and the service is running.

    If it fails: follow the troubleshooting of the service page.

  2. Host the server of the project. On the MCP page of the Navin Manager, choose the tenant, the project and the preset or the command, enter the secrets (sealed by the Manager) and host it.

    Expected result: the Manager writes servers.json on the instance, the runtime reloads it and the server answers.

    If it fails: the connection test of the page shows the error of the child process.

  3. Publish it in the project. In the portal, Navin library > MCP > Sync the servers hosted by the Navin Manager (tenant_admin), or POST /v1/mcp/hosted/sync with ?project=<slug>.

    client.mcp.sync_hosted(project="alpha")

    Expected result: the answer lists created, updated, disabled and skipped. The token of each server is kept in the Credential Broker at openbao://mcp/hosted/<id>, never in a table nor an answer; the server of the project gets a bearer authentication, its allowed tools and its hosted_ref.

    If it fails: skipped gives a reason per entry (other_tenant, invalid_id, invalid_name, invalid_url, token_missing, project_not_found, not_active, project_moved); 403 mcp_project_forbidden when project differs from the request project.

  4. Discover and approve its tools. As for any server.

    Expected result: the agents of the project call it through the gateway.

    If it fails: see MCP Development Guide.

Lifecycle​

OperationInternal hostNavin Manager (mcp-host)MCP Runtime
StartThe owner team starts the server; register it onceHost it on the MCP page of the Manager, then syncPOST /v1/projects/{project_id}/mcp/instances, then any call wakes it
Stop for a whilePOST /v1/mcp/servers/{server_id}/status disabled (applied at once, audited)Remove or disable it in the Manager, then sync: the server is disabledPOST /v1/projects/{project_id}/mcp/instances/{instance_id}/stop
Start againPOST /v1/mcp/servers/{server_id}/status active (promotion request, 202)Host it again, then sync: the server becomes activePOST /v1/projects/{project_id}/mcp/instances/{instance_id}/start, or the next call
UpgradeDeploy the new build at the same URL, then discover againChange the command or the version in the Manager (the process restarts, sessions are invalidated), sync, discover againPublish a new image in the company catalogue; the next generation takes it
Renew the credentialWrite the new secret at the same openbao:// reference"..." menu of the Manager, renew the token, then syncEvery generation gets new credentials
DeleteDELETE /v1/mcp/servers/{server_id} (tenant_admin)Remove it in the Manager, then sync (the entry is disabled)DELETE /v1/projects/{project_id}/mcp/instances/{instance_id}
warning

Disabling a server cuts every agent of the project that uses it. Enabling it again goes through a promotion request that a second person approves.

Screens and routes​

ScreenWhereRoutes
MCP studioPortal, studio/mcpPOST /v1/mcp/servers, POST /v1/mcp/servers/{server_id}/discover, GET /v1/mcp/catalog
MCP serversPortal, capabilities/mcpGET /v1/mcp/servers, GET /v1/mcp/servers/{server_id}/tools, POST /v1/mcp/servers/{server_id}/status, DELETE /v1/mcp/servers/{server_id}, POST /v1/mcp/gateway/{server_id}
Navin library > MCPPortal, studio/navin-libraryPOST /v1/projects/{project_id}/navin-library/mcp-presets/{name}/install, POST /v1/mcp/hosted/sync, PUT /v1/mcp/servers/{server_id}/access
Navin library > MCP instancesPortal, studio/navin-libraryGET /v1/projects/{project_id}/mcp/instances and the instance routes
MCP pageNavin ManagerHosting, token renewal and connection test of the stdio servers
MCP runtime pageNavin ManagerOperator state, instances, destruction and restart

Verify​

  • GET /v1/mcp/servers sent with the X-Project of the project lists the server; sent with another project it does not.
  • A call from another project answers the JSON-RPC error mcp_server_project_mismatch and the audit holds the refusal.

Next steps​

Troubleshooting​

SymptomCauseFix
403 mcp_online_forbiddenHost public or not listed as internalHost the server internally and list its host
403 mcp_server_project_mismatchCall from another project or without a project contextSend the X-Project of the project of the server
Sync answers skipped with project_not_foundThe project slug of the Manager does not exist in the tenantCreate the project or host the server in an existing project
Sync answers skipped with project_movedThe hosted server was published in another project beforeRemove it in the Manager and host it again in the right project
Hosted server disabled after a syncThe Manager no longer hosts itHost it again, then sync
409 mcp_preset_needs_endpoint at preset installationstdio preset without HTTP addressHost it in the Navin Manager, then give its URL or sync