Skip to main content
Glama
index.ts8.56 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema, } from "@modelcontextprotocol/sdk/types.js"; import { readdir, readFile, writeFile, mkdir } from "fs/promises"; import { join, dirname, extname, basename } from "path"; import { fileURLToPath } from "url"; import { existsSync } from "fs"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; // 获取当前文件的目录路径 const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // 工具参数验证 schemas const GetTemplateListSchema = z.object({ dummy: z.string().optional().describe("Dummy parameter for no-parameter tools") }); const GetTemplateDetailSchema = z.object({ templateName: z.string().describe("模板文件名(不包含扩展名),必须使用 get_template_list 返回的 templates 中的 templateName") }); const ToolInputSchema = ToolSchema.shape.inputSchema; type ToolInput = z.infer<typeof ToolInputSchema>; // 获取模板目录路径 function getTemplateDir(): string { // 如果在build目录中运行,需要回到上级目录找src/template const srcDir = __dirname.includes('build') ? join(__dirname, '..', 'src') : __dirname; return join(srcDir, "template"); } // 获取记录目录路径 function getRecordDir(): string { // 如果在build目录中运行,需要回到上级目录找src/record const srcDir = __dirname.includes('build') ? join(__dirname, '..', 'src') : __dirname; return join(srcDir, "record"); } // 确保记录目录存在 async function ensureRecordDir(): Promise<void> { const recordDir = getRecordDir(); if (!existsSync(recordDir)) { await mkdir(recordDir, { recursive: true }); } } // 根据模板名称获取描述 function getTemplateDescription(templateName: string): string { const descriptions: Record<string, string> = { "meeting-record": "会议记录模板 - 用于记录会议内容、决策和行动项", "project-summary": "项目总结模板 - 用于总结项目进展、问题和计划", "learning-notes": "学习笔记模板 - 用于记录学习内容和心得体会", "daily-standup": "每日站会模板 - 用于记录团队每日站会内容" }; return descriptions[templateName] || "通用记录模板"; } // Server setup const server = new Server( { name: "devrecord-server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); // 工具列表处理器 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_template_list", description: "获取可用的开发记录模板列表。返回包含模板名称、文件名和描述的结构化数据。" + "返回的 templates 数组中每个模板都有 templateName 字段,这是后续调用 get_template_detail 时必需的参数。" + "使用此工具来发现可用的模板,然后使用返回的 templateName 来获取具体模板内容。", inputSchema: zodToJsonSchema(GetTemplateListSchema) as ToolInput, }, { name: "get_template_detail", description: "获取指定模板的详细内容。templateName 参数必须使用 get_template_list 返回的 templates 中的 templateName 值。" + "这确保了参数的准确性和一致性。返回模板的完整内容,可用于创建新的开发记录文档。" + "如果模板不存在,会返回明确的错误信息。", inputSchema: zodToJsonSchema(GetTemplateDetailSchema) as ToolInput, }, ], }; }); // 工具调用处理器 server.setRequestHandler(CallToolRequestSchema, async (request: any) => { try { const { name, arguments: args } = request.params; switch (name) { case "get_template_list": { const parsed = GetTemplateListSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for get_template_list: ${parsed.error}`); } try { const templateDir = getTemplateDir(); const files = await readdir(templateDir); // 过滤出 .md 文件并构建模板信息 const templates = files .filter((file: string) => extname(file) === ".md") .map((file: string) => { const templateName = basename(file, ".md"); return { templateName, filename: file, description: getTemplateDescription(templateName) }; }); // 返回标准化格式,便于AI识别和使用 return { content: [ { type: "text", text: JSON.stringify({ success: true, templates, count: templates.length, message: `Found ${templates.length} available templates`, usage: "Use the 'templateName' field from any template to call get_template_detail" }, null, 2) } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: JSON.stringify({ success: false, error: `Failed to get template list: ${errorMessage}`, templates: [] }, null, 2) } ], isError: true, }; } } case "get_template_detail": { const parsed = GetTemplateDetailSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for get_template_detail: ${parsed.error}`); } try { const { templateName } = parsed.data; const templateDir = getTemplateDir(); const templatePath = join(templateDir, `${templateName}.md`); if (!existsSync(templatePath)) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: `Template '${templateName}' does not exist`, templateName, suggestion: "Use get_template_list to see available templates" }, null, 2) } ], isError: true, }; } const content = await readFile(templatePath, "utf-8"); return { content: [ { type: "text", text: JSON.stringify({ success: true, templateName, content, description: getTemplateDescription(templateName), message: `Successfully retrieved template '${templateName}'` }, null, 2) } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: JSON.stringify({ success: false, error: `Failed to get template detail: ${errorMessage}`, templateName: parsed.data?.templateName || "unknown" }, null, 2) } ], isError: true, }; } } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true, }; } }); // 启动服务器 async function runServer() { try { // 确保记录目录存在 await ensureRecordDir(); const transport = new StdioServerTransport(); await server.connect(transport); console.error("DevRecord MCP Server running on stdio"); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/seenbefore/DevRecord'

If you have feedback or need assistance with the MCP directory API, please join our Discord server