mcp-planes-server
Version:
Plane API client and server
347 lines (346 loc) • 14.5 kB
JavaScript
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);
});
});
});