Notion API MCP Server

by pbohannon
Verified
  • src
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, JSONRPCResponseSchema, JSONRPCResponse } from '@modelcontextprotocol/sdk/types.js'; // Import all tools import { workItemTools } from './tools/work-item/index.js'; import { boardTools } from './tools/board/index.js'; import { wikiTools } from './tools/wiki/index.js'; import { projectTools } from './tools/project/index.js'; import { pipelineTools } from './tools/pipeline/index.js'; import { pullRequestTools } from './tools/pull-request/index.js'; import { AzureDevOpsConfig, createConfig } from './config/environment.js'; import { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'; // TODO: Use the proper ToolDefinition type from MCP SDK // type ToolDefinition = ReturnType<typeof workItemTools.initialize>['definitions'][number]; type ToolDefinition = any; // TODO: Use a proper ToolInstace definition from MCP SDK //interface ToolInstances { // workItem: ReturnType<typeof workItemTools.initialize>; // board: ReturnType<typeof boardTools.initialize>; // wiki: ReturnType<typeof wikiTools.initialize>; // project: ReturnType<typeof projectTools.initialize>; // pipeline: ReturnType<typeof pipelineTools.initialize>; // pullRequest: ReturnType<typeof pullRequestTools.initialize>; //} type ToolInstances = any; // Type Validations function validateArgs<T>(args: Record<string, unknown> | undefined, errorMessage: string): T { if (!args) { throw new McpError(ErrorCode.InvalidParams, errorMessage); } return args as T; } type MCPResponse = JSONRPCResponse["result"] // Response Formatting function formatResponse(data: unknown): MCPResponse { if (data && typeof data === 'object' && 'content' in data) { return data as MCPResponse; } return { content: [ { type: 'text', text: JSON.stringify(data, null, 2), }, ], }; } class AzureDevOpsServer { private server: Server; private config: AzureDevOpsConfig; private toolDefinitions: ToolDefinition[]; constructor(options?: Partial<Omit<AzureDevOpsConfig, 'orgUrl'>>) { this.config = createConfig(options); // Initialize tools with config const toolInstances = { workItem: workItemTools.initialize(this.config), board: boardTools.initialize(this.config), wiki: wikiTools.initialize(this.config), project: projectTools.initialize(this.config), pipeline: pipelineTools.initialize(this.config), pullRequest: pullRequestTools.initialize(this.config), }; // Combine all tool definitions this.toolDefinitions = [ ...toolInstances.workItem.definitions, ...toolInstances.board.definitions, ...toolInstances.wiki.definitions, ...toolInstances.project.definitions, ...toolInstances.pipeline.definitions, ...toolInstances.pullRequest.definitions, ]; this.server = new Server( { name: 'azure-devops-server', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(toolInstances); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers(tools: ToolInstances) { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: this.toolDefinitions, })); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { try { let result; switch (request.params.name) { // Work Item Tools case 'get_work_item': result = await tools.workItem.getWorkItem(request.params.arguments); break; case 'list_work_items': result = await tools.workItem.listWorkItems(request.params.arguments); break; // Board Tools case 'get_boards': result = await tools.board.getBoards(request.params.arguments); break; // Wiki Tools case 'get_wikis': result = await tools.wiki.getWikis(request.params.arguments); break; case 'get_wiki_page': result = await tools.wiki.getWikiPage(request.params.arguments); break; case 'create_wiki': result = await tools.wiki.createWiki(request.params.arguments); break; case 'update_wiki_page': result = await tools.wiki.updateWikiPage(request.params.arguments); break; // Project Tools case 'list_projects': result = await tools.project.listProjects(request.params.arguments); break; // Pipeline Tools case 'list_pipelines': result = await tools.pipeline.getPipelines( validateArgs(request.params.arguments, 'Pipeline arguments required') ); break; case 'trigger_pipeline': result = await tools.pipeline.triggerPipeline( validateArgs(request.params.arguments, 'Pipeline trigger arguments required') ); break; // Pull Request Tools case 'list_pull_requests': result = await tools.pullRequest.getPullRequests( validateArgs(request.params.arguments, 'Pull request list arguments required') ); break; case 'get_pull_request': result = await tools.pullRequest.getPullRequest( validateArgs(request.params.arguments, 'Pull request ID required') ); break; case 'create_pull_request': result = await tools.pullRequest.createPullRequest( validateArgs(request.params.arguments, 'Pull request creation arguments required') ); break; case 'update_pull_request': result = await tools.pullRequest.updatePullRequest( validateArgs(request.params.arguments, 'Pull request update arguments required') ); break; default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } // Ensure consistent response format const response = formatResponse(result); return { _meta: request.params._meta, ...response }; } catch (error: unknown) { if (error instanceof McpError) throw error; const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new McpError( ErrorCode.InternalError, `Azure DevOps API error: ${errorMessage}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Azure DevOps MCP server running on stdio'); } } // Allow configuration through constructor or environment variables const server = new AzureDevOpsServer(); server.run().catch(console.error);