Integrating MCP with Static and Dynamic Servers in OpenHuman

OpenHuman's MCP subsystem unifies static TOML-declared servers and dynamic SQLite-backed Smithery installations under a single registry API, enabling seamless tool discovery and invocation across both stdio and HTTP transports.

OpenHuman provides a robust Modular Compute Provider (MCP) implementation that bridges local command-line tools and remote HTTP services through a unified interface. Whether you are configuring persistent servers in config.toml or installing ephemeral tools via the Smithery UI, the openhuman::mcp family of crates offers consistent APIs for tool discovery, authentication, and execution. This guide walks through the architecture, transport implementations, and integration patterns found in the tinyhumansai/openhuman repository.

MCP Architecture Overview

The openhuman::mcp family consists of five specialized members governed by feature gates. The root module at src/openhuman/mcp/mod.rs is always compiled and re-exports functionality from its submodules:

  • http_client – Ungated HTTP transport and MCP protocol implementation
  • config_servers – Leaf-gated (mcp feature) static server registry for stdio transport
  • registry – Leaf-gated (mcp feature) dynamic server registry backed by SQLite
  • audit – Leaf-gated (mcp feature) write-only activity logging
  • server – Leaf-gated (mcp feature) built-in stdio/HTTP server for debugging

Each gated member contains its own stub.rs file, ensuring that builds without the mcp feature still compile successfully.

Static Server Integration

Static servers are defined declaratively in the user's config.toml under the [[mcp_client.servers]] array. These definitions are parsed into McpServerDefinition structs and exposed through the McpServerRegistry.

Loading Static Configurations

The registry builder resides in src/openhuman/mcp/config_servers/registry.rs. It constructs a static registry directly from the loaded configuration:

use openhuman::mcp::config_servers::registry::McpServerRegistry;
use openhuman::config::Config;

let cfg = Config::load_or_init()?;
let registry = McpServerRegistry::from_config(&cfg)?;

Static Server Definition Schema

Each server entry in config.toml supports the following fields, which map to the McpServerConfig struct:

[[mcp_client.servers]]
name = "my-local-mcp"
command = "npx"
args = ["mcp-server"]
env = { NODE_ENV = "production" }
enabled = true
allowed_tools = ["gitbooks", "my_tool"]
auth = { bearer = "my-token" }

When the command field is non-empty, the registry instantiates an McpStdioClient (stdio transport). If command is empty, the entry uses the McpHttpClient (HTTP transport). Transport selection logic is implemented in src/openhuman/mcp/config_servers/mod.rs.

Environment Setup for Subprocesses

The stdio client uses src/openhuman/mcp/config_servers/spawn_env.rs to reconstruct the PATH environment variable. This ensures that version managers like npx and uvx remain discoverable even when the host GUI strips the user's PATH.

Dynamic Server Integration

Dynamic servers are installed through the Smithery UI and persist their metadata in a SQLite database (mcp_clients.db). The registry member loads this data and exposes it through the same McpServerRegistry interface used for static servers.

Loading Dynamic Registries

Located in src/openhuman/mcp/registry/mod.rs, the dynamic registry builder queries the SQLite store:

use openhuman::mcp::registry::McpServerRegistry;
use openhuman::config::Config;

let cfg = Config::load_or_init()?;
let dynamic_registry = McpServerRegistry::from_dynamic_store(&cfg)?;

The helper functions for database operations reside in src/openhuman/mcp/registry/helpers.rs.

Unified API Surface

Both static and dynamic registries expose identical public methods: list, get, list_tools, and call_tool. This allows the rest of the OpenHuman core to treat them uniformly regardless of origin.

Transport Implementations

HTTP Transport

The McpHttpClient in src/openhuman/mcp/http_client/client.rs implements the streamable HTTP transport. It performs an initialize handshake that negotiates the MCP protocol version (latest: 2025-11-25), supports SSE-based event draining, and manages session lifecycle via the Mcp-Session-Id header.

For authentication, the client handles OAuth discovery by parsing WWW-Authenticate challenges on 401 responses and following the OIDC .well-known flow.

let http = McpHttpClient::new(endpoint, timeout_secs);
let init = http.initialize().await?;
let tools = http.list_tools().await?;
let result = http.call_tool("gitbooks", payload).await?;

Stdio Transport

The McpStdioClient in src/openhuman/mcp/config_servers/stdio.rs spawns subprocesses (e.g., npx mcp-server) and communicates via JSON-RPC over stdin/stdout. It maintains a single long-lived session guarded by a tokio::Mutex.

