import { Injectable, Logger } from '@nestjs/common'; import { allowedWecomPluginMcpTools } from '@dukang/shared-types'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { WECOM_PLUGIN_MCP_TOOL_DEFS } from './wecom-plugin-mcp.tools'; import type { WecomPluginQueryService } from './wecom-plugin-query.service'; import type { WecomPluginRuntime } from './wecom-plugin.types'; import { wecomPluginCaller } from './wecom-plugin.util'; export function createWecomPluginMcpServer( query: WecomPluginQueryService, plugin: WecomPluginRuntime, wecomUserId: string, ): McpServer { const server = new McpServer({ name: plugin.name || 'dukang-wecom-plugin', version: '1.0.0', }); const allowed = new Set(allowedWecomPluginMcpTools(plugin.permissions)); const ctx = { query, plugin, wecomUserId }; for (const def of WECOM_PLUGIN_MCP_TOOL_DEFS) { if (!allowed.has(def.name)) continue; server.registerTool( def.name, { title: def.name, description: def.description, inputSchema: def.inputSchema }, async (args) => def.invoke(ctx, (args ?? {}) as Record), ); } return server; } @Injectable() export class WecomPluginMcpFactory { private readonly logger = new Logger(WecomPluginMcpFactory.name); constructor(private readonly query: WecomPluginQueryService) {} async handle( req: IncomingMessage & { body?: unknown; headers: IncomingMessage['headers'] }, res: ServerResponse, plugin: WecomPluginRuntime, ): Promise { const server = createWecomPluginMcpServer(this.query, plugin, wecomPluginCaller(req)); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); res.on('close', () => { void transport.close(); void server.close(); }); try { await server.connect(transport); await transport.handleRequest(req, res, req.body); } catch (e) { const message = e instanceof Error ? e.message : String(e); this.logger.error(`wecom plugin MCP failed: ${message}`); if (!res.headersSent) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end( JSON.stringify({ jsonrpc: '2.0', error: { code: -32603, message }, id: null, }), ); } } } }