Skip to main content
Glama
nicolasegpla

plane-selfhost-mcp

by nicolasegpla

plane-selfhost-mcp

Custom MCP server for self-hosted Plane instances that require X-API-Key authentication instead of Authorization: Bearer.

This project exists because the official Plane MCP flow did not work reliably against this self-hosted environment. This server talks directly to the Plane REST API, exposes a small focused MCP toolset, and is designed to be consumed from OpenCode.

What this project does

  • Connects to a self-hosted Plane workspace with X-API-Key

  • Exposes Plane operations as MCP tools over stdio

  • Supports project discovery, issue listing, issue creation, issue updates, parent-child hierarchy updates, comments, workflow state lookup, and project label management

  • Includes experimental/diagnostic project page tools for Plane editions/builds that expose API-key-compatible Pages endpoints

  • Works with OpenCode through a local MCP server entry such as plane-selfhost

Related MCP server: Plane MCP Server

Why it was created

The official Plane MCP server uses Authorization: Bearer <token>. In this self-hosted setup that produced auth failures, while direct API calls with X-API-Key worked correctly.

So the right move was NOT to keep fighting the wrong auth model. The right move was to build a thin MCP wrapper around the API behavior that the real instance actually accepts.

Quick start

Requirements

  • Python 3.10+

  • A reachable self-hosted Plane instance

  • A Plane API key with workspace access

  • The Plane workspace slug

Install

cd plane-selfhost-mcp
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Configure environment

Preferred path: copy .env.example to a project-root .env file so OpenCode and local commands can share one source of truth.

cp .env.example .env

Then edit .env with your real values.

At startup the MCP loads .env automatically, then reads the real process environment on top of it. That means explicit environment variables still win when you need a one-off override.

If you prefer, you can still export the variables directly instead of using .env.

The server fails fast with a clear error if any required variable is missing from both sources.

Run locally

stdio mode

source .venv/bin/activate
python -m plane_selfhost_mcp

Installed script

source .venv/bin/activate
plane-selfhost-mcp

Makefile shortcuts

After installing the package, you can use the local Makefile instead of manually activating the virtual environment:

make help             # list available targets
make install          # install the package and dev dependencies in .venv
make run              # run the MCP server (sources .env automatically if present)
make smoke-test       # verify config loads (sources .env automatically if present)
make whoami           # print the authenticated Plane user (sources .env automatically if present)
make list-projects    # list workspace projects (sources .env automatically if present)
make list-states      # list workflow states for a project (requires PROJECT_ID=...)
make create-test-issue # create a test issue in a project (requires PROJECT_ID=... TITLE=...)
make move-issue       # move an issue to a target state (requires PROJECT_ID=... ISSUE_ID=... STATE_ID=...)
make comment          # add an HTML/plain comment to an issue (requires PROJECT_ID=... ISSUE_ID=... COMMENT="...")
make test             # run pytest

Free-form values such as TITLE and COMMENT are passed to the inline Python scripts through environment variables, so quotes and special characters do not break the command.

make run, make smoke-test, make whoami, make list-projects, make list-states, make create-test-issue, make move-issue, and make comment will source a .env file in the project root if one exists, so you do not need to export variables manually every time.

OpenCode integration

Add this MCP server to your OpenCode config:

{
  "mcp": {
    "plane-selfhost": {
      "type": "local",
      "enabled": true,
      "command": [
        "/absolute/path/to/plane-selfhost-mcp/.venv/bin/python",
        "-m",
        "plane_selfhost_mcp"
      ]
    }
  }
}

Recommended setup for OpenCode: keep Plane credentials in the repo's .env file and let the MCP load them automatically at startup.

Use the MCP env block only when you intentionally want OpenCode to override the .env values for that process.

Replace /absolute/path/to/plane-selfhost-mcp/.venv/bin/python with the Python executable from your own local virtual environment.

After updating OpenCode config, restart OpenCode. MCP config is not hot-reloaded.

Available tools

Tool

What it does

Typical use

get_user_me

Returns the authenticated Plane user

Verify auth/config works

list_projects

Lists projects in the configured workspace

Resolve a target project before issue work

list_project_issues

