From e5ce8c20b50b40df802ff0cb036123e5d176cb4a Mon Sep 17 00:00:00 2001 From: hypercross Date: Wed, 18 Mar 2026 15:00:13 +0800 Subject: [PATCH] fix: mcp serve --- src/cli/commands/mcp.ts | 34 ++++++++++++++++------ src/cli/commands/serve.ts | 60 +++++++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/cli/commands/mcp.ts b/src/cli/commands/mcp.ts index e40bc85..c03a9d7 100644 --- a/src/cli/commands/mcp.ts +++ b/src/cli/commands/mcp.ts @@ -24,7 +24,7 @@ import { readResource, type DocResource } from '../resources/docs.js'; -import { serveCommand } from './serve.js'; +import { createContentServer, type ContentServer } from './serve.js'; /** * MCP 服务器命令 @@ -69,12 +69,8 @@ async function mcpServeAction(host: string, options: MCPOptions) { process.chdir(cwd); console.error(`MCP 服务器工作目录:${cwd}`); - // 启动 serve 服务器(在后台运行) - console.error('启动预览服务器...'); - serveCommand(cwd, { port: '3000' }); - - // 等待服务器启动 - await new Promise(resolve => setTimeout(resolve, 1000)); + // 启动内容服务器(预览服务器) + const contentServer: ContentServer = createContentServer(cwd, 3000); // 动态导入 MCP SDK const { Server } = await import('@modelcontextprotocol/sdk/server/index.js'); @@ -328,7 +324,7 @@ async function mcpServeAction(host: string, options: MCPOptions) { } case 'deck_card_crud': { - const params = args as unknown as CardCrudParams; + let params = args as unknown as CardCrudParams; if (!params.csv_file || !params.action) { return { @@ -337,6 +333,15 @@ async function mcpServeAction(host: string, options: MCPOptions) { }; } + // 解析 cards 参数(可能是 JSON 字符串) + if (params.cards && typeof params.cards === 'string') { + try { + params.cards = JSON.parse(params.cards); + } catch (e) { + // 如果不是 JSON,保持原样 + } + } + const result = cardCrud(params); return { @@ -465,6 +470,19 @@ async function mcpServeAction(host: string, options: MCPOptions) { const transport = new StdioServerTransport(); await server.connect(transport); console.error('TTRPG 卡牌生成 MCP 服务器已启动(stdio)'); + + // 监听进程退出事件,关闭服务器 + process.on('SIGINT', () => { + console.error('\n正在关闭服务器...'); + contentServer.close(); + process.exit(0); + }); + + process.on('SIGTERM', () => { + console.error('\n正在关闭服务器...'); + contentServer.close(); + process.exit(0); + }); } else { // TODO: 支持 HTTP 传输 console.error('HTTP 传输模式尚未实现,请使用 stdio 模式'); diff --git a/src/cli/commands/serve.ts b/src/cli/commands/serve.ts index 95bbc0a..680ab3c 100644 --- a/src/cli/commands/serve.ts +++ b/src/cli/commands/serve.ts @@ -47,7 +47,7 @@ function getMimeType(filePath: string): string { /** * 扫描目录内的 .md 文件,生成索引 */ -function scanDirectory(dir: string): ContentIndex { +export function scanDirectory(dir: string): ContentIndex { const index: ContentIndex = {}; function scan(currentPath: string, relativePath: string) { @@ -176,10 +176,35 @@ function createRequestHandler( } /** - * 启动开发服务器 + * 内容服务器接口 */ -export const serveCommand: ServeCommandHandler = async (dir, options) => { - const contentDir = resolve(dir); +export interface ContentServer { + /** + * HTTP 服务器实例 + */ + server: Server; + /** + * 文件监听器 + */ + watcher: ReturnType; + /** + * 内容索引 + */ + index: ContentIndex; + /** + * 关闭服务器 + */ + close(): void; +} + +/** + * 创建内容服务器 + */ +export function createContentServer( + contentDir: string, + port: number, + distPath: string = distDir, +): ContentServer { let contentIndex: ContentIndex = {}; // 扫描内容目录生成索引 @@ -231,19 +256,38 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => { // 创建请求处理器 const handleRequest = createRequestHandler( contentDir, - distDir, + distPath, () => contentIndex, ); // 创建 HTTP 服务器 const server = createServer(handleRequest); - const port = parseInt(options.port, 10); - server.listen(port, () => { console.log(`\n开发服务器已启动:http://localhost:${port}`); console.log(`内容目录:${contentDir}`); - console.log(`静态资源目录:${distDir}`); + console.log(`静态资源目录:${distPath}`); console.log(`索引文件:http://localhost:${port}/__CONTENT_INDEX.json\n`); }); + + return { + server, + watcher, + index: contentIndex, + close() { + console.log("关闭内容服务器..."); + server.close(); + watcher.close(); + }, + }; +} + +/** + * 启动开发服务器 + */ +export const serveCommand: ServeCommandHandler = async (dir, options) => { + const contentDir = resolve(dir); + const port = parseInt(options.port, 10); + + createContentServer(contentDir, port); };