Plane MCP
# Plane MCP
A [Model Context Protocol](https://modelcontextprotocol.io) server for **[Plane](https://plane.so)** (self-hosted or Cloud). It lets an LLM client — Claude Desktop, Claude Code, Cursor, etc. — fully drive your Plane workspace: discover projects, and create / read / update / delete / re-organize work items, states, labels, cycles, modules, and comments.
> Verified against a self-hosted instance at `plane.nzminds.com` (workspace `nzmt`).
## What it can do
| Resource | Tools |
|---|---|
| **Projects** | `list_projects`, `find_project`, `get_project`, `create_project`, `update_project`, `delete_project` |
| **Work items** | `list_work_items`, `get_work_item`, `get_work_item_by_number`, `search_work_items`, `create_work_item`, `update_work_item`, `set_work_item_status`, `delete_work_item` |
| **States (statuses)** | `list_states`, `find_state`, `create_state`, `update_state`, `delete_state` |
| **Labels** | `list_labels`, `create_label`, `update_label`, `delete_label` |
| **Cycles (sprints)** | `list_cycles`, `get_cycle`, `create_cycle`, `update_cycle`, `delete_cycle`, `list_cycle_work_items`, `add_work_items_to_cycle`, `remove_work_item_from_cycle` |
| **Modules** | `list_modules`, `get_module`, `create_module`, `update_module`, `delete_module`, `list_module_work_items`, `add_work_items_to_module`, `remove_work_item_from_module` |
| **Comments** | `list_comments`, `add_comment`, `update_comment`, `delete_comment` |
| **Members** | `list_members`, `find_member` |
## How Plane's model works (quick mental model)
```
Workspace (slug, e.g. "nzmt")
└─ Project (UUID) ← discover with list_projects
├─ Work Items / Issues ← the tickets; have a UUID + human number (TRAVELXS-391)
├─ States ← the statuses (Backlog/Todo/In Progress/Done…), each a UUID
├─ Labels ← UUIDs
├─ Cycles ← time-boxed sprints
└─ Modules ← feature groupings
```
**Everything in a request body is a UUID, not a name.** So the normal flow is:
1. `list_projects` → pick a project UUID.
2. `list_states` → map "In Progress" → its UUID. `list_members` / `list_labels` likewise.
3. Create/update the work item. **To change status, set `state` to the target state UUID** (or use `set_work_item_status`).
Most tools take an optional `project_id`; if omitted they fall back to `PLANE_DEFAULT_PROJECT_ID` from your `.env`.
## Setup
### 1. Get an API key
See **[SETUP.md](SETUP.md)** for step-by-step instructions (Profile Settings → Personal Access Tokens).
### 2. Configure
```bash
cp .env.example .env
# edit .env: PLANE_BASE_URL, PLANE_API_KEY, PLANE_WORKSPACE_SLUG
```
### 3. Install
Requires Python ≥ 3.10.
```bash
python -m pip install -e .
```
(or with [uv](https://docs.astral.sh/uv/): `uv pip install -e .`)
### 4. Run
```bash
plane-mcp # or: python -m plane_mcp
```
It speaks MCP over **stdio**, so you normally don't run it by hand — your MCP client launches it.
## Connecting to a client
### Claude Desktop / Claude Code
Add to your MCP config (`claude_desktop_config.json`, or via `claude mcp add`):
```json
{
"mcpServers": {
"plane": {
"command": "plane-mcp",
"env": {
"PLANE_BASE_URL": "https://plane.nzminds.com",
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "nzmt"
}
}
}
}
```
Or, if you'd rather rely on the `.env` file, drop the `env` block and use the absolute interpreter:
```json
{
"mcpServers": {
"plane": {
"command": "python",
"args": ["-m", "plane_mcp"],
"cwd": "d:/Govind/Plane MCP"
}
}
}
```
Claude Code one-liner:
```bash
claude mcp add plane -- python -m plane_mcp
```
## Example prompts once connected
- "List my projects in Plane."
- "In TravelXS, create a high-priority bug 'Login fails on Safari' and assign it to govind.kumar."
- "Move TRAVELXS-391 to In Progress."
- "Create a cycle 'Sprint 12' from 2026-07-01 to 2026-07-14 and add TRAVELXS-394 and -376 to it."
- "Add a comment to TRAVELXS-308 saying the fix is deployed."
## Notes & limits
- **Rate limit:** 60 requests/min per key. The client auto-retries on `429` with backoff.
- **Pagination:** list tools follow Plane's cursor pagination and return *all* results.
- **Destructive ops:** the hard `delete_*` tools (project, work item, state, label, cycle, module, comment) are **disabled by default**. Set `PLANE_ALLOW_DELETE=true` in `.env` to expose them. Relationship removals (`remove_work_item_from_cycle` / `_module`) are always available since they're reversible.
- **Self-hosted path quirk:** cycle/module work-item sub-resources are `/cycle-issues/` and `/module-issues/` (the public docs say `work_items`, which 404s on self-hosted). This server uses the verified paths.
## Project layout
```
src/plane_mcp/
├── config.py # env loading & validation
├── client.py # async HTTP client: pagination, 429 retry, errors
├── server.py # FastMCP instance + tool registration
├── __main__.py # entry point (stdio)
└── tools/ # one module per resource
├── projects.py issues.py states.py labels.py
├── cycles.py modules.py comments.py members.py
```
## License
MIT
TDQS
Scored across 38 tools
Tools are generally distinct, with clear separation via resource type (project, work item, cycle, module, comment, label, member, state) and action. The main overlap is set_work_item_status being a convenience wrapper around update_work_item, but descriptions clarify the purpose. Some pairs like list_projects/find_project could be confused but are well-defined.
Names follow a consistent verb_noun pattern (list_*, get_*, create_*, update_*, find_*, add_*, remove_*). Variations like get_work_item_by_number and set_work_item_status are logically extended forms, not inconsistent. The convention is uniform across all 38 tools.
With 38 tools, the server is well over the 25+ threshold that typically indicates too many. While the scope is broad (multiple project management entities), the set includes many similar list/get/update tools per resource, making it feel heavy. A more streamlined set could consolidate some operations.
The server covers create, read, and update for most resources, plus work item status changes and assignment to cycles/modules. However, there are no delete operations for any entity (projects, work items, labels, cycles, modules, comments, states), which is a notable gap in CRUD coverage. Also missing get_label and get_comment, but these are less critical.