How MCP server tools are exposed to the agent tool registry
MCP Bridge — Model Context Protocol Tool Bridging
MCP server discovery, configuration extraction, and tool serving for bundled plugins
6 Core Files 3 Server Formats ~800 LOC TypeScript
🔵 MCP Protocol 🟢 Stdio Servers 🟡 Config Merging 🔴 Runtime Support
📚 What is this & why does it matter?
The MCP Bridge sub-module connects OpenClaw’s plugin system with the Model Context Protocol (MCP). MCP defines a standard for AI assistants to discover and invoke tools from external servers. When a bundled plugin declares MCP servers in its manifest or .mcp.json file, this module extracts the server configurations, resolves paths, and determines runtime support. The bridge enables plugins originally built for Claude, Codex, or Cursor to expose their MCP tools to OpenClaw’s agent system.
💡 Real-world analogy
Think of the MCP Bridge as a USB hub that translates different device protocols into a universal interface. Each MCP server is a USB device with its own communication protocol. The bridge reads the device descriptor (server config), checks if the host supports it (stdio server check), and exposes the device’s capabilities (tools) through a standard API that the agent can call just like any other tool.
Contents
Architecture
flowchart TD
subgraph Config["Configuration Sources"]
MCPJson[".mcp.json files"]
BundleManifest["Bundle Manifest"]
MCPDeclared["Declared mcpServers paths"]
end
subgraph Extract["Config Extraction"]
Resolve["resolveBundleMcpConfigPaths()"]
Read["readBundleJsonObject()"]
ExtractMap["extractMcpServerMap()"]
MergePatch["applyMergePatch()"]
end
subgraph Runtime["Runtime Support"]
Inspect["inspectBundleMcpRuntimeSupport()"]
StdioCheck["hasSupportedStdioServer"]
ServerNames["supported/unsupported names"]
Diagnostics["diagnostic messages"]
end
subgraph Serve["Tool Serving"]
MCPConfig["BundleMcpConfig"]
ToolFactory["Plugin Tool Factory"]
AgentTools["Agent Tool Registry"]
end
MCPJson --> Resolve
BundleManifest --> Resolve
MCPDeclared --> Resolve
Resolve --> Read
Read --> ExtractMap
ExtractMap --> MergePatch
MergePatch --> MCPConfig
MCPConfig --> Inspect
Inspect --> StdioCheck
Inspect --> ServerNames
Inspect --> Diagnostics
MCPConfig --> ToolFactory
ToolFactory --> AgentTools
Configuration flows from three sources (.mcp.json files, bundle manifests, and declared server paths) through extraction and merging, then into runtime support inspection and tool serving. The bridge resolves all config paths, extracts server maps, applies merge patches, and finally exposes validated tools to the agent registry.
Key Types
| Name | File | Type | Description |
|---|---|---|---|
BundleMcpServerConfig | bundle-mcp.ts | Type | Arbitrary MCP server configuration (Record<string, unknown>) |
BundleMcpConfig | bundle-mcp.ts | Type | Collection of MCP servers keyed by name |
BundleMcpDiagnostic | bundle-mcp.ts | Type | Diagnostic message with pluginId and message string |
EnabledBundleMcpConfigResult | bundle-mcp.ts | Type | Loaded config with diagnostics array |
BundleMcpRuntimeSupport | bundle-mcp.ts | Type | Runtime support check: supported stdio servers, diagnostics |
PluginBundleFormat | types.ts | Union | ‘claude’ | ‘codex’ | ‘cursor’ bundle format |
MCP Config Resolution
The resolution process loads, merges, and validates MCP server configurations from multiple sources, replacing placeholders and accumulating diagnostics along the way:
sequenceDiagram
participant Loader as Plugin Loader
participant MCP as bundle-mcp.ts
participant FS as File System
participant Shared as bundle-config-shared.ts
Loader->>MCP: loadEnabledBundleMcpConfig(rootDir, config)
MCP->>MCP: resolveBundleMcpConfigPaths(raw, rootDir, format)
MCP->>FS: Check .mcp.json exists
MCP->>MCP: mergeBundlePathLists(defaults, declared)
loop Each config path
MCP->>Shared: readBundleJsonObject(path)
Shared->>FS: openBoundaryFileSync(path)
FS-->>Shared: file contents
Shared-->>MCP: parsed JSON
MCP->>MCP: extractMcpServerMap(raw)
MCP->>MCP: Replace ${CLAUDE_PLUGIN_ROOT} placeholders
end
MCP->>MCP: applyMergePatch(accumulated, newConfig)
MCP-->>Loader: EnabledBundleMcpConfigResult
Loader->>MCP: inspectBundleMcpRuntimeSupport(config)
MCP->>MCP: Check for stdio transport
MCP-->>Loader: BundleMcpRuntimeSupport
${CLAUDE_PLUGIN_ROOT} placeholder in server configs is replaced with the actual bundle root directory at resolution time. This allows .mcp.json files to use relative paths that resolve correctly regardless of where the bundle is installed.
Deep-Dive Highlights
Bridging channel-specific plugins with MCP tool capabilities
Tool resolution, allowlists, and conflict management
Directory Map
Source files for MCP Bridge
📁 src/plugins/ (MCP-related files)
├── bundle-mcp.ts # MCP server config extraction & merging
├── bundle-config-shared.ts # Shared bundle config utilities
├── bundle-manifest.ts # Bundle manifest (MCP paths declared here)
├── tools.ts # Plugin tool resolution & serving
├── capability-provider-runtime.ts # Capability-level provider runtime
├── slots.ts # Plugin slot assignment
├── bundled-sources.ts # Bundled plugin source resolution
└── runtime/
└── gateway-request-scope.ts # Gateway request scope for plugin tools
Code Examples
Extract and iterate over MCP server configurations from a bundle’s .mcp.json file:
// Extracting MCP server configuration from a bundle
import { extractMcpServerMap } from "./bundle-mcp.js";
const raw = JSON.parse(fs.readFileSync(".mcp.json", "utf-8"));
const servers = extractMcpServerMap(raw);
// servers = {
// "filesystem": { command: "npx", args: ["-y", "@mcp/filesystem", "/path"] },
// "github": { command: "npx", args: ["-y", "@mcp/github"], env: { GITHUB_TOKEN: "..." } }
// }
for (const [name, config] of Object.entries(servers)) {
console.log(`MCP Server: ${name}`, config);
}