From 61ab68f5c904050fce61f32a5c6731edb1a8ea92 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 18:25:25 +0100 Subject: [PATCH] refactor: share MCP tools stdio server --- src/mcp/openclaw-tools-serve.ts | 46 +++---------------------------- src/mcp/plugin-tools-serve.ts | 46 +++---------------------------- src/mcp/tools-stdio-server.ts | 48 +++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 86 deletions(-) create mode 100644 src/mcp/tools-stdio-server.ts diff --git a/src/mcp/openclaw-tools-serve.ts b/src/mcp/openclaw-tools-serve.ts index ef2496f5c62..6f2a2659f6b 100644 --- a/src/mcp/openclaw-tools-serve.ts +++ b/src/mcp/openclaw-tools-serve.ts @@ -6,14 +6,10 @@ */ import { pathToFileURL } from "node:url"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import type { AnyAgentTool } from "../agents/tools/common.js"; import { createCronTool } from "../agents/tools/cron-tool.js"; import { formatErrorMessage } from "../infra/errors.js"; -import { routeLogsToStderr } from "../logging/console.js"; -import { VERSION } from "../version.js"; -import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js"; +import { connectToolsMcpServerToStdio, createToolsMcpServer } from "./tools-stdio-server.js"; export function resolveOpenClawToolsForMcp(): AnyAgentTool[] { return [createCronTool()]; @@ -25,48 +21,12 @@ export function createOpenClawToolsMcpServer( } = {}, ): Server { const tools = params.tools ?? resolveOpenClawToolsForMcp(); - const handlers = createPluginToolsMcpHandlers(tools); - - const server = new Server( - { name: "openclaw-tools", version: VERSION }, - { capabilities: { tools: {} } }, - ); - - server.setRequestHandler(ListToolsRequestSchema, handlers.listTools); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - return await handlers.callTool(request.params); - }); - - return server; + return createToolsMcpServer({ name: "openclaw-tools", tools }); } export async function serveOpenClawToolsMcp(): Promise { - // MCP stdio requires stdout to stay protocol-only. - routeLogsToStderr(); - const server = createOpenClawToolsMcpServer(); - const transport = new StdioServerTransport(); - - let shuttingDown = false; - const shutdown = () => { - if (shuttingDown) { - return; - } - shuttingDown = true; - process.stdin.off("end", shutdown); - process.stdin.off("close", shutdown); - process.off("SIGINT", shutdown); - process.off("SIGTERM", shutdown); - void server.close(); - }; - - process.stdin.once("end", shutdown); - process.stdin.once("close", shutdown); - process.once("SIGINT", shutdown); - process.once("SIGTERM", shutdown); - - await server.connect(transport); + await connectToolsMcpServerToStdio(server); } if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { diff --git a/src/mcp/plugin-tools-serve.ts b/src/mcp/plugin-tools-serve.ts index 8303998b2e1..f3e108b7e8f 100644 --- a/src/mcp/plugin-tools-serve.ts +++ b/src/mcp/plugin-tools-serve.ts @@ -8,16 +8,12 @@ */ import { pathToFileURL } from "node:url"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import type { AnyAgentTool } from "../agents/tools/common.js"; import { loadConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { formatErrorMessage } from "../infra/errors.js"; -import { routeLogsToStderr } from "../logging/console.js"; import { resolvePluginTools } from "../plugins/tools.js"; -import { VERSION } from "../version.js"; -import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js"; +import { connectToolsMcpServerToStdio, createToolsMcpServer } from "./tools-stdio-server.js"; function resolveTools(config: OpenClawConfig): AnyAgentTool[] { return resolvePluginTools({ @@ -34,26 +30,10 @@ export function createPluginToolsMcpServer( ): Server { const cfg = params.config ?? loadConfig(); const tools = params.tools ?? resolveTools(cfg); - const handlers = createPluginToolsMcpHandlers(tools); - - const server = new Server( - { name: "openclaw-plugin-tools", version: VERSION }, - { capabilities: { tools: {} } }, - ); - - server.setRequestHandler(ListToolsRequestSchema, handlers.listTools); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - return await handlers.callTool(request.params); - }); - - return server; + return createToolsMcpServer({ name: "openclaw-plugin-tools", tools }); } export async function servePluginToolsMcp(): Promise { - // MCP stdio requires stdout to stay protocol-only. - routeLogsToStderr(); - const config = loadConfig(); const tools = resolveTools(config); const server = createPluginToolsMcpServer({ config, tools }); @@ -61,27 +41,7 @@ export async function servePluginToolsMcp(): Promise { process.stderr.write("plugin-tools-serve: no plugin tools found\n"); } - const transport = new StdioServerTransport(); - - let shuttingDown = false; - const shutdown = () => { - if (shuttingDown) { - return; - } - shuttingDown = true; - process.stdin.off("end", shutdown); - process.stdin.off("close", shutdown); - process.off("SIGINT", shutdown); - process.off("SIGTERM", shutdown); - void server.close(); - }; - - process.stdin.once("end", shutdown); - process.stdin.once("close", shutdown); - process.once("SIGINT", shutdown); - process.once("SIGTERM", shutdown); - - await server.connect(transport); + await connectToolsMcpServerToStdio(server); } if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { diff --git a/src/mcp/tools-stdio-server.ts b/src/mcp/tools-stdio-server.ts new file mode 100644 index 00000000000..7ad96cd4e77 --- /dev/null +++ b/src/mcp/tools-stdio-server.ts @@ -0,0 +1,48 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; +import type { AnyAgentTool } from "../agents/tools/common.js"; +import { routeLogsToStderr } from "../logging/console.js"; +import { VERSION } from "../version.js"; +import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js"; + +export function createToolsMcpServer(params: { name: string; tools: AnyAgentTool[] }): Server { + const handlers = createPluginToolsMcpHandlers(params.tools); + const server = new Server( + { name: params.name, version: VERSION }, + { capabilities: { tools: {} } }, + ); + + server.setRequestHandler(ListToolsRequestSchema, handlers.listTools); + server.setRequestHandler(CallToolRequestSchema, async (request) => { + return await handlers.callTool(request.params); + }); + + return server; +} + +export async function connectToolsMcpServerToStdio(server: Server): Promise { + // MCP stdio requires stdout to stay protocol-only. + routeLogsToStderr(); + + const transport = new StdioServerTransport(); + let shuttingDown = false; + const shutdown = () => { + if (shuttingDown) { + return; + } + shuttingDown = true; + process.stdin.off("end", shutdown); + process.stdin.off("close", shutdown); + process.off("SIGINT", shutdown); + process.off("SIGTERM", shutdown); + void server.close(); + }; + + process.stdin.once("end", shutdown); + process.stdin.once("close", shutdown); + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); + + await server.connect(transport); +}