Files
openclaw/src/mcp/channel-server.ts
2026-03-28 04:58:34 +00:00

103 lines
3.1 KiB
TypeScript

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { loadConfig, type OpenClawConfig } from "../config/config.js";
import { VERSION } from "../version.js";
import { OpenClawChannelBridge } from "./channel-bridge.js";
import { ClaudePermissionRequestSchema, type ClaudeChannelMode } from "./channel-shared.js";
import { getChannelMcpCapabilities, registerChannelMcpTools } from "./channel-tools.js";
export { OpenClawChannelBridge } from "./channel-bridge.js";
export type OpenClawMcpServeOptions = {
gatewayUrl?: string;
gatewayToken?: string;
gatewayPassword?: string;
config?: OpenClawConfig;
claudeChannelMode?: ClaudeChannelMode;
verbose?: boolean;
};
export async function createOpenClawChannelMcpServer(opts: OpenClawMcpServeOptions = {}): Promise<{
server: McpServer;
bridge: OpenClawChannelBridge;
start: () => Promise<void>;
close: () => Promise<void>;
}> {
const cfg = opts.config ?? loadConfig();
const claudeChannelMode = opts.claudeChannelMode ?? "auto";
const capabilities = getChannelMcpCapabilities(claudeChannelMode);
const server = new McpServer(
{ name: "openclaw", version: VERSION },
capabilities ? { capabilities } : undefined,
);
const bridge = new OpenClawChannelBridge(cfg, {
gatewayUrl: opts.gatewayUrl,
gatewayToken: opts.gatewayToken,
gatewayPassword: opts.gatewayPassword,
claudeChannelMode,
verbose: opts.verbose ?? false,
});
bridge.setServer(server);
server.server.setNotificationHandler(ClaudePermissionRequestSchema, async ({ params }) => {
await bridge.handleClaudePermissionRequest({
requestId: params.request_id,
toolName: params.tool_name,
description: params.description,
inputPreview: params.input_preview,
});
});
registerChannelMcpTools(server, bridge);
return {
server,
bridge,
start: async () => {
await bridge.start();
},
close: async () => {
await bridge.close();
await server.close();
},
};
}
export async function serveOpenClawChannelMcp(opts: OpenClawMcpServeOptions = {}): Promise<void> {
const { server, start, close } = await createOpenClawChannelMcpServer(opts);
const transport = new StdioServerTransport();
let shuttingDown = false;
let resolveClosed!: () => void;
const closed = new Promise<void>((resolve) => {
resolveClosed = resolve;
});
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);
transport["onclose"] = undefined;
void close().finally(resolveClosed);
};
transport["onclose"] = shutdown;
process.stdin.once("end", shutdown);
process.stdin.once("close", shutdown);
process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);
try {
await server.connect(transport);
await start();
await closed;
} finally {
shutdown();
await closed;
}
}