UNPKG

mcp-planes-server

Version:

Plane API client and server

347 lines (346 loc) 14.5 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { startSSEServer } from "./utils/sse.js"; import { getResourceHandlers } from "./resources/handlers.js"; import dotenv from 'dotenv'; import { createCommentSchema, createCycleSchema, createIssueSchema, createLabelSchema, createMemberSchema, createProjectSchema, createStateSchema, deleteCommentSchema, deleteCycleSchema, deleteIssueSchema, deleteLabelSchema, deleteMemberSchema, deleteProjectSchema, deleteStateSchema, getIssueSchema, getProjectSchema, listCommentsSchema, listIssuesSchema, listLabelsSchema, listProjectsSchema, listStatesSchema, listMembersSchema, listCyclesSchema, getCycleSchema, updateCommentSchema, updateCycleSchema, updateIssueSchema, updateLabelSchema, updateMemberSchema, updateProjectSchema, updateStateSchema, getMemberSchema, } from './schemas/tools.js'; import { serverConfig } from "./config/server-config.js"; import { client } from './shared.js'; dotenv.config(); // 创建服务器实例 const server = new Server({ name: serverConfig.name, version: serverConfig.version, }, serverConfig); // 设置工具处理器 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ listProjectsSchema, getProjectSchema, listIssuesSchema, getIssueSchema, listLabelsSchema, listCommentsSchema, listStatesSchema, createCommentSchema, createIssueSchema, createProjectSchema, createLabelSchema, createStateSchema, updateCommentSchema, updateIssueSchema, updateProjectSchema, updateLabelSchema, updateStateSchema, deleteCommentSchema, deleteIssueSchema, deleteProjectSchema, deleteLabelSchema, deleteStateSchema, createCycleSchema, updateCycleSchema, deleteCycleSchema, createMemberSchema, updateMemberSchema, deleteMemberSchema, listMembersSchema, getMemberSchema, listCyclesSchema, getCycleSchema, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (!request || !request.params) { throw new McpError(ErrorCode.InvalidRequest, "Invalid request format"); } const { name, arguments: input = {} } = request.params; if (!name) { throw new McpError(ErrorCode.InvalidRequest, "Tool name is required"); } try { let result; switch (name) { case "list_projects": result = await client.getProjects(); const projects = result.results.map((project) => ({ id: project.id, name: project.name, identifier: project.identifier, description: project.description, created_at: project.created_at, updated_at: project.updated_at, })); return { content: [{ type: "text", text: JSON.stringify(projects, null, 2), }], }; case "get_project": result = await client.getProject(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "list_issues": result = await client.getIssues(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "get_issue": result = await client.getIssue(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "list_labels": result = await client.getLabels(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "list_comments": result = await client.getComments(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "list_states": result = await client.getStates(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_comment": result = await client.createComment(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_issue": result = await client.createIssue(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_project": result = await client.createProject(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_label": result = await client.createLabel(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_state": result = await client.createState(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_comment": result = await client.updateComment(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_issue": result = await client.updateIssue(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_project": result = await client.updateProject(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_label": result = await client.updateLabel(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_state": result = await client.updateState(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_comment": result = await client.deleteComment(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_issue": result = await client.deleteIssue(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_project": result = await client.deleteProject(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_label": result = await client.deleteLabel(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_state": result = await client.deleteState(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_cycle": result = await client.createCycle(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_cycle": result = await client.updateCycle(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_cycle": result = await client.deleteCycle(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "list_members": result = await client.getMembers(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "create_member": result = await client.createMember(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "update_member": result = await client.updateMember(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; case "delete_member": result = await client.deleteMember(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2), }], }; default: throw new McpError(ErrorCode.InvalidRequest, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) throw error; throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error}`); } } catch (error) { if (error instanceof McpError) throw error; throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error}`); } }); // 设置资源处理器 const resourceHandlers = getResourceHandlers(); server.setRequestHandler(ListResourcesRequestSchema, resourceHandlers.listResources); server.setRequestHandler(ReadResourceRequestSchema, resourceHandlers.readResource); if (process.env.ENABLE_UNSAFE_SSE_TRANSPORT) { startSSEServer(server); } else { const transport = new StdioServerTransport(); await server.connect(transport); } // 处理进程信号以优雅关闭 ["SIGINT", "SIGTERM"].forEach(signal => { process.on(signal, () => { console.log(`Received ${signal}, shutting down...`); server.close() .then(() => process.exit(0)) .catch(error => { console.error('Error during shutdown:', error); process.exit(1); }); }); });