Skip to main content

Plane MCP Server

CI PyPI License: MIT Python 3.10+ Built with FastMCP MCP

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):

  1. CLI flags (--base-url, --workspace, --api-key)
  2. Process environment variables
  3. A .env file in the working directory (loaded automatically)
  4. 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, assignees and labels take UUIDs, not names. Call list_states / list_labels / list_workspace_members first.
  • description is plain text and is converted to the description_html the API expects; description_html overrides it when supplied.
  • Priority is one of urgent, high, medium, low, none.
  • The API allows 60 requests/minute per key; the client surfaces 429 as 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.2 and master (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 rejects X-API-Key and 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_items is permission-gated on some workspaces and editions and can return 403. Use search_work_items or list_work_items as 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)

Source distribution for plane-mcp-oss 0.1.1
File Size Uploaded
plane_mcp_oss-0.1.1.tar.gz 145.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for plane-mcp-oss 0.1.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page