Skip to content

apomesh-substrate-mcp

apomesh-substrate-mcp is a substrate implementation plane: the rmcp-backed adapter that turns an operator-configured MCP server’s tools into first-class substrate Tool instances. From the worker’s dispatch path, an MCP-backed tool is indistinguishable from a native one — same Tool trait, same ToolSandbox::execute wrap, same cancellation contract. This crate owns the transport (how the substrate connects to an MCP server), the tool projection (how a discovered MCP tool becomes a substrate Tool), and the sandboxed lifecycle of a spawned server. Touch it for MCP transport mechanics: a new transport variant, the connection pool’s lifecycle, or the discovered-tool adapter.

  • Layer: substrate implementation plane. See Control & execution planes.
  • Depends on: apomesh-substrate-types only (the Tool / ToolSandbox / RuntimeLayerStore ports plus the agent/tenant/tool/cancellation vocabulary), and the rmcp transport tree — stdio child-process always, streamable-HTTP behind the mcp-http feature.
  • Consumed by: apomesh-substrate, which pulls this plane in behind its optional mcp cargo feature and re-exports the surface flat through apomesh_substrate::tool. Deployments that wire no MCP servers don’t compile rmcp at all. The orchestrator and worker reach the plane only through that facade.
  • The transport vocabularyMcpTransport, a closed enum of Stdio (the substrate spawns the server as a subprocess and speaks JSON-RPC over its pipes) and Http (streamable-HTTP per the MCP 2025-11-25 spec, gated on mcp-http). One transport per McpServerSpec.
  • The tool projectionMcpServerConnection::connect runs the initialize handshake and caches the tools/list advertisement; register_mcp_tools builds one McpTool per discovered tool (filtered by the spec’s optional permitted_tools allow-list), prefixing its ToolId as <server>:<tool> for collision-free provenance. McpTool implements the substrate Tool trait — surfacing the MCP-side name, description, and input schema, and routing dispatch through a tools/call round-trip.
  • The sandboxed server lifecycle — a stdio server is spawned as a long-lived sandboxed subprocess via ToolSandbox::spawn_long_lived under the SideEffectingTool policy; the connection holds the kill lifeline, so dropping it terminates the sandbox. HTTP servers are remote (no subprocess).
  • Per-tenant spec catalog + connection poolMcpServerStore (a plugin trait with an in-memory adapter here; the Postgres adapter lives in the state-store crate) and McpConnectionPool, which lazy-spawns, refcounts, and idle-evicts one connection per (tenant, server).
  • The runtime-layer adapterLocalDiskRuntimeLayerStore plus the substrate_shipped_seeds catalog, which verify host-provisioned language runtimes for runtime-bound stdio servers (bind-mounted at /runtime/<id>).

It does not own server registration/admin or session-start admission — the operator-facing list/register/update/remove/probe workflows and the admit_session_mcp call live in the orchestrator. It does not own at-rest secret sealing — the secret-bearing transport fields (Stdio.env, Http.auth_header) are sealed by apomesh-state-store-postgres’s PostgresMcpServerStore, never by this crate. And the worker-side delegate stub (McpDelegateTool) lives in apomesh-substrate outside the mcp gate, so the worker never compiles the rmcp transport tree.

  • McpTransport — closed-enum transport selection: Stdio { command, args, env } | Http { url, auth_header }.
  • McpServerSpec — per-server registration input (name, transport, ToolCategory, optional allow-list, optional runtime layer); the substrate mirror of config’s McpServerConfig.
  • register_mcp_tools — the worker-boot driver: connect each server, discover, and register its McpTools into a ToolRegistry; a failing server is logged and skipped, never fatal.
  • McpServerConnection — one live server connection; connect does the sandboxed handshake + tools/list, invoke_tool does the per-call tools/call.
  • absorb_mcp_server — projects a legacy McpServerSpec onto the one connector declaration. The per-tenant McpServerStore this crate used to own is gone: it was a second sealed secret home beside the one per-tenant CredentialStore, and the connector plane’s registry replaced it. A stored row’s inline credential values are lifted to names under the same move.
  • McpConnectionPool — per-(tenant, server) lifecycle: acquire (lazy-spawn + refcount), probe (non-disruptive health check), evict_idle.
  • LocalDiskRuntimeLayerStore + substrate_shipped_seeds — the runtime-layer adapter (verifies host-provisioned runtimes; does not download them) and its seed catalog.
  • McpError — the closed failure vocabularies, mapped to ToolError on dispatch and to a gRPC Status at the admin wire boundary.

In a running daemon the orchestrator holds an Arc<dyn McpServerStore> and an Arc<McpConnectionPool>. An operator registers a server for their tenant through the console’s MCP surface (or the equivalent admin RPC), which upserts an McpServerSpec into the store. At session start, create_session_workflow calls admit_session_mcp, which list_for_tenants the registered specs and acquires a pooled connection per server — lazy-spawning the sandboxed subprocess on first use. The discovered tools project into the session’s tool set as McpTools, and the worker dispatches them exactly like native tools: tier-validated, sandbox-wrapped, and cancellable by future-drop. The MCP server process stays alive across dispatches (one persistent transport); only the tools/call round-trip is per-dispatch, and the pool tears down the subprocess once the connection has been idle past its window.

Register and inspect servers from the operator console; transport-config secrets are sealed at rest by the Postgres store, never written in cleartext.