let stdio = McpStdioClient::new(command, args, env, cwd, identity);
let init = stdio.initialize().await?;
let result = stdio.call_tool("my_tool", payload).await?;

Agent Tool Integration

MCP-related agent tools (mcp_list_servers, mcp_list_tools, mcp_call_tool) are implemented in src/openhuman/tools/impl/network/mcp.rs. These tools retrieve the registry via a global accessor pattern:

use openhuman::mcp::registry::McpServerRegistry;

let registry = McpServerRegistry::global();
let tools = registry.list_tools(server_name).await?;

Before any network or subprocess I/O occurs, the tools validate calls against per-server allowed_tools and disallowed_tools lists. This check is fail-closed and enforced at line 106 of the implementation.

Front-End React Integration

The TypeScript implementation resides under app/src/lib/mcp/ and provides:

  • Transport abstraction (transport.ts) – Mirrors the Rust client APIs for browser contexts
  • Validation utilities (validation.ts) – Ensures JSON payloads conform to MCP schemas before transmission
  • Rate limiting (rateLimiter.ts) – Prevents server overload

Component Usage

import { McpHttpClient } from '@/lib/mcp/transport';
import { validateMcpPayload } from '@/lib/mcp/validation';

const client = new McpHttpClient('https://my-mcp.example.com', 30);
const payload = { path: '/docs' };

if (validateMcpPayload(payload)) {
  const result = await client.callTool('gitbooks', payload);
}

The McpToolPlayground.tsx component in app/src/components/channels/mcp/ provides an interactive UI for testing both static and dynamic servers. Server representations are rendered by McpServerCard.tsx, while InstallDialog.tsx handles Smithery installations.

Security and Logging

All transport implementations use tracing::debug and tracing::info macros for structured logging. The redact_endpoint helper in the HTTP client (defined in src/openhuman/mcp/http_client/client.rs) strips user-info from URLs before logging, replacing credentials with <redacted>. The stdio client applies the same redaction logic when logging spawned command lines.

Summary

  • OpenHuman's MCP architecture separates concerns into five gated members under openhuman::mcp, with http_client always available and the rest gated behind the mcp feature.
  • Static servers are configured in config.toml and managed by config_servers/registry.rs, supporting both stdio and HTTP transports.
  • Dynamic servers are stored in SQLite and loaded via registry/mod.rs, exposing the same API as static registries.
  • The HTTP transport handles protocol negotiation, OAuth discovery, and SSE streaming in http_client/client.rs.
  • The stdio transport spawns subprocesses with reconstructed PATH environments via config_servers/stdio.rs and spawn_env.rs.
  • Agent tools access the registry through a global singleton in tools/impl/network/mcp.rs, with fail-closed validation on tool allow-lists.
  • The React front-end provides type-safe transport wrappers, validation, and UI components for interactive MCP tool usage.

Frequently Asked Questions

How does OpenHuman decide between stdio and HTTP transport for a static server?

OpenHuman inspects the command field in the [[mcp_client.servers]] configuration entry. If command is non-empty, it instantiates McpStdioClient from src/openhuman/mcp/config_servers/stdio.rs. If command is empty, it uses McpHttpClient from src/openhuman/mcp/http_client/client.rs. This logic is centralized in src/openhuman/mcp/config_servers/mod.rs.

Can I use both static and dynamic servers simultaneously in the same session?

Yes. The McpServerRegistry supports merging static and dynamic sources. You can load static definitions via McpServerRegistry::from_config and dynamic definitions via McpServerRegistry::from_dynamic_store. Both expose identical APIs (list_tools, call_tool, etc.), allowing seamless switching between TOML-declared servers and Smithery-installed tools.

How does OpenHuman handle authentication tokens for MCP servers?

Authentication is configured per-server in config.toml using the auth field (e.g., auth = { bearer = "token" }). For HTTP servers, the McpHttpClient supports OAuth discovery by parsing WWW-Authenticate headers and following OIDC flows. All credentials are redacted from logs via the redact_endpoint helper in src/openhuman/mcp/http_client/client.rs to prevent leakage in debug output.

What happens if the PATH environment is missing when spawning a stdio server?

The McpStdioClient uses src/openhuman/mcp/config_servers/spawn_env.rs to reconstruct the PATH environment variable before spawning subprocesses. This ensures that binaries managed by version managers like npx, uvx, or pnpm remain discoverable even when the hosting environment (such as a GUI application) provides a stripped PATH.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →