Unofficial MCP server for self-hosted Plane (Community Edition)
Project description
plane-ce-mcp
Unofficial MCP server for self-hosted Plane (Community Edition).
A Model Context Protocol server exposing Plane's project management API as MCP tools, built for people who run Plane themselves. Based on makeplane/plane-mcp-server (MIT). Not affiliated with Plane / makeplane.
Why this exists
The upstream server targets Plane Cloud. Run it against a self-hosted Community Edition instance and you hit missing "lite" endpoints, raw SDK stack traces on every 404, and no pages support. This project fixes what upstream didn't:
- Central compatibility layer (plane_mcp/compat.py): every Plane API error becomes an actionable message - missing endpoint vs missing resource vs auth vs timeout. No raw SDK exceptions reach the MCP client.
- Transparent endpoint fallbacks: lite endpoints absent on CE fall back to the full endpoints (projects, cycles, modules, project/workspace members), with the fallback logged for diagnosability.
- PAT-only HTTP mode: the HTTP transport runs without any OAuth configuration -
header auth alone (
/http/api-key/mcp). - Pages tools on CE: list/retrieve/search/create pages via Plane's internal API (CE has no public pages API), with content truncation for LLM-sized outputs.
- Documented CE vs Cloud differences: docs/plane-api-compat.md.
May still work against Plane Cloud - untested, unmaintained, not a goal.
Quick start
Requirements: Python 3.10+ and uv. No clone, no
install - uvx runs the server straight from GitHub.
1. Get your credentials from Plane:
- API key: Plane → Workspace Settings → API tokens → create token
- Workspace slug: the part of your Plane URL after the host
(
https://plane.your-domain.tld/<slug>/)
2. Smoke-test the server (optional, confirms connectivity):
PLANE_API_KEY=<your-api-key> \
PLANE_WORKSPACE_SLUG=<your-workspace-slug> \
PLANE_BASE_URL=https://plane.your-domain.tld \
uvx --from git+https://github.com/Bl4nk44/plane-ce-mcp plane-ce-mcp stdio
Server starts and waits for MCP messages on stdin - Ctrl+C to exit. Errors at this point mean wrong URL or key, not a client problem.
3. Add to your MCP client (Claude Code, Claude Desktop, Cursor, ...):
{
"mcpServers": {
"plane": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Bl4nk44/plane-ce-mcp", "plane-ce-mcp", "stdio"],
"env": {
"PLANE_API_KEY": "<your-api-key>",
"PLANE_WORKSPACE_SLUG": "<your-workspace-slug>",
"PLANE_BASE_URL": "https://plane.your-domain.tld"
}
}
}
}
For Claude Code that's one command:
claude mcp add plane \
-e PLANE_API_KEY=<your-api-key> \
-e PLANE_WORKSPACE_SLUG=<your-workspace-slug> \
-e PLANE_BASE_URL=https://plane.your-domain.tld \
-- uvx --from git+https://github.com/Bl4nk44/plane-ce-mcp plane-ce-mcp stdio
PLANE_BASE_URL must point at your instance's API - the default is Plane Cloud
(https://api.plane.so), so self-host setups must set it.
Run from source
git clone https://github.com/Bl4nk44/plane-ce-mcp.git
cd plane-ce-mcp
uv pip install -e .
PLANE_API_KEY=... PLANE_WORKSPACE_SLUG=... PLANE_BASE_URL=... python -m plane_mcp stdio
HTTP transport (self-hosted, PAT auth)
Run the server next to your Plane instance (port 8211):
PLANE_BASE_URL=https://plane.your-domain.tld python -m plane_mcp http
Connect with a Personal Access Token - no OAuth setup required:
URL: http://<server>:8211/http/api-key/mcp
Headers:
Authorization: Bearer <PAT_TOKEN>(orx-api-key: <PAT_TOKEN>)X-Workspace-slug: <SLUG>
{
"mcpServers": {
"plane": {
"command": "npx",
"args": ["mcp-remote@latest", "http://<server>:8211/http/api-key/mcp"],
"headers": {
"Authorization": "Bearer <PAT_TOKEN>",
"X-Workspace-slug": "<SLUG>"
}
}
}
}
An OAuth endpoint (/oauth/mcp) and a legacy SSE transport also exist for setups
with a configured Plane OAuth app - see Configuration. Deployment
recipes (Docker Compose, Caddy, Tailscale) live in deploy/ and
docs/tailscale-deployment.md.
Configuration
| Variable | Required for | Purpose |
|---|---|---|
PLANE_BASE_URL |
all (self-host: required) | Your Plane API URL (default: https://api.plane.so = Cloud) |
PLANE_API_KEY |
stdio | API key (workspace settings → API tokens) |
PLANE_WORKSPACE_SLUG |
stdio | Target workspace |
PLANE_INTERNAL_BASE_URL |
http/sse (optional) | Internal URL for server-to-server calls (e.g. http://plane-api:8000 inside Docker) |
PLANE_INTERNAL_API_EMAIL / PLANE_INTERNAL_API_PASSWORD |
pages tools | Plane account used by the internal-API adapter (CE has no public pages API); the account needs membership in the relevant projects |
PLANE_TOOLSETS |
optional (default: all) |
Comma-separated tool groups to register: core,comments,pages,types,planning,admin,pql (or all). Loads fewer tool definitions into the client's context - e.g. core is the daily-driver set (~64 tools vs ~142). Call list_toolsets to see active/available groups. |
PLANE_MAX_RESPONSE_KB |
optional (default: 1024) |
Size cap for list_work_items / list_archived_work_items responses. Over the limit the tool returns an actionable error instead of the payload, to protect the client's context window. 0 disables. |
PLANE_MAX_RETRIES |
optional (default: 2) |
Retries after the first attempt for transient Plane API failures (429/503 for any call; 502/504/timeouts for read-only calls only, to avoid duplicate writes). 0 disables. |
PLANE_RETRY_BASE_DELAY |
optional (default: 0.5) |
Base seconds for exponential backoff with jitter between retries (capped at 10s). |
PLANE_PAGES_MAX_CONTENT_LENGTH |
optional | Default truncation for retrieve_page content |
REDIS_HOST / REDIS_PORT |
http/sse (optional) | Token storage (falls back to in-memory) |
PLANE_OAUTH_PROVIDER_* |
http/sse OAuth only | OAuth client credentials and base URL |
PLANE_OAUTH_ALLOWED_REDIRECT_URIS |
http/sse OAuth (optional) | Comma-separated redirect URI patterns appended to the built-in allowlist |
LOG_USER_INFO |
optional (default: false) |
When true, include user display name (PII) in logs alongside the opaque user id |
LOG_PAYLOADS |
optional (default: false) |
When true, include tool-call payloads (may contain PII / full descriptions) in structured logs. Off by default; enable only for debugging. |
Logging
The server emits structured JSON logs. Each tool call is logged with its tool name, duration, status, and (when available) the opaque user id and workspace slug. Endpoint fallbacks are logged when they trigger (which endpoint failed, which was used instead), so CE quirks are diagnosable from logs.
Health check
In HTTP mode the server exposes an unauthenticated GET /healthz returning
{"status": "ok", "server_version": ...}. It reports the server's own liveness
and does not call Plane, so a Plane outage never marks the MCP server unhealthy.
The Docker image ships a HEALTHCHECK against it.
Available Tools
All tools use Pydantic models from the Plane SDK for type safety and validation.
Projects
| Tool Name | Description |
|---|---|
list_projects |
List all projects in a workspace with optional pagination and filtering |
create_project |
Create a new project with name, identifier, and optional configuration |
retrieve_project |
Retrieve a project by ID |
update_project |
Update a project with partial data |
delete_project |
Delete a project by ID |
get_project_worklog_summary |
Get work log summary for a project |
get_project_members |
Get all members of a project |
update_project_features |
Update features configuration of a project |
manage_project_archive |
Archive or unarchive a project |
Work Items
| Tool Name | Description |
|---|---|
list_work_items |
List all work items in a project with optional filtering and pagination |
create_work_item |
Create a new work item with name, assignees, labels, and other attributes |
retrieve_work_item |
Retrieve a work item by ID with optional field expansion |
retrieve_work_item_by_identifier |
Retrieve a work item by project identifier and issue sequence number |
update_work_item |
Update a work item with partial data |
delete_work_item |
Delete a work item by ID |
search_work_items |
Search work items across a workspace with query string |
count_work_items |
Count work items across the workspace with optional grouping |
list_archived_work_items |
List archived work items in a project with optional PQL filtering |
manage_work_item_archive |
Archive or unarchive a work item |
manage_work_item_assignee |
Add or remove a single assignee without replacing the full list |
manage_work_item_label |
Add or remove a single label without replacing the full list |
Cycles
| Tool Name | Description |
|---|---|
list_cycles |
List cycles in a project (set archived=true for archived) |
create_cycle |
Create a new cycle with name, dates, and owner |
retrieve_cycle |
Retrieve a cycle by ID |
update_cycle |
Update a cycle with partial data |
delete_cycle |
Delete a cycle by ID |
manage_cycle_work_items |
Add and/or remove work items on a cycle |
list_cycle_work_items |
List work items in a cycle |
transfer_cycle_work_items |
Transfer work items from one cycle to another |
manage_cycle_archive |
Archive or unarchive a cycle |
complete_cycle |
Complete (close) a cycle by setting its end date to today |
Modules
| Tool Name | Description |
|---|---|
list_modules |
List modules in a project (set archived=true for archived) |
create_module |
Create a new module with name, dates, status, and members |
retrieve_module |
Retrieve a module by ID |
update_module |
Update a module with partial data |
delete_module |
Delete a module by ID |
manage_module_work_items |
Add and/or remove work items on a module |
list_module_work_items |
List work items in a module |
manage_module_archive |
Archive or unarchive a module |
Initiatives
| Tool Name | Description |
|---|---|
list_initiatives |
List all initiatives in a workspace |
create_initiative |
Create a new initiative with name, dates, state, and lead |
retrieve_initiative |
Retrieve an initiative by ID |
update_initiative |
Update an initiative with partial data |
delete_initiative |
Delete an initiative by ID |
list_initiative_projects |
List projects linked to an initiative |
manage_initiative_projects |
Link or unlink projects of an initiative |
Intake Work Items
| Tool Name | Description |
|---|---|
list_intake_work_items |
List all intake work items in a project with optional pagination |
create_intake_work_item |
Create a new intake work item in a project |
retrieve_intake_work_item |
Retrieve an intake work item by work item ID with optional field expansion |
update_intake_work_item |
Update an intake work item with partial data |
delete_intake_work_item |
Delete an intake work item by work item ID |
Work Item Properties
| Tool Name | Description |
|---|---|
list_work_item_properties |
List work item properties for a work item type |
create_work_item_property |
Create a new work item property with type, settings, and validation rules |
retrieve_work_item_property |
Retrieve a work item property by ID |
update_work_item_property |
Update a work item property with partial data |
delete_work_item_property |
Delete a work item property by ID |
list_work_item_property_options |
List options for a work item property |
create_work_item_property_option |
Create an option on a work item property |
retrieve_work_item_property_option |
Retrieve a single option from a work item property |
update_work_item_property_option |
Update an option on a work item property |
delete_work_item_property_option |
Delete an option from a work item property |
get_work_item_property_value |
Get the value(s) of a custom property on a work item |
set_work_item_property_value |
Set (create or update) the value of a custom property on a work item |
delete_work_item_property_value |
Delete the value(s) of a custom property on a work item |
Milestones
| Tool Name | Description |
|---|---|
list_milestones |
List all milestones in a project |
create_milestone |
Create a new milestone |
retrieve_milestone |
Retrieve a milestone by ID |
update_milestone |
Update a milestone by ID |
delete_milestone |
Delete a milestone by ID |
manage_milestone_work_items |
Add and/or remove work items on a milestone |
list_milestone_work_items |
List work items in a milestone |
Labels
| Tool Name | Description |
|---|---|
list_labels |
List all labels in a project |
create_label |
Create a new label |
retrieve_label |
Retrieve a label by ID |
update_label |
Update a label by ID |
delete_label |
Delete a label by ID |
States
| Tool Name | Description |
|---|---|
list_states |
List all states in a project |
create_state |
Create a new state |
retrieve_state |
Retrieve a state by ID |
update_state |
Update a state by ID |
delete_state |
Delete a state by ID |
Work Item Comments
| Tool Name | Description |
|---|---|
list_work_item_comments |
List comments for a work item |
retrieve_work_item_comment |
Retrieve a specific comment for a work item |
create_work_item_comment |
Create a comment for a work item |
update_work_item_comment |
Update a comment for a work item |
delete_work_item_comment |
Delete a comment for a work item |
Work Item Links
| Tool Name | Description |
|---|---|
list_work_item_links |
List links for a work item |
retrieve_work_item_link |
Retrieve a specific link for a work item |
create_work_item_link |
Create a link for a work item |
update_work_item_link |
Update a link for a work item |
delete_work_item_link |
Delete a link for a work item |
Work Item Types
| Tool Name | Description |
|---|---|
list_work_item_types |
List all work item types in a project |
create_work_item_type |
Create a new work item type |
retrieve_work_item_type |
Retrieve a work item type by ID |
update_work_item_type |
Update a work item type by ID |
delete_work_item_type |
Delete a work item type by ID |
import_work_item_types_to_project |
Bulk-link workspace-level work item types to a project |
resolve_work_item_type |
Find or create a named type for a project, auto-handling workspace vs project scope and import |
manage_work_item_type_properties |
Attach or detach properties on a work item type in a single call |
Work Item Relations
| Tool Name | Description |
|---|---|
list_work_item_relations |
List relations for a work item |
create_work_item_relation |
Create relations for a work item |
remove_work_item_relation |
Remove a relation from a work item |
Work Item Relation Definitions
| Tool Name | Description |
|---|---|
list_work_item_relation_definitions |
List workspace custom relation definitions |
create_work_item_relation_definition |
Create a workspace relation definition |
update_work_item_relation_definition |
Update a relation definition |
delete_work_item_relation_definition |
Delete a relation definition |
Work Item Activities
| Tool Name | Description |
|---|---|
list_work_item_activities |
List activities for a work item |
retrieve_work_item_activity |
Retrieve a specific activity for a work item |
Work Logs
| Tool Name | Description |
|---|---|
list_work_logs |
List work logs for a work item |
create_work_log |
Create a work log for a work item |
update_work_log |
Update a work log for a work item |
delete_work_log |
Delete a work log for a work item |
Pages
| Tool Name | Description |
|---|---|
list_pages |
List pages - metadata only (workspace, or a project's if project_id given) |
retrieve_page |
Retrieve a page incl. content; optional max_length truncation (env default PLANE_PAGES_MAX_CONTENT_LENGTH) |
search_pages |
Search pages by title, optionally inside content (client-side, case-insensitive) |
create_page |
Create a workspace or project page |
attach_page_to_work_item |
Link a page to a work item |
detach_page_from_work_item |
Remove a page link from a work item |
list_work_item_pages |
List all pages linked to a work item |
Work Item Attachments
| Tool Name | Description |
|---|---|
list_work_item_attachments |
List all attachments for a work item |
get_work_item_attachment_download_url |
Get a presigned download URL for a work item attachment |
read_work_item_attachment |
Fetch an attachment's content so the LLM can read or analyze it |
upload_work_item_attachment_from_url |
Fetch a file from a public URL and attach it to a work item |
delete_work_item_attachment |
Delete an attachment from a work item |
Estimates
| Tool Name | Description |
|---|---|
get_project_estimate |
Get the estimate configuration for a project |
create_project_estimate |
Create a new estimate for a project |
update_project_estimate |
Update the estimate for a project |
delete_project_estimate |
Delete the estimate for a project |
link_estimate_to_project |
Link an estimate to a project, making it the active estimate system |
list_project_estimate_points |
List all valid estimate points for a project |
create_project_estimate_points |
Create estimate points for a project estimate |
update_project_estimate_point |
Update a single estimate point |
delete_project_estimate_point |
Delete a single estimate point |
Roles
| Tool Name | Description |
|---|---|
list_roles |
List role definitions in the workspace |
retrieve_role |
Retrieve a role definition by ID |
PQL
| Tool Name | Description |
|---|---|
get_pql_reference |
Return the Plane Query Language (PQL) syntax reference (also exposed as the resource://pql-reference MCP resource) |
Workspaces
| Tool Name | Description |
|---|---|
get_workspace_members |
Get all members of the current workspace |
get_features |
Get feature flags (workspace, or a project's if project_id given) |
update_workspace_features |
Update features of the current workspace |
Users
| Tool Name | Description |
|---|---|
get_me |
Get current authenticated user information |
Instance
| Tool Name | Description |
|---|---|
get_instance_info |
Get edition/version of the connected Plane instance and its known API limitations (e.g. Community Edition) |
list_toolsets |
List the tool groups (toolsets) this server supports and which are active |
Total Tools: 142 tools across 24 categories (scripts/check_tool_docs.py keeps these tables in sync)
Development
# Install (uses uv)
uv pip install -e ".[dev]"
# Tests
pytest
# Format + lint
ruff format plane_mcp/
ruff check plane_mcp/
Integration tests run against a live Plane instance - copy .env.test to
.env.test.local with real values, then export $(cat .env.test.local | xargs) && pytest tests/ -v.
Attribution & License
MIT License - see LICENSE.
Based on makeplane/plane-mcp-server (MIT, © 2025 Plane MCP Server Contributors). This is an independent, unofficial project: not affiliated with, endorsed by, or supported by Plane / makeplane. "Plane" is a trademark of its respective owner.
Contributing
Contributions welcome - especially fixes for self-hosted Community Edition quirks. See CONTRIBUTING.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file plane_ce_mcp-1.1.0.tar.gz.
File metadata
- Download URL: plane_ce_mcp-1.1.0.tar.gz
- Upload date:
- Size: 91.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b17ab754424a0066644c9c2b34a097de73085940819e385a9f78cdc4921929b
|
|
| MD5 |
7e009d0e0997890423d0862c6f7019c9
|
|
| BLAKE2b-256 |
afb8f5b040bde4c14099a16de5dd9c6eafaec16cf1807823a88bd357efce2ffc
|
File details
Details for the file plane_ce_mcp-1.1.0-py3-none-any.whl.
File metadata
- Download URL: plane_ce_mcp-1.1.0-py3-none-any.whl
- Upload date:
- Size: 92.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
925e07089cd1af07650ed5411c226e9c85e0e82c48958035adbad04aa04589ad
|
|
| MD5 |
df2851144826e3ec18e544c2655c8ee0
|
|
| BLAKE2b-256 |
810e44581a998593eea4ee7a2cbf9811e4d151bbf6333c37050735217e6db102
|