fix: mcp
This commit is contained in:
+252
-43
@@ -1,9 +1,13 @@
|
||||
import { Command } from 'commander';
|
||||
import { generateCardDeck, type GenerateCardDeckParams, type CardField } from '../tools/generate-card-deck.js';
|
||||
import { readFrontmatter, type ReadFrontmatterParams, type DeckFrontmatter } from '../tools/frontmatter/read-frontmatter.js';
|
||||
import { writeFrontmatter, type WriteFrontmatterParams } from '../tools/frontmatter/write-frontmatter.js';
|
||||
import { cardCrud, type CardCrudParams, type CardData } from '../tools/card/card-crud.js';
|
||||
import { ensureDeckPreview, type EnsureDeckPreviewParams } from '../tools/ensure-deck-preview.js';
|
||||
|
||||
/**
|
||||
* MCP 服务器命令
|
||||
*
|
||||
*
|
||||
* 提供 MCP (Model Context Protocol) 服务器功能,用于与 AI 助手集成
|
||||
*/
|
||||
|
||||
@@ -70,7 +74,7 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
||||
tools: [
|
||||
{
|
||||
name: 'generate_card_deck',
|
||||
description: '生成 TTRPG 卡牌组内容,包括 Markdown 介绍文件、CSV 数据文件和 md-deck 组件配置',
|
||||
description: '生成 TTRPG 卡牌组内容,包括 Markdown 介绍文件、CSV 数据文件和 md-deck 组件配置(一站式快捷工具)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -173,6 +177,129 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
||||
required: ['deck_name', 'output_dir'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'deck_frontmatter_read',
|
||||
description: '读取 CSV 文件的 frontmatter(包含模板定义和 deck 配置)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
csv_file: {
|
||||
type: 'string',
|
||||
description: 'CSV 文件路径(相对路径相对于 MCP 服务器工作目录)',
|
||||
},
|
||||
},
|
||||
required: ['csv_file'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'deck_frontmatter_write',
|
||||
description: '写入/更新 CSV 文件的 frontmatter(模板定义和 deck 配置)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
csv_file: {
|
||||
type: 'string',
|
||||
description: 'CSV 文件路径',
|
||||
},
|
||||
frontmatter: {
|
||||
type: 'object',
|
||||
description: '要写入的 frontmatter 数据',
|
||||
properties: {
|
||||
fields: {
|
||||
type: 'array',
|
||||
description: '字段定义列表',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: '字段名称' },
|
||||
description: { type: 'string', description: '字段描述' },
|
||||
examples: { type: 'array', items: { type: 'string' }, description: '示例值列表' },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
deck: {
|
||||
type: 'object',
|
||||
description: 'Deck 配置',
|
||||
properties: {
|
||||
size: { type: 'string', description: '卡牌尺寸,格式 "宽 x 高"' },
|
||||
grid: { type: 'string', description: '网格布局,格式 "列 x 行"' },
|
||||
bleed: { type: 'number', description: '出血边距(mm)' },
|
||||
padding: { type: 'number', description: '内边距(mm)' },
|
||||
shape: { type: 'string', enum: ['rectangle', 'circle', 'hex', 'diamond'] },
|
||||
layers: { type: 'string', description: '正面图层配置' },
|
||||
back_layers: { type: 'string', description: '背面图层配置' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
merge: {
|
||||
type: 'boolean',
|
||||
description: '是否合并现有 frontmatter(默认 true)',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
required: ['csv_file', 'frontmatter'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'deck_card_crud',
|
||||
description: '卡牌 CRUD 操作(创建/读取/更新/删除),支持批量操作',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
csv_file: {
|
||||
type: 'string',
|
||||
description: 'CSV 文件路径',
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
description: '操作类型',
|
||||
enum: ['create', 'read', 'update', 'delete'],
|
||||
},
|
||||
cards: {
|
||||
type: ['array', 'object'],
|
||||
description: '卡牌数据(单张或数组)',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
},
|
||||
},
|
||||
label: {
|
||||
type: ['string', 'array'],
|
||||
description: '要操作的卡牌 label(用于 read/update/delete)',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
required: ['csv_file', 'action'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'deck_ensure_preview',
|
||||
description: '确保 CSV 对应的 Markdown 预览文件存在',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
csv_file: {
|
||||
type: 'string',
|
||||
description: 'CSV 文件路径',
|
||||
},
|
||||
md_file: {
|
||||
type: 'string',
|
||||
description: 'Markdown 文件路径(可选,默认与 CSV 同名)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: '标题(可选,默认从 CSV 文件名推断)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '描述(可选)',
|
||||
},
|
||||
},
|
||||
required: ['csv_file'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
@@ -181,60 +308,142 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
if (name === 'generate_card_deck') {
|
||||
try {
|
||||
const params = args as unknown as GenerateCardDeckParams;
|
||||
try {
|
||||
switch (name) {
|
||||
case 'generate_card_deck': {
|
||||
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);
|
||||
|
||||
// 验证必需参数
|
||||
if (!params.deck_name || !params.output_dir) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '错误:缺少必需参数 deck_name 或 output_dir',
|
||||
text: result.message,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: `\n## 生成的组件代码\n\n\`\`\`markdown\n${result.deckComponent}\n\`\`\``,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 生成卡牌组(使用当前工作目录)
|
||||
const result = generateCardDeck(params);
|
||||
case 'deck_frontmatter_read': {
|
||||
const params = args as unknown as ReadFrontmatterParams;
|
||||
|
||||
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,
|
||||
};
|
||||
if (!params.csv_file) {
|
||||
return {
|
||||
content: [{ type: 'text', text: '错误:缺少必需参数 csv_file' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = readFrontmatter(params);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: result.message,
|
||||
},
|
||||
...(result.frontmatter ? [{
|
||||
type: 'text',
|
||||
text: `\n## Frontmatter\n\n\`\`\`json\n${JSON.stringify(result.frontmatter, null, 2)}\n\`\`\``,
|
||||
}] : []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'deck_frontmatter_write': {
|
||||
const params = args as unknown as WriteFrontmatterParams;
|
||||
|
||||
if (!params.csv_file || !params.frontmatter) {
|
||||
return {
|
||||
content: [{ type: 'text', text: '错误:缺少必需参数 csv_file 或 frontmatter' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = writeFrontmatter(params);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
||||
|
||||
case 'deck_card_crud': {
|
||||
const params = args as unknown as CardCrudParams;
|
||||
|
||||
if (!params.csv_file || !params.action) {
|
||||
return {
|
||||
content: [{ type: 'text', text: '错误:缺少必需参数 csv_file 或 action' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = cardCrud(params);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: result.success ? `✅ ${result.message}` : `❌ ${result.message}`,
|
||||
},
|
||||
...(result.cards && result.cards.length > 0 ? [{
|
||||
type: 'text',
|
||||
text: `\n## 卡牌数据\n\n\`\`\`json\n${JSON.stringify(result.cards, null, 2)}\n\`\`\``,
|
||||
}] : []),
|
||||
],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
||||
|
||||
case 'deck_ensure_preview': {
|
||||
const params = args as unknown as EnsureDeckPreviewParams;
|
||||
|
||||
if (!params.csv_file) {
|
||||
return {
|
||||
content: [{ type: 'text', text: '错误:缺少必需参数 csv_file' }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = ensureDeckPreview(params);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
content: [{ type: 'text', text: `未知工具:${name}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `工具调用失败:${error instanceof Error ? error.message : '未知错误'}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `未知工具:${name}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
|
||||
Reference in New Issue
Block a user