feat: resources?
This commit is contained in:
parent
8213092bb6
commit
301f499494
72
docs/mcp.md
72
docs/mcp.md
|
|
@ -289,6 +289,78 @@ label,name,type,cost,description
|
|||
- 其他列:由 frontmatter 中的 `fields` 定义
|
||||
- `body`: 卡牌 body 内容(可选,支持 `{{字段名}}` 语法)
|
||||
|
||||
## MCP Resources
|
||||
|
||||
MCP 服务器提供以下 Resources,包含 TTRPG Tools 的文档和参考材料:
|
||||
|
||||
### `ttrpg-docs://csv` - CSV 编写说明
|
||||
|
||||
**名称:** csv.md
|
||||
**标题:** CSV 编写说明
|
||||
**描述:** TTRPG Tools CSV 文件格式说明,包括 Front Matter、字段定义、变量语法等
|
||||
|
||||
**内容包含:**
|
||||
- YAML Front Matter 结构(fields、deck、自定义属性)
|
||||
- 数据类型(word、paragraph、number、symbol、symbol_list)
|
||||
- 字段功能(identify、compare、flavor、rule)
|
||||
- CSV 数据格式和特殊字符处理
|
||||
- 变量语法(`{{prop}}`)
|
||||
- 分组支持
|
||||
- 完整示例(魔法物品、随机遭遇表、NPC 名录)
|
||||
|
||||
### `ttrpg-docs://markdown` - Markdown 编写说明
|
||||
|
||||
**名称:** markdown.md
|
||||
**标题:** Markdown 编写说明
|
||||
**描述:** TTRPG Tools Markdown 扩展语法和组件用法说明
|
||||
|
||||
**内容包含:**
|
||||
- 基础语法(GFM、marked-alert、marked-directive)
|
||||
- 指令语法格式
|
||||
- 图标语法(`:[icon-name]`)
|
||||
- 组件库:
|
||||
- `:md-dice` - 骰子组件
|
||||
- `:md-link` - 链接组件
|
||||
- `:md-bg` - 背景组件
|
||||
- `:md-pins` - 标记组件
|
||||
- `:md-table` - 表格组件
|
||||
- `:md-deck` - 卡牌组件
|
||||
- `:md-yarn-spinner` - 叙事线组件
|
||||
- `:md-token` - 代币组件
|
||||
- `:md-commander` - 命令追踪器
|
||||
- YAML 标签
|
||||
- Mermaid 图表
|
||||
- 警告/提示块
|
||||
- 文件引用规则
|
||||
- 样式定制
|
||||
|
||||
### 使用示例
|
||||
|
||||
**列出所有 Resources:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "resources/list"
|
||||
}
|
||||
```
|
||||
|
||||
**读取 Resource:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "resources/read",
|
||||
"params": {
|
||||
"uri": "ttrpg-docs://csv"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Prompts
|
||||
|
||||
MCP 服务器提供以下 Prompts,用于引导用户完成卡牌设计工作流:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import {
|
|||
getSetupDeckDisplayPrompt,
|
||||
type SetupDeckDisplayOptions
|
||||
} from '../prompts/setup-deck-display.js';
|
||||
import {
|
||||
listResources,
|
||||
readResource,
|
||||
type DocResource
|
||||
} from '../resources/docs.js';
|
||||
|
||||
/**
|
||||
* MCP 服务器命令
|
||||
|
|
@ -71,6 +76,8 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
|||
ListToolsRequestSchema,
|
||||
ListPromptsRequestSchema,
|
||||
GetPromptRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} = await import('@modelcontextprotocol/sdk/types.js');
|
||||
|
||||
const server = new Server(
|
||||
|
|
@ -82,6 +89,7 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
|||
capabilities: {
|
||||
tools: {},
|
||||
prompts: {},
|
||||
resources: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
@ -411,6 +419,34 @@ async function mcpServeAction(host: string, options: MCPOptions) {
|
|||
}
|
||||
});
|
||||
|
||||
// 处理 Resources 列表请求
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: listResources().map(r => ({
|
||||
uri: r.uri,
|
||||
name: r.name,
|
||||
title: r.title,
|
||||
description: r.description,
|
||||
mimeType: r.mimeType,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// 处理 Resources 读取请求
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const { uri } = request.params;
|
||||
|
||||
const resource = readResource(uri, process.cwd());
|
||||
|
||||
if (!resource) {
|
||||
throw new Error(`Resource not found: ${uri}`);
|
||||
}
|
||||
|
||||
return {
|
||||
contents: [resource],
|
||||
};
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
if (host === 'stdio') {
|
||||
const transport = new StdioServerTransport();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* 文档资源定义
|
||||
*/
|
||||
export interface DocResource {
|
||||
uri: string;
|
||||
name: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预定义的文档资源列表
|
||||
*/
|
||||
export const DOC_RESOURCES: DocResource[] = [
|
||||
{
|
||||
uri: 'ttrpg-docs://csv',
|
||||
name: 'csv.md',
|
||||
title: 'CSV 编写说明',
|
||||
description: 'TTRPG Tools CSV 文件格式说明,包括 Front Matter、字段定义、变量语法等',
|
||||
mimeType: 'text/markdown'
|
||||
},
|
||||
{
|
||||
uri: 'ttrpg-docs://markdown',
|
||||
name: 'markdown.md',
|
||||
title: 'Markdown 编写说明',
|
||||
description: 'TTRPG Tools Markdown 扩展语法和组件用法说明',
|
||||
mimeType: 'text/markdown'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取资源列表
|
||||
*/
|
||||
export function listResources(): DocResource[] {
|
||||
return DOC_RESOURCES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取资源内容
|
||||
* @param uri 资源 URI
|
||||
* @param cwd 工作目录
|
||||
* @returns 资源内容
|
||||
*/
|
||||
export function readResource(uri: string, cwd: string): {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
text: string;
|
||||
} | null {
|
||||
// 解析 URI
|
||||
const docName = uri.replace('ttrpg-docs://', '');
|
||||
const fileName = `${docName}.md`;
|
||||
const filePath = join(cwd, 'docs', fileName);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
return {
|
||||
uri,
|
||||
mimeType: 'text/markdown',
|
||||
text: content
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`Failed to read resource ${uri}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue