#!/usr/bin/env node /** * OpenProject MCP Server * * Connects Claude to OpenProject via the v3 REST API. * * Environment variables: * OPENPROJECT_URL — Base URL of your OpenProject instance (e.g. https://op.example.com) * OPENPROJECT_API_KEY — API key (generated under My Account > Access Tokens) */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { OpenProjectClient } from "./client.js"; import { toolDefs } from "./tools.js"; import { createHandlers } from "./handlers.js"; function getEnvOrThrow(name: string): string { const value = process.env[name]; if (!value) { console.error(`Missing required environment variable: ${name}`); process.exit(1); } return value; } const client = new OpenProjectClient({ baseUrl: getEnvOrThrow("OPENPROJECT_URL"), apiKey: getEnvOrThrow("OPENPROJECT_API_KEY"), }); const handlers = createHandlers(client); const server = new McpServer({ name: "openproject", version: "0.1.0", }); // Register each tool from toolDefs with its corresponding handler. for (const [name, def] of Object.entries(toolDefs)) { const handler = handlers[name as keyof typeof handlers]; if (!handler) { console.error(`No handler for tool: ${name}`); continue; } server.tool( name, def.description, def.inputSchema, async (args: any) => { try { const result = await (handler as Function)(args); return { content: [{ type: "text" as const, text: String(result) }] }; } catch (err: any) { return { content: [{ type: "text" as const, text: `Error: ${err.message}` }], isError: true, }; } }, ); } async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("OpenProject MCP server running on stdio"); } main().catch((err) => { console.error("Fatal error:", err); process.exit(1); });