Lists issues for a project

Find an issue before updating it

list_project_labels

Lists labels for a project

Resolve label IDs or inspect available labels

list_project_pages

Experimentally lists pages for a project when the Plane edition/build exposes API-key-compatible Pages endpoints

Diagnose page endpoint availability or inspect pages on supported builds

create_project_page

Experimentally creates a page when the Plane edition/build exposes API-key-compatible Pages endpoints

Add rich project documentation only on supported builds

update_project_page

Experimentally updates page fields when the Plane edition/build exposes API-key-compatible Pages endpoints

Edit page metadata only on supported builds

create_project_label

Creates a label in a project

Add a new label before assigning it to issues

create_issue

Creates a new issue

Add a task/bug/story in Plane

update_issue

Updates issue fields

Move states, rename, reprioritize, assign, relabel

set_issue_parent

Sets an issue's parent in Plane's hierarchy

Create parent-child issue/work-item structure, not dependencies

add_issue_comment

Adds an HTML comment to an issue

Leave progress notes or audit comments

list_states

Lists workflow states for a project

Resolve initial/done state IDs before transitions

How the MCP should be used

This MCP is intentionally simple. The safest usage flow is:

  1. Call get_user_me to prove the connection.

  2. Call list_projects to resolve the target project_id.

  3. If the action depends on workflow placement, call list_states.

  4. For labels, call list_project_labels first or create them with create_project_label.

  5. For pages, first confirm your Plane edition/build exposes API-key-compatible Pages endpoints; on Plane Community v1.3.1, these tools are expected to fail diagnostically.

  6. Create or update issues and supported pages only after resolving IDs from live responses.

  7. Treat responses as successful only when the MCP payload returns ok: true.

Important operational rules

  • Do not invent project_id, issue_id, or state values.

  • Label assignment accepts existing label UUIDs or names, but names are resolved against the project's existing labels before the request is sent.

  • Missing label names are rejected locally with a clear error. Issue tools do not auto-create labels.

  • Use create_project_label explicitly when you want to create a new label.

  • Use description_html and comment_html for rich text fields.

  • Plane Community v1.3.1 exposes project pages only through web-app/session-auth routes under /api/workspaces/{workspace_slug}/projects/{project_id}/pages/. As tested, API-key-compatible Pages API routes are not available in Community.

  • Page tools are experimental/diagnostic unless the target Plane edition/build exposes API-key-compatible Pages endpoints. On Community v1.3.1, they are expected to fail diagnostically rather than provide functional Pages support.

  • Existing project, issue, label, and state tools continue to use public /api/v1/workspaces/... routes with X-API-Key.

  • External Pages API access with API-key authentication appears to be Commercial/Cloud functionality, not Plane Community behavior. The MCP intentionally does not implement browser/session auth.

  • Use set_issue_parent for parent-child hierarchy only. It calls Plane's work-items API with parent; it does not create dependency relations such as blocking or blocked-by.

  • Self-hosted Plane in this setup expects X-API-Key, not bearer auth.

  • If auth fails, debug credentials first; do not assume the API path is wrong.

Shared OpenCode skill

This repository includes a shared plane-mcp skill artifact that another user can copy into their own OpenCode skills setup.

  • Skill location in this repository: skills/plane-mcp/SKILL.md

The repository does not auto-register that skill for anyone. OpenCode will only use it after each user copies or registers it in their own local OpenCode configuration.

Skill purpose

The plane-mcp skill exists so agents follow the correct operational contract every time instead of improvising.

It teaches the agent to:

  • target the custom plane-selfhost-mcp

  • verify config and auth first

  • resolve IDs from live Plane responses

  • use list_states before state transitions

  • stop and surface errors when the MCP payload returns ok: false

How another user can use it

  1. Clone this repository and install the MCP package.

  2. Configure the repository root .env file with real Plane credentials.

  3. Add the MCP server entry shown in OpenCode integration to your own OpenCode config.

  4. Copy skills/plane-mcp/SKILL.md into your own OpenCode skills directory, or register it from your own OpenCode setup.

  5. Restart OpenCode after registering the skill.

The .env-first recommendation stays the same: keep credentials in the repository root .env file by default, and use the MCP env block only for intentional per-process overrides.

Verification and smoke tests

Config loader only

source .venv/bin/activate
python -c "from plane_selfhost_mcp.config import load_config; print(load_config())"

Real API check

source .venv/bin/activate
python -c "
import asyncio
from plane_selfhost_mcp.config import load_config
from plane_selfhost_mcp.client import PlaneClient

async def main():
    cfg = load_config()
    client = PlaneClient(cfg)
    try:
        print('ME:', await client.get_user_me())
        print('PROJECTS:', await client.list_projects())
    finally:
        await client.close()

asyncio.run(main())
"

Expected result:

  • get_user_me returns the authenticated user

  • list_projects returns workspace projects

Label workflow

Plane issue mutations expect label UUIDs, but this MCP now resolves existing project label names for you before sending the API request.

Deterministic behavior:

  • create_issue and update_issue accept labels as a list of existing label UUIDs and/or existing label names.

  • Name resolution checks the target project's labels first.

  • Exact name matches win first.

  • If only one case-insensitive match exists, it is accepted.

  • If a name does not exist, the MCP fails before sending the issue mutation.

  • If case-insensitive matching is ambiguous, the MCP fails and tells you to use a UUID.

  • Labels are only created through create_project_label.

Example flow:

  1. Call list_project_labels for the target project.

  2. If the label is missing, call create_project_label.

  3. Call create_issue or update_issue with either the label UUID or the label name.

Live verification status:

  • After restarting OpenCode so it reloaded the MCP config, we verified live MCP issue creation with labels against the self-hosted Plane instance.

  • The successful path was: restart OpenCode -> resolve the project -> ensure the label already exists (or create it first) -> call create_issue with label names.

Development

Run tests

make test

Or, with the virtual environment activated:

pytest

Package facts

Topic

Value

Python

>=3.10

Entry point

plane-selfhost-mcp = plane_selfhost_mcp.server:main

Runtime deps

httpx, mcp, pydantic

Test deps

pytest, pytest-asyncio, respx

Project structure

src/plane_selfhost_mcp/
├── client.py      # Plane REST client
├── config.py      # env loading and validation
├── server.py      # MCP tool registration and stdio server
└── __main__.py    # python -m entrypoint
tests/
└── test_client.py # HTTP client tests

Troubleshooting

Missing environment variables

If you see a runtime error about missing PLANE_BASE_URL, PLANE_API_KEY, or PLANE_WORKSPACE_SLUG, check the project-root .env file first, then check whether the MCP process is receiving any explicit env overrides.

401 Unauthorized

Plane received no credentials. Check whether .env exists in the project root, then check whether the MCP env block is actually being passed.

For project page tools specifically, a 401 Unauthorized response can also mean the route exists but Plane rejected the current X-API-Key auth for pages. The self-hosted pages endpoint may require session, JWT, cookie auth, or an API token with pages support. This MCP intentionally does not support browser/session auth yet.

Current self-hosted stable finding: project pages are exposed through Plane's web-app/session-auth route family, not through the public API-key route family used by the rest of this MCP. Do not configure browser cookies or session tokens as a workaround unless Plane documents that auth flow as a stable automation contract.

403 Forbidden

The credential reached Plane but the API key is invalid or lacks access.

Project not found

Verify the workspace slug and call list_projects before attempting issue operations.

Project pages API returns 404

If list_project_pages, create_project_page, or update_project_page returns 404 while project and issue tools work, verify that the self-hosted Plane pages routes are available under /api/workspaces/{workspace_slug}/projects/{project_id}/pages/ rather than the v1 prefix. Existing project, issue, label, and state tools still use /api/v1/workspaces/.... Also confirm the API key includes projects.pages:read or projects.pages:write, because Plane may mask missing page scopes as 404.

License

MIT

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Shortcut project management. Create, update, search stories and manage workflows.

  • Connect to Atlassian Jira, Confluence, and Compass to search, create, and manage your work.

  • Task manager your agent can fully operate: boards, tasks, sprints, roles, worklogs, day planner.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nicolasegpla/plane-selfhost-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server