Skip to main content
Glama
liratanak

Tonle OpenProject MCP Server

by liratanak
ARCHITECTURE.md16.9 kB
# Tonle OpenProject MCP Server Architecture This document describes the architecture and detailed behaviour of the Tonle OpenProject MCP server. ## Goals - Expose the full OpenProject API v3 as MCP tools. - Provide a simple, predictable tool schema for AI assistants. - Support both local (stdio) and remote (HTTP) transports. - Be production-ready and easy to extend. ## High-level architecture ```text ┌─────────────────────────────────────────────────────────────┐ │ MCP-Compatible Clients │ │ (Claude Desktop, Cursor, Windsurf, etc.) │ └─────────────────────────────────────────────────────────────┘ │ │ MCP Protocol (JSON-RPC 2.0) │ ┌─────────────────────────────────────────────────────────────┐ │ OpenProject MCP Server │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ Transport Layer ││ │ │ (Stdio / Streamable HTTP / WebSocket) ││ │ └─────────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────────┐│ │ │ MCP Server Core ││ │ │ (@modelcontextprotocol/sdk TypeScript SDK) ││ │ └─────────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────────┐│ │ │ Tool Registry ││ │ │ (Tool definitions, schemas, handlers) ││ │ └─────────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────────┐│ │ │ Resource Provider ││ │ │ (Dynamic data exposure for projects, users, etc.) ││ │ └─────────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────────┐│ │ │ OpenProject API Client ││ │ │ (HAL+JSON API v3 communication layer) ││ │ └─────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────┘ │ │ HTTPS (HAL+JSON) │ ┌─────────────────────────────────────────────────────────────┐ │ OpenProject Instance │ │ (API v3 Endpoints) │ └─────────────────────────────────────────────────────────────┘ ``` ## Core technologies | Component | Technology | Purpose | |---------------|----------------------------------|------------------------------------------| | Runtime | Bun | Server execution environment | | Language | TypeScript 5.x | Type-safe development | | MCP SDK | `@modelcontextprotocol/sdk` | MCP protocol implementation | | Validation | Zod | Runtime schema validation | | HTTP Client | Axios/Fetch | OpenProject API communication | | Testing | Vitest | Unit and integration testing | | Documentation | TypeDoc | API documentation generation | ## Transports ### Stdio (default) - Used by desktop/CLI clients that spawn the server as a subprocess. - Command: `bun run index.ts` - Environment: `OPENPROJECT_URL`, `OPENPROJECT_API_KEY`, optional `OPENPROJECT_TIMEOUT`. ### HTTP / Streamable HTTP - Optional HTTP server to expose MCP over HTTP/SSE for remote clients. - Typical start command: ```bash # default port 3100 bun run start:http # custom port MCP_HTTP_PORT=8080 bun run start:http ``` - Exposes `/mcp` (JSON-RPC over HTTP + SSE) and `/health`. ## MCP implementation model ### Tools Each OpenProject API operation is wrapped as an MCP tool with: - A descriptive name (e.g. `create_work_package`). - A Zod `inputSchema` matching the OpenProject payload. - A Zod `outputSchema` describing the key response fields. - A handler that calls the OpenProject client and returns MCP content. Example: ```typescript import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; server.registerTool( 'create_work_package', { title: 'Create Work Package', description: 'Creates a new work package in OpenProject with specified properties', inputSchema: { project_id: z.number().describe('The ID of the project'), subject: z.string().describe('The subject/title of the work package'), type_id: z.number().optional().describe('The work package type ID'), description: z.string().optional().describe('Detailed description (supports markdown)'), assignee_id: z.number().optional().describe('User ID of the assignee'), priority_id: z.number().optional().describe('Priority ID'), start_date: z.string().optional().describe('Start date (ISO 8601)'), due_date: z.string().optional().describe('Due date (ISO 8601)'), estimated_hours: z.number().optional().describe('Estimated hours'), notify: z.boolean().optional().default(true).describe('Send notifications'), }, outputSchema: { id: z.number(), subject: z.string(), project: z.object({ id: z.number(), name: z.string() }), status: z.object({ id: z.number(), name: z.string() }), _links: z.object({ self: z.object({ href: z.string() }), }), }, }, async (params) => { const response = await openProjectClient.createWorkPackage(params); return { content: [ { type: 'text', text: JSON.stringify(response, null, 2), }, ], }; } ); ``` The goal is to provide one tool per OpenProject API v3 endpoint whenever practical. ### Resources Read-only data snapshots (projects, users, etc.) are exposed as MCP resources: ```typescript server.resource( 'projects', 'openproject://projects', { name: 'OpenProject Projects', description: 'List of all accessible projects', mimeType: 'application/json', }, async () => { const projects = await openProjectClient.listProjects(); return { contents: [ { uri: 'openproject://projects', mimeType: 'application/json', text: JSON.stringify(projects, null, 2), }, ], }; } ); ``` ### Prompt templates Reusable prompt templates help clients drive common workflows: ```typescript server.prompt( 'create_sprint_backlog', { name: 'Create Sprint Backlog', description: 'Template for creating work packages for a sprint', arguments: [ { name: 'project_id', description: 'Project ID', required: true }, { name: 'sprint_name', description: 'Sprint name', required: true }, { name: 'tasks', description: 'JSON array of task titles', required: true }, ], }, async ({ project_id, sprint_name, tasks }) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Create work packages in project ${project_id} for sprint "${sprint_name}" with these tasks: ${tasks}`, }, }, ], }; } ); ``` ## Configuration & environment - OpenProject connection is configured via: - `OPENPROJECT_URL` – base URL of your OpenProject instance. - `OPENPROJECT_API_KEY` – API key or access token. - `OPENPROJECT_TIMEOUT` – optional request timeout in ms (default 30000). - For local development, you can use a `.env` file and let Bun load it. ## Custom MCP clients For custom TypeScript/Node/Bun MCP clients: ### Stdio transport ```typescript import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; const transport = new StdioClientTransport({ command: 'bun', args: ['run', '/path/to/tonle/index.ts'], env: { OPENPROJECT_URL: 'https://your-instance.openproject.com', OPENPROJECT_API_KEY: 'your-api-key', }, }); const client = new Client({ name: 'my-mcp-client', version: '1.0.0', }); await client.connect(transport); const tools = await client.listTools(); ``` ### HTTP transport ```typescript import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; const transport = new StreamableHTTPClientTransport( new URL('http://localhost:3100/mcp') ); const client = new Client({ name: 'my-http-client', version: '1.0.0', }); await client.connect(transport); const tools = await client.listTools(); ``` ## Security & error handling - Uses HTTPS to talk to OpenProject. - API keys are provided only via environment variables. - Operations respect OpenProject permissions; the server never elevates privileges. - Errors from OpenProject are normalised into a small set of error identifiers such as: ```typescript const ERROR_CODES = { NOT_FOUND: 'urn:openproject-org:api:v3:errors:NotFound', UNAUTHORIZED: 'urn:openproject-org:api:v3:errors:Unauthenticated', FORBIDDEN: 'urn:openproject-org:api:v3:errors:MissingPermission', VALIDATION: 'urn:openproject-org:api:v3:errors:PropertyConstraintViolation', CONFLICT: 'urn:openproject-org:api:v3:errors:UpdateConflict', INVALID_BODY: 'urn:openproject-org:api:v3:errors:InvalidRequestBody', }; ``` Transient errors can be retried with backoff; user-facing messages are kept clear and actionable. ## Roadmap (abridged) - **Phase 1 – Core**: API client, work packages & projects CRUD, API-key auth, stdio transport. - **Phase 2 – Coverage**: Remaining endpoints, OAuth2, resources, prompts, HTTP transport. - **Phase 3 – Enterprise**: Rate limiting, caching, audit logging, multi-instance, SSO. - **Phase 4 – Advanced**: Webhooks, real-time notifications, batch operations, AI-assisted planning prompts. ## OpenProject API v3 Endpoint Coverage The MCP server provides tools for all OpenProject API v3 endpoints, organized into 41 categories: ### Core Categories 1. **Work Packages** - Tasks, features, bugs, milestones (CRUD + watchers, assignees, custom actions) 2. **Projects** - Project management (CRUD, copy, statuses) 3. **Users & Authentication** - User management (CRUD, lock/unlock, current user) 4. **Groups** - Group management (CRUD) 5. **Principals** - Users, groups, placeholder users 6. **Memberships** - Project memberships with roles 7. **Roles** - Permission roles 8. **Time Entries** - Time tracking (CRUD) 9. **Time Entry Activities** - Activity types for time entries 10. **Activities (Journal Entries)** - Work package history and comments ### Content & Documents 11. **Attachments** - File attachments (upload, download, link to work packages/activities) 12. **News** - News articles 13. **Posts** - Forum messages 14. **Wiki Pages** - Wiki content (read, update) 15. **Documents** - Document management 16. **File Links & Storages** - External file integration (Nextcloud, OneDrive/SharePoint) ### Organization & Configuration 17. **Versions** - Releases/milestones with sharing scopes 18. **Categories** - Work package categories 19. **Types** - Work package types (Task, Bug, Feature, etc.) 20. **Statuses** - Work package statuses (New, In Progress, Done, etc.) 21. **Priorities** - Priority levels 22. **Relations** - Work package relationships (blocks, precedes, relates, etc.) ### Views & Queries 23. **Queries** - Saved filters (CRUD, star/unstar) 24. **Query Filters** - Available filter definitions 25. **Query Columns** - Available column definitions 26. **Query Operators** - Filter operators (=, !=, <, >, etc.) 27. **Query Sort Bys** - Sort options 28. **Views** - Work packages table, team planner, Gantt views ### Notifications & Schedules 29. **Notifications** - In-app notifications (read/unread status) 30. **Work Schedule** - Days, week days, non-working days ### Budgets & Revisions 31. **Budgets** - Project budgets 32. **Revisions** - Repository commits linked to work packages ### Grids & Dashboards 33. **Grids** - Dashboard layouts with widgets ### Advanced Features 34. **Actions & Capabilities** - User capabilities 35. **Custom Actions** - Execute custom workflow actions 36. **Custom Options** - Custom field value options 37. **Help Texts** - Attribute help texts 38. **Project Phases & Definitions** - Project phase management 39. **Schemas** - Entity schemas 40. **User Preferences** - Current user preferences ### System & Configuration 41. **Root & Configuration** - API root, instance configuration (attachment limits, feature flags) 42. **OAuth 2.0** - OAuth application management 43. **Previewing** - Markdown and plain text rendering For detailed endpoint documentation, see: https://www.openproject.org/docs/api/ ## Deployment Options ### Docker Deployment ```dockerfile FROM oven/bun:1 WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --production COPY . . ENV OPENPROJECT_URL="" ENV OPENPROJECT_API_KEY="" EXPOSE 3100 CMD ["bun", "run", "start:http"] ``` Build and run: ```bash docker build -t tonle-mcp . docker run -p 3100:3100 \ -e OPENPROJECT_URL=https://your-instance.openproject.com \ -e OPENPROJECT_API_KEY=your-api-key \ tonle-mcp ``` ### Kubernetes Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: tonle-mcp spec: replicas: 3 selector: matchLabels: app: tonle-mcp template: metadata: labels: app: tonle-mcp spec: containers: - name: mcp-server image: your-registry/tonle-mcp:latest ports: - containerPort: 3100 env: - name: OPENPROJECT_URL valueFrom: secretKeyRef: name: openproject-secrets key: url - name: OPENPROJECT_API_KEY valueFrom: secretKeyRef: name: openproject-secrets key: api-key --- apiVersion: v1 kind: Service metadata: name: tonle-mcp spec: selector: app: tonle-mcp ports: - port: 3100 targetPort: 3100 ``` ### Process Manager (PM2) ```bash pm2 start "bun run start:http" --name tonle-mcp pm2 save pm2 startup ``` ## References - OpenProject docs: `https://www.openproject.org/docs/` - OpenProject API reference: `https://www.openproject.org/docs/api/` - MCP spec: `https://spec.modelcontextprotocol.io/` - MCP TypeScript SDK: `https://github.com/modelcontextprotocol/typescript-sdk`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/liratanak/openproject-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server