import { Command } from 'commander'; import { generateCardDeck, type GenerateCardDeckParams, type CardField } from '../tools/generate-card-deck.js'; /** * MCP 服务器命令 * * 提供 MCP (Model Context Protocol) 服务器功能,用于与 AI 助手集成 */ export interface MCPOptions { port?: string; } export const mcpCommand = new Command('mcp') .description('MCP 服务器 - 用于 AI 助手集成的工具协议') .addCommand( new Command('serve') .description('启动 MCP 服务器') .argument('[host]', '服务器地址', 'stdio') .option('-p, --port ', 'HTTP 端口(仅 HTTP 传输)', '3001') .action(mcpServeAction) ) .addCommand( new Command('generate-card-deck') .description('生成卡牌组(快速命令)') .requiredOption('--name ', '卡牌组名称') .requiredOption('--output ', '输出目录') .option('-c, --count ', '卡牌数量', '10') .option('--fields ', '字段列表(逗号分隔)', 'name,type,cost,description') .option('--description ', '卡牌组描述') .option('--size ', '卡牌尺寸', '54x86') .option('--grid ', '网格布局', '5x8') .action(generateCardDeckAction) ); /** * MCP 服务器启动处理函数 */ async function mcpServeAction(host: string, options: MCPOptions) { // 动态导入 MCP SDK const { Server } = await import('@modelcontextprotocol/sdk/server/index.js'); const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js'); const { CallToolRequestSchema, ListToolsRequestSchema, } = await import('@modelcontextprotocol/sdk/types.js'); const server = new Server( { name: 'ttrpg-card-generator', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); // 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'generate_card_deck', description: '生成 TTRPG 卡牌组内容,包括 Markdown 介绍文件、CSV 数据文件和 md-deck 组件配置', inputSchema: { type: 'object', properties: { deck_name: { type: 'string', description: '卡牌组名称,将用于生成文件名', }, output_dir: { type: 'string', description: '输出目录路径', }, card_count: { type: 'number', description: '卡牌数量', default: 10, }, card_template: { type: 'object', description: '卡牌模板定义', properties: { fields: { type: 'array', description: '卡牌字段列表', items: { type: 'object', properties: { name: { type: 'string', description: '字段名称(英文,用于 CSV 列名)', }, description: { type: 'string', description: '字段描述', }, examples: { type: 'array', description: '示例值列表', items: { type: 'string' }, }, }, required: ['name'], }, }, examples: { type: 'array', description: '完整的卡牌示例数据', items: { type: 'object', additionalProperties: { type: 'string' }, }, }, }, required: ['fields'], }, deck_config: { type: 'object', description: 'md-deck 组件配置', properties: { size: { type: 'string', description: '卡牌尺寸,格式 "宽 x 高"(单位 mm)', default: '54x86', }, grid: { type: 'string', description: '网格布局,格式 "列 x 行"', default: '5x8', }, bleed: { type: 'number', description: '出血边距(mm)', default: 1, }, padding: { type: 'number', description: '内边距(mm)', default: 2, }, shape: { type: 'string', description: '卡牌形状', enum: ['rectangle', 'circle', 'hex', 'diamond'], default: 'rectangle', }, layers: { type: 'string', description: '正面图层配置,格式 "字段:行,列范围,字体大小"', }, back_layers: { type: 'string', description: '背面图层配置', }, }, }, description: { type: 'string', description: '卡牌组的介绍描述(可选)', }, }, required: ['deck_name', 'output_dir'], }, }, ], }; }); // 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === 'generate_card_deck') { try { const params = args as unknown as GenerateCardDeckParams; // 验证必需参数 if (!params.deck_name || !params.output_dir) { return { content: [ { type: 'text', text: '错误:缺少必需参数 deck_name 或 output_dir', }, ], isError: true, }; } // 生成卡牌组 const result = generateCardDeck(params); return { content: [ { type: 'text', text: result.message, }, { type: 'text', text: `\n## 生成的组件代码\n\n\`\`\`markdown\n${result.deckComponent}\n\`\`\``, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `生成失败:${error instanceof Error ? error.message : '未知错误'}`, }, ], isError: true, }; } } return { content: [ { type: 'text', text: `未知工具:${name}`, }, ], isError: true, }; }); // 启动服务器 if (host === 'stdio') { const transport = new StdioServerTransport(); await server.connect(transport); console.error('TTRPG 卡牌生成 MCP 服务器已启动(stdio)'); } else { // TODO: 支持 HTTP 传输 console.error('HTTP 传输模式尚未实现,请使用 stdio 模式'); process.exit(1); } } /** * 快速生成卡牌组命令处理函数 */ async function generateCardDeckAction(options: { name: string; output: string; count: string; fields: string; description?: string; size: string; grid: string; }) { const fieldNames = options.fields.split(',').map(f => f.trim()); const template = { fields: fieldNames.map((name): CardField => ({ name, examples: ['示例 1', '示例 2'] })) }; const params: GenerateCardDeckParams = { deck_name: options.name, output_dir: options.output, card_count: parseInt(options.count, 10), card_template: template, deck_config: { size: options.size, grid: options.grid }, description: options.description }; const result = generateCardDeck(params); console.log('\n✅ 卡牌组生成成功!\n'); console.log(result.message); console.log('\n📦 组件代码:'); console.log(` ${result.deckComponent}\n`); }