Plane MCP Server
An MCP server that exposes the Plane REST API to AI clients (Claude, Cursor, VS Code, Codex, …). Built with FastMCP.
It authenticates as a Plane personal access token and acts on a single workspace, giving the model tools to read and manage projects, work items, states, labels, cycles, modules, members and comments.
Requirements
- Python 3.10+
- A Plane account and a personal access token: Plane → Profile settings → Personal access tokens → Add personal access token
- Your workspace slug — the segment in your Plane URL:
https://app.plane.so/<workspace-slug>/projects/
Install
From PyPI — package plane-mcp-oss:
uvx plane-mcp-oss # run without installing
# or
pip install plane-mcp-oss # installs the `plane-mcp` and `plane-mcp-oss` commands
From source:
uv sync # installs fastmcp + httpx into .venv
# or, without uv:
pip install -e .
Configure
The server targets one Plane instance and one workspace. Settings are resolved with the following precedence (highest wins):
- CLI flags (
--base-url,--workspace,--api-key) - Process environment variables
- A
.envfile in the working directory (loaded automatically) - Built-in defaults
| Variable | Alias | Required | Default | Purpose |
|---|---|---|---|---|
PLANE_API_KEY |
PLANE_TOKEN |
yes* | — | Personal access token, sent as X-API-Key. |
PLANE_OAUTH_TOKEN |
— | yes* | — | OAuth access token, sent as Authorization: Bearer …. |
PLANE_WORKSPACE_SLUG |
PLANE_WORKSPACE |
yes | — | Target workspace slug. |
PLANE_BASE_URL |
PLANE_URL |
no | https://api.plane.so |
Plane instance URL. |
PLANE_TIMEOUT |
— | no | 30 |
Request timeout (seconds). |
* One of PLANE_API_KEY / PLANE_OAUTH_TOKEN is required.
Pointing at a self-hosted instance
PLANE_BASE_URL accepts whatever you copy from your browser. The /api/v1
suffix is added automatically when needed:
PLANE_BASE_URL=https://api.plane.so # Plane Cloud (default)
PLANE_BASE_URL=https://plane.example.com # self-hosted
PLANE_BASE_URL=https://example.com/plane # self-hosted behind a subpath
PLANE_BASE_URL=https://plane.example.com/api/v1 # already versioned
CLI flags
plane-mcp --base-url https://plane.example.com/plane \
--workspace my-team \
--api-key plane_api_xxxx
Check what the server resolved — without leaking the token:
$ plane-mcp --show-config --base-url https://plane.example.com/plane --workspace my-team --api-key xxx
{
"base_url": "https://plane.example.com/plane/api/v1",
"workspace_slug": "my-team",
"auth": "api_key",
"timeout": 30.0
}
Copy .env.example for a template; a .env file is loaded from the working
directory (change it with --env-file, or pass --env-file '' to skip).
Run
# stdio — how MCP clients launch it locally
PLANE_API_KEY=... PLANE_WORKSPACE_SLUG=my-team uv run plane-mcp
# streamable HTTP
PLANE_API_KEY=... PLANE_WORKSPACE_SLUG=my-team uv run plane-mcp --transport http --port 8000
python -m plane_mcp and python main.py are equivalent entry points.
Client setup
Every example below runs the published package with uvx plane-mcp-oss, so
nothing needs to be installed first. For a self-hosted instance, add
PLANE_BASE_URL to the same environment (https://plane.example.com).
Claude Code
claude mcp add plane \
-e PLANE_API_KEY=plane_api_xxxxxxxx \
-e PLANE_WORKSPACE_SLUG=my-team \
-- uvx plane-mcp-oss
# add --scope user to make it available in every project (default is "local")
claude mcp list
Or commit a project-scoped .mcp.json so the whole team gets it:
{
"mcpServers": {
"plane": {
"command": "uvx",
"args": ["plane-mcp-oss"],
"env": {
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "my-team"
}
}
}
}
OpenAI Codex
codex mcp add plane \
--env PLANE_API_KEY=plane_api_xxxxxxxx \
--env PLANE_WORKSPACE_SLUG=my-team \
-- uvx plane-mcp-oss
codex mcp list
Codex writes this to ~/.codex/config.toml:
[mcp_servers.plane]
command = "uvx"
args = ["plane-mcp-oss"]
[mcp_servers.plane.env]
PLANE_API_KEY = "plane_api_xxxxxxxx"
PLANE_WORKSPACE_SLUG = "my-team"
phoson-cli
Add an entry to ~/.phoson/mcps.json under mcpServers:
{
"mcpServers": {
"plane": {
"command": "uvx",
"args": ["plane-mcp-oss"],
"env": {
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "my-team"
},
"enabled": true
}
}
}
Cursor
~/.cursor/mcp.json (global) or .cursor/mcp.json (per project):
{
"mcpServers": {
"plane": {
"command": "uvx",
"args": ["plane-mcp-oss"],
"env": {
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "my-team"
}
}
}
}
VS Code
.vscode/mcp.json (note the servers key and type):
{
"servers": {
"plane": {
"type": "stdio",
"command": "uvx",
"args": ["plane-mcp-oss"],
"env": {
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "my-team"
}
}
}
}
VS Code can prompt for the token instead of storing it — add an inputs entry
and reference it as ${input:plane_api_key}.
Claude Desktop
claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"plane": {
"command": "uvx",
"args": ["plane-mcp-oss"],
"env": {
"PLANE_API_KEY": "plane_api_xxxxxxxx",
"PLANE_WORKSPACE_SLUG": "my-team"
}
}
}
}
Remote / HTTP transport
Any client that supports remote MCP servers can connect over HTTP instead of spawning a process:
PLANE_API_KEY=... PLANE_WORKSPACE_SLUG=my-team plane-mcp-oss --transport http --port 8000
# endpoint: http://127.0.0.1:8000/mcp
Clients without native remote support can bridge to it with mcp-remote:
{
"mcpServers": {
"plane": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8000/mcp"]
}
}
}
Keep tokens out of version control: prefer the client's secret input mechanism, or reference an environment variable your client expands, instead of committing a real key to
.mcp.json.
Tools
| Tool | What it does |
|---|---|
get_current_user |
Profile of the token's user. |
list_workspace_members |
Workspace members (to resolve assignee UUIDs). |
list_projects / get_project |
Browse projects (paginated). |
create_project / update_project |
Create or edit a project. |
list_work_items / get_work_item |
Browse work items in a project (paginated). |
get_work_item_by_identifier |
Look up e.g. PROJ-123 directly. |
search_work_items |
Text search across names/identifiers (works everywhere). |
advanced_search_work_items |
Filter-based search; permission-gated, may return 403. |
create_work_item / update_work_item / delete_work_item |
Manage work items. |
list_states |
Workflow states — get the UUID before setting state. |
list_labels / create_label |
Project labels. |
list_cycles / list_modules |
Sprints and modules. |
list_comments / add_comment / update_comment / delete_comment |
Work item comments. |
list_pages / get_page / create_page / update_page |
Pages — workspace wiki or project (omit/ pass project_id). |
archive_page / restore_page / delete_page |
Page lifecycle; delete requires archiving first. |
List tools return {results, count, total_results, next_cursor}; pass
next_cursor back to page through results.
Resources: plane://me, plane://projects, plane://projects/{id}/states.
Prompts: triage_work_items.
Notes on the API
- Work item
state,assigneesandlabelstake UUIDs, not names. Calllist_states/list_labels/list_workspace_membersfirst. descriptionis plain text and is converted to thedescription_htmlthe API expects;description_htmloverrides it when supplied.- Priority is one of
urgent,high,medium,low,none. - The API allows 60 requests/minute per key; the client surfaces
429as a tool error so the model can retry.
Architecture
src/plane_mcp/
├── config.py # env-driven Settings + validation
├── client.py # async httpx wrapper: auth, URLs, error translation, pagination
├── server.py # FastMCP instance, tool/resource/prompt definitions, CLI
└── __main__.py # `python -m plane_mcp`
tests/ # offline: httpx.MockTransport + in-memory FastMCP client
client.py has no FastMCP dependency, so it is reusable and easy to test; the
server layer only maps tools to client calls and turns PlaneAPIError into
ToolError for clean MCP error messages.
Known limitations
- Pages are Plane Cloud only. The public Pages REST API is not part of the
open-source Community Edition — it is absent from the API URL routing at
v1.3.1,v1.4.2andmaster(apps/api/plane/api/urls/registers asset, cycle, intake, label, member, module, project, state, user, work_item, invite and sticky — no pages). On a self-hosted instance, pages exist in the UI behind an internal session API (/api/…) that rejectsX-API-Keyand Bearer tokens, so the page tools will 404 there. They work against Plane Cloud, where the documented/api/v1/…/pages/routes exist. The page tools detect this and return an explanatory error rather than a bare 404. advanced_search_work_itemsis permission-gated on some workspaces and editions and can return403. Usesearch_work_itemsorlist_work_itemsas a fallback.- Not implemented yet: work item links, attachments, activity feed, and custom properties/types, though the Plane API supports them.
Development
uv run pytest # 12 offline tests, no credentials needed
Extending
Add a method to PlaneClient for the endpoint you need (see the
API reference), then
register a tool in server.py:
@mcp.tool
async def list_pages(project_id: str) -> dict[str, Any]:
"""List a project's pages."""
client = get_client()
data = await _call(client.request("GET", client._workspace("projects", project_id, "pages")))
return summarize_paginated(data)
Contributing
Contributions are welcome! See CONTRIBUTING.md for the development setup, how to add a tool, and commit/PR guidelines. This project follows the Contributor Covenant Code of Conduct.
Security
Please report vulnerabilities privately — see SECURITY.md. Never
commit real credentials; .env is git-ignored.
License
Released under the MIT License. © 2026 Abel Santillan Rodriguez.
See CHANGELOG.md for release history.
Release files for plane-mcp-oss 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| plane_mcp_oss-0.1.1.tar.gz | 145.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plane_mcp_oss-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 165.5 kB
Release files / plane_mcp_oss-0.1.1.tar.gz
| Download URL | plane_mcp_oss-0.1.1.tar.gz |
|---|---|
| Size | 145.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9269442858b7e7e18517cfe7211a89c65e50e568880f7ec1ff314cddb6a32df8
|
|
BLAKE2b-256 checksum How to use checksums |
52586f4f0cdbc65b40a0e46e2acbb9f261e58707dc93bbe333143a6bb7df4a5b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / plane_mcp_oss-0.1.1-py3-none-any.whl
| Download URL | plane_mcp_oss-0.1.1-py3-none-any.whl |
|---|---|
| Size | 20.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a96c1774f0d6894488f99ee49ae19e2b1778f2220e0aeb616e58c74336e7013f
|
|
BLAKE2b-256 checksum How to use checksums |
14284e474b81f7312408a704056d3ac56e0e50363184e6ce10e64d0e7a3d46dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log