@ai-partner/ai-partner-prd
Version:
PRD MCP Server for Cursor - 为 Cursor 终端大模型提供 PRD 相关提示词的 MCP Server
330 lines (300 loc) • 12.8 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import fs from "fs";
import path from "path";
// 获取 API Key 的函数
function getApiKey() {
const apiKey = process.env.API_KEY;
if (!apiKey) {
console.error("API_KEY environment variable is not set");
process.exit(1);
}
return apiKey; // 返回原始 API Key,用于 X-API-Key header
}
// 读取配置文件
function loadConfig() {
// 获取当前脚本文件所在的目录(处理 Windows 路径问题)
const __filename = new URL(import.meta.url).pathname;
const __dirname = path.dirname(__filename.startsWith('/') && process.platform === 'win32' ? __filename.slice(1) : __filename);
const configPath = path.join(__dirname, "mcp-server.json");
try {
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
return config;
} catch (e) {
console.error(`无法读取 mcp-server.json 配置文件,请检查!配置文件路径: ${configPath}`);
process.exit(1);
}
}
// 初始化配置
const config = loadConfig();
const API_KEY = getApiKey();
const API_URL = config.baseUrl;
const TOOL_FILE_PATHS = config.toolFilePaths;
// Tool 定义
const INIT_PROMPT_TOOL = {
name: "init_prompt",
description: "获取初始化提示词,用于系统启动时的初始设置和指导。当触发任何 ai-partner-prd 工具时,系统需要先检查是否已经初始化,如果未初始化则必须先调用此工具进行初始化。初始化后系统将设置相关的配置信息和指导原则,确保后续工具调用的正确性。",
inputSchema: {
type: "object",
properties: {
random_string: {
type: "string",
description: "Dummy parameter for no-parameter tools"
},
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: ["random_string"]
}
};
const PRD_DOCUMENT_PROMPT_TOOL = {
name: "prd_document_prompt",
description: "获取产品PRD文档编写的提示词。所有写文案相关的提示词都通过这个工具获取,包括概述编写、业务需求、功能设计、非功能设计等文档编写指导。\n\n**参数规则说明:**\n1. **必须使用完整路径**:filePath 参数必须传入完整的文件路径,包含目录结构\n2. **不支持仅文件名**:不能只传入文件名(如 'function-design-thinking.mdc'),必须包含完整目录\n3. **标准路径格式**:使用相对于项目根目录的路径格式\n4. **常用完整路径示例**:\n - '.cursor/rules/prd/design-methodology/function-design-thinking.mdc' - 功能设计思考方法论\n - '.cursor/rules/prd/templates/docs/template-function-module-design.mdc' - 功能模块设计模板\n - '.cursor/rules/prd/design-methodology/business-process-design-thinking.mdc' - 业务流程设计思考\n - '.cursor/rules/prd/design-methodology/overview-design-thinking.mdc' - 概述设计思考\n - '.cursor/rules/prd/design-methodology/report-design-thinking.mdc' - 报表设计思考\n5. **错误示例**:\n - ❌ 'function-design-thinking.mdc' (缺少路径)\n - ❌ 'template-function-module-design.mdc' (缺少路径)\n - ✅ '.cursor/rules/prd/design-methodology/function-design-thinking.mdc'\n - ✅ '.cursor/rules/prd/templates/docs/template-function-module-design.mdc'\n6. **目录结构**:\n - 设计方法论文件:`.cursor/rules/prd/design-methodology/`\n - 模板文件:`.cursor/rules/prd/templates/docs/`\n7. **路径验证**:确保路径存在且文件可访问,否则会返回文件获取失败的错误",
inputSchema: {
type: "object",
properties: {
filePath: {
type: "string",
description: "完整的文件路径(必须包含目录结构)。例如:'.cursor/rules/prd/design-methodology/function-design-thinking.mdc'、'.cursor/rules/prd/templates/docs/template-function-module-design.mdc' 等。不能仅传入文件名。"
},
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: ["filePath"]
}
};
const DIAGRAM_PROMPT_TOOL = {
name: "diagram_prompt",
description: "获取图形类的提示词。功能需求说明书或PRD中所有画图相关的提示词都从这个工具获取,包括流程图、架构图、原型图等绘制指导",
inputSchema: {
type: "object",
properties: {
filePath: {
type: "string",
description: "文件路径"
},
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: ["filePath"]
}
};
const QUALITY_CHECK_PROMPT_TOOL = {
name: "quality_check_prompt",
description: "获取质量检查提示词。在生成某一个任务完成后主动调用,用于检查文档质量、完整性和规范性",
inputSchema: {
type: "object",
properties: {
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: []
}
};
const SUMMARY_PROMPT_TOOL = {
name: "summary_prompt",
description: "获取总结提示词。对整个文档生成完成后进行总结,包括项目概览、完成情况和关键要点汇总",
inputSchema: {
type: "object",
properties: {
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: []
}
};
const NEXT_STEP_PROMPT_TOOL = {
name: "next_step_prompt",
description: "获取下一步建议的提示词。返回多个相关指令和建议选项,大模型需要根据返回结果以及上下文信息为用户提供个性化的下一步行动建议",
inputSchema: {
type: "object",
properties: {
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: []
}
};
const GENERATE_SPEC_DOC_COMMAND_TOOL = {
name: "generate_spec_doc_command",
description: "必须先完成初始化相关文件或文件夹操作之后再获取生成功能规格说明书的指令内容。先查看指令内容,再根据指令获取对应的提示词进行生成。用户输入 /生成功能规格说明书 时调用此工具",
inputSchema: {
type: "object",
properties: {
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: []
}
};
const HELP_GUIDE_TOOL = {
name: "help_guide",
description: "获取 PRD MCP 工具使用指南,帮助大模型了解如何正确使用这个 MCP 的各种工具。建议在 MCP 初始化完成后或用户询问使用方法时调用此工具",
inputSchema: {
type: "object",
properties: {
silent: {
type: "boolean",
description: "是否静默执行,设置为 true 时不显示结果框架",
default: true
}
},
required: []
}
};
// 通过远程 API 获取文件内容
async function getFileContent(filePath) {
try {
const url = `${API_URL}/api/file/content/scenario/prd`;
const response = await axios.get(url, {
params: { filePath },
headers: {
"X-API-Key": API_KEY,
"accept": "*/*"
}
});
if (response.data && response.data.data && response.data.data.content) {
return response.data.data.content;
}
throw new Error("远程接口未返回 content 字段");
} catch (error) {
throw new Error(`远程获取文件失败: ${error.message}`);
}
}
// 创建服务器实例
const server = new Server(
{
name: "prd-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// 工具列表处理
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("=== MCP 工具列表请求 ==="); // 调试信息
const tools = [
INIT_PROMPT_TOOL,
PRD_DOCUMENT_PROMPT_TOOL,
DIAGRAM_PROMPT_TOOL,
QUALITY_CHECK_PROMPT_TOOL,
SUMMARY_PROMPT_TOOL,
NEXT_STEP_PROMPT_TOOL,
GENERATE_SPEC_DOC_COMMAND_TOOL,
HELP_GUIDE_TOOL
];
console.error("返回工具数量:", tools.length); // 调试信息
console.error("工具名称列表:", tools.map(t => t.name)); // 调试信息
return { tools };
});
// 工具调用处理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "init_prompt":
case "next_step_prompt":
case "generate_spec_doc_command":
case "quality_check_prompt":
case "summary_prompt":
case "help_guide": {
// 无参数工具,从配置文件获取文件路径
const filePath = TOOL_FILE_PATHS[name];
if (!filePath) {
throw new Error(`工具 ${name} 的文件路径配置未找到`);
}
const content = await getFileContent(filePath);
const silent = true;
const result = {
content: [
{
type: "text",
text: content,
},
],
};
// 如果设置了 silent=true,添加静默标识(内容正常返回给AI,但界面不显示)
if (silent) {
result._meta = { silent: true };
}
return result;
}
case "prd_document_prompt":
case "diagram_prompt": {
// 需要参数的工具,从参数获取文件路径
const filePath = args.filePath;
const content = await getFileContent(filePath);
const silent = true;
const result = {
content: [
{
type: "text",
text: content,
},
],
};
// 如果设置了 silent=true,添加静默标识(内容正常返回给AI,但界面不显示)
if (silent) {
result._meta = { silent: true };
}
return result;
}
default:
throw new Error(`未知的工具: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `错误: ${error.message}`,
},
],
isError: true,
};
}
});
// 启动服务器
async function main() {
console.error("=== 启动 MCP Server ==="); // 调试信息
console.error("配置文件读取成功:", !!config); // 调试信息
console.error("API Key 设置:", !!API_KEY); // 调试信息
console.error("工具文件路径配置:", Object.keys(TOOL_FILE_PATHS)); // 调试信息
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("=== MCP Server 已连接 ==="); // 调试信息
}
main().catch((error) => {
console.error("服务器启动失败:", error);
process.exit(1);
});