Files
openclaw/src/gateway/mcp-http.handlers.ts
2026-04-04 15:34:13 +09:00

90 lines
2.9 KiB
TypeScript

import crypto from "node:crypto";
import {
MCP_LOOPBACK_SERVER_NAME,
MCP_LOOPBACK_SERVER_VERSION,
MCP_LOOPBACK_SUPPORTED_PROTOCOL_VERSIONS,
jsonRpcError,
jsonRpcResult,
type JsonRpcRequest,
} from "./mcp-http.protocol.js";
import type { McpLoopbackTool, McpToolSchemaEntry } from "./mcp-http.schema.js";
type McpTextContent = {
type: "text";
text: string;
};
function normalizeToolCallContent(result: unknown): McpTextContent[] {
const content = (result as { content?: unknown })?.content;
if (Array.isArray(content)) {
return content.map((block: { type?: string; text?: string }) => ({
type: (block.type ?? "text") as "text",
text: block.text ?? (typeof block === "string" ? block : JSON.stringify(block)),
}));
}
return [
{
type: "text",
text: typeof result === "string" ? result : JSON.stringify(result),
},
];
}
export async function handleMcpJsonRpc(params: {
message: JsonRpcRequest;
tools: McpLoopbackTool[];
toolSchema: McpToolSchemaEntry[];
}): Promise<object | null> {
const { id, method, params: methodParams } = params.message;
switch (method) {
case "initialize": {
const clientVersion = (methodParams?.protocolVersion as string) ?? "";
const negotiated =
MCP_LOOPBACK_SUPPORTED_PROTOCOL_VERSIONS.find((version) => version === clientVersion) ??
MCP_LOOPBACK_SUPPORTED_PROTOCOL_VERSIONS[0];
return jsonRpcResult(id, {
protocolVersion: negotiated,
capabilities: { tools: {} },
serverInfo: {
name: MCP_LOOPBACK_SERVER_NAME,
version: MCP_LOOPBACK_SERVER_VERSION,
},
});
}
case "notifications/initialized":
case "notifications/cancelled":
return null;
case "tools/list":
return jsonRpcResult(id, { tools: params.toolSchema });
case "tools/call": {
const toolName = methodParams?.name as string;
const toolArgs = (methodParams?.arguments ?? {}) as Record<string, unknown>;
const tool = params.tools.find((candidate) => candidate.name === toolName);
if (!tool) {
return jsonRpcResult(id, {
content: [{ type: "text", text: `Tool not available: ${toolName}` }],
isError: true,
});
}
const toolCallId = `mcp-${crypto.randomUUID()}`;
try {
// oxlint-disable-next-line typescript/no-explicit-any
const result = await (tool as any).execute(toolCallId, toolArgs);
return jsonRpcResult(id, {
content: normalizeToolCallContent(result),
isError: false,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return jsonRpcResult(id, {
content: [{ type: "text", text: message || "tool execution failed" }],
isError: true,
});
}
}
default:
return jsonRpcError(id, -32601, `Method not found: ${method}`);
}
}