openproject-mcp
An MCP server for OpenProject that enables creating, updating, commenting on work packages, and logging time, plus read operations for projects, work packages, users, and activities. Zero-dependency, stdio transport.
README
openproject-mcp
An MCP server over the OpenProject APIv3 — work packages, comments, time entries, users, and activities. Zero dependencies, stdio transport, one file.
Why this exists
OpenProject ships its own MCP server as of 17.2, but it is an Enterprise add-on (Professional, Premium, Corporate) and is read-only: "Right now OpenProject only offers read-only tools." That rules it out for the job this server does, which is creating and updating work packages.
The community alternatives were surveyed before writing this:
| Server | Stack | Stars | Comments | Time entries | Blocker |
|---|---|---|---|---|---|
| AndyEverything | Python + uv | 72 | ❌ | ✅ | "Do not use it productively"; 5 delete tools |
| brunofin | TypeScript | 1 | ✅ | ❌ | 4 commits total |
| Tangaratta | Python + FastMCP | 14 | ❌ | ❌ | SSE-only, fixed port |
None combined comments with time entries, and all three carry immaturity warnings. Hence 13 tools, tightly scoped, no delete tools at all.
Tools
Read — op_list_projects, op_list_work_packages, op_get_work_package, op_list_types,
op_list_statuses, op_list_time_entry_activities, op_list_time_entries, op_list_users,
op_list_activities
Write — op_create_work_package, op_update_work_package, op_comment_work_package,
op_log_time
Lists stay slim — id, subject, status, and the like. op_get_work_package is the one that is
genuinely full: description, custom fields, priority, version, the
spentTime/estimatedTime/remainingTime aggregates, and createdAt.
op_list_projects and op_list_work_packages page via pageSize (capped at 200) and offset,
returning total/offset/limit; op_list_time_entries takes pageSize too. On
op_list_work_packages, project: "all" searches across every project even when
OPENPROJECT_PROJECT_ID is set.
op_list_users exists to resolve assignee ids — they feed the assignee argument of create/update.
op_list_activities reads comments back, the read-your-comments counterpart to
op_comment_work_package.
Writes default to notify=false — op_log_time doesn't even expose the flag — so bulk agent
activity does not email the whole project. op_update_work_package fetches lockVersion itself, so
a concurrent edit fails loudly instead of silently overwriting; parent: null on an update clears
the parent link. Arguments are validated up front: ids must be positive integers, dates must be
YYYY-MM-DD, and unknown statuses/activities/ids fail with messages that name the offending value.
Configuration
Resolution order:
OPENPROJECT_ENV_FILE— path to a.envto readOPENPROJECT_*from. Values in an explicitly named.envwin over the ambient environment. Deliberate: a stale exportedOPENPROJECT_API_KEYsilently shadowing a freshly rotated one in.envcost hours of debugging — the API reports it as401 You did not provide the correct credentials, indistinguishable from a bad key. Naming a file is a deliberate act; an inherited variable usually is not.OPENPROJECT_URL(orOPENPROJECT_BASE_URL) +OPENPROJECT_API_KEY+OPENPROJECT_PROJECT_IDfrom the environment~/.config/openproject-mcp/config.json—{"url": "...", "apiKey": "...", "defaultProject": "..."}(consulted only when a URL or key is still missing)
OPENPROJECT_PROJECT_ID sets the default project, so most calls need no project argument.
Auth is HTTP Basic with the literal username apikey and the key as password. The key is never
written to stdout, stderr, or any tool result — and the URL must be https (plain http is
rejected unless the host is localhost or 127.0.0.1), because the key must not travel in the
clear.
OPENPROJECT_TIMEOUT_MS sets the per-request timeout (default 30000). Transient failures (429, 502,
503, 504) are retried twice with backoff, honoring Retry-After — GETs only; writes are never
retried, so a timed-out create is not silently replayed.
.env and config.json should be chmod 600 — the server warns on stderr when either is readable
by others. An unreadable or missing OPENPROJECT_ENV_FILE prints a stderr warning and falls through
to the remaining sources instead of failing silently.
Install
Requires Node.js ≥ 18. The server is a single self-contained file with no dependencies — no build step, nothing to compile. Install it in one line, then register it with your client below.
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/DDeluca06/openproject-mcp/master/scripts/install.sh | bash
Windows (PowerShell):
irm https://raw.githubusercontent.com/DDeluca06/openproject-mcp/master/scripts/install.ps1 | iex
Both download server.mjs into ~/.local/bin (Windows adds an openproject-mcp.cmd shim, since
shebangs don't work there) and print the next steps. If ~/.local/bin isn't on your PATH
(macOS and Windows by default), the installer tells you how to add it:
export PATH="$HOME/.local/bin:$PATH" # put this line in ~/.zshrc or ~/.bashrc
GUI-launched apps (VS Code, some terminals) inherit a minimal PATH — if your client can't find
openproject-mcp, use the absolute path the installer printed.
Alternative (npm, all three platforms) — installs the same one file with a proper bin entry:
npm install -g --allow-remote https://github.com/DDeluca06/openproject-mcp/tarball/master
--allow-remote is required on npm ≥ 12, which blocks GitHub tarballs by default; older npm
versions ignore the flag.
Either way, verify the server starts: openproject-mcp should run and wait on stdin.
Claude Code
claude mcp add openproject -s user \
-e OPENPROJECT_URL=https://projects.example.com \
-e OPENPROJECT_PROJECT_ID=your-project-identifier \
-e OPENPROJECT_ENV_FILE=/path/to/.env \
-- openproject-mcp
The snippet sets both OPENPROJECT_URL and OPENPROJECT_ENV_FILE; if the .env also holds
OPENPROJECT_URL, the -e value is ignored — deliberate, so a freshly rotated .env key wins.
OpenCode
In ~/.config/opencode/opencode.json, under the top-level mcp key — not nested under
mcp.servers, which fails schema validation and silently disables every server in the file:
{
"mcp": {
"openproject": {
"type": "local",
"command": ["openproject-mcp"],
"enabled": true,
"environment": {
"OPENPROJECT_URL": "https://projects.example.com",
"OPENPROJECT_PROJECT_ID": "your-project-identifier",
"OPENPROJECT_ENV_FILE": "/path/to/.env"
}
}
}
}
Verify with claude mcp get openproject and opencode mcp list.
Tests
npm test # mock suite (no credentials needed) + live smoke test
npm test -- --writes # smoke also creates a real work package, comment, and time entry
npm test runs test/mock.test.mjs (a deterministic fake APIv3 server, 59 checks, no
credentials) and then test/smoke.mjs against the live instance; the smoke test self-skips when
OPENPROJECT_URL is unset. --writes creates a real work package, comment, and time entry — the
work package is clearly marked as a smoke test, then permanently deleted through the APIv3 DELETE
endpoint after the run (its time entries go with it). The MCP tool surface itself still has no
delete tool — the test cleanup bypasses it on purpose. If the API key lacks the delete permission
the deletion is skipped loudly (DELETE 403), and the work package is left closed for manual
cleanup; pass --keep to always leave the records in place.
APIv3 quirks this server absorbs
Each of these was found by a failing call, not from the docs:
- A parentless work package still returns
parent: {href: null}rather than omitting the link, so naiveparent.href.split()throws. time_entriesrejects a project identifier and demands the numeric id, unlike thework_packagesendpoints which take either. Resolved and cached internally.- The work-package filter on
time_entriesisentity, notwork_package— the resource was generalised to attach to meetings too, and the old filter name no longer exists. TimeEntriesActivityhas no collection endpoint. The server first asks/api/v3/time_entries/schemaforactivity.allowedValues; instances that omit it fall back to probing/api/v3/time_entries/activities/{id}in parallel batches of 12, stopping after the first fully-missed batch (capped at id 120). The set is cached for 30 minutes. That plural/time_entries/activities/{id}path is an undocumented alias — it works, but it is not in the API docs.hoursis an ISO-8601 duration (PT1H30M; day components likeP1DT2Hparse too).op_log_timeaccepts decimal hours and converts; reads return both forms plus a decimal total. A duration that will not parse comes back ashoursDecimal: nullwith aparseWarningscount inop_list_time_entries— not silently as 0.op_log_time's defaultspentOnis the local calendar date, not UTC — "today" in the server's timezone.- Time entries are always attributed to the API key's own user. OpenProject makes that field read-only, so time cannot be logged on someone else's behalf.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.