diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 67908285..9b423da3 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -969,7 +969,10 @@ export default withMermaid( text: "MCP Server", link: "/dev-tools/mcp-server", collapsed: false, - items: [{ text: "For Claude Code", link: "/dev-tools/mcp-server-claude-code" }], + items: [ + { text: "Tool Reference", link: "/dev-tools/mcp-server-tools" }, + { text: "Self-host", link: "/dev-tools/mcp-server-self-host" }, + ], }, { text: "Plane Compose", link: "/dev-tools/plane-compose" }, { text: "OpenAPI Specification", link: "/dev-tools/openapi-specification" }, diff --git a/docs/dev-tools/mcp-server-claude-code.md b/docs/dev-tools/mcp-server-claude-code.md deleted file mode 100644 index 8d1924b7..00000000 --- a/docs/dev-tools/mcp-server-claude-code.md +++ /dev/null @@ -1,284 +0,0 @@ ---- -title: MCP Server for Claude Code -description: Install and use the Plane MCP Server with Claude Code CLI. Manage work items, projects, cycles, and modules from your terminal with AI. -keywords: plane, mcp server, claude code, cli, ai, project management, work items, developer tools ---- - -# MCP Server for Claude Code - -[Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's CLI tool for agentic -coding. By connecting the Plane MCP Server to Claude Code, you can manage your Plane workspace -directly from the terminal — create work items, plan cycles, search across projects, and more, -all through natural language. - - -Beta -The Plane MCP Server is currently in **Beta**. Some aspects of the API may change. Please send any -issues to support@plane.so. - - -## Prerequisites - -- **Claude Code**: [Install Claude Code](https://docs.anthropic.com/en/docs/claude-code/getting-started) if you haven't already. -- **Node.js 22+**: Required for HTTP transports. Verify with `node --version`. -- **Python 3.10+** and **uvx**: Required only for the local Stdio transport. Verify with `python --version` and `uvx --version`. -- **Plane API Key**: For PAT or Stdio transports, generate one from **Workspace Settings > API Tokens** in Plane. - -## Setup methods - -Choose the transport method that fits your setup. - -| Method | Best for | Auth | -| ------------------------------------- | ---------------------------- | --------------------- | -| [HTTP with OAuth](#http-with-oauth) | Plane Cloud, interactive use | Browser-based OAuth | -| [HTTP with PAT](#http-with-pat-token) | CI/CD, automated workflows | API key in headers | -| [Local Stdio](#local-stdio) | Self-hosted Plane instances | Environment variables | - -## HTTP with OAuth - -The simplest way to connect to Plane Cloud. Run this in your terminal: - -```bash -claude mcp add --transport http plane https://mcp.plane.so/http/mcp -``` - -After adding the server, start Claude Code and run `/mcp` to authenticate via your browser. - -## HTTP with PAT token - -For automated workflows or when you prefer token-based auth: - -```bash -claude mcp add-json plane '{ - "type": "http", - "url": "https://mcp.plane.so/http/api-key/mcp", - "headers": { - "Authorization": "Bearer ", - "X-Workspace-slug": "" - } -}' -``` - -Replace `` and `` with your actual values. - -## Local Stdio - -For self-hosted Plane instances, use the Stdio transport which runs locally: - -```bash -claude mcp add-json plane '{ - "type": "stdio", - "command": "uvx", - "args": ["plane-mcp-server", "stdio"], - "env": { - "PLANE_API_KEY": "", - "PLANE_WORKSPACE_SLUG": "", - "PLANE_BASE_URL": "https://your-plane-instance.com" - } -}' -``` - -### Environment variables - -| Variable | Required | Description | -| ---------------------- | -------- | --------------------------------------------------------------------- | -| `PLANE_API_KEY` | Yes | API key from your workspace settings | -| `PLANE_WORKSPACE_SLUG` | Yes | Your workspace slug (found in your Plane URL) | -| `PLANE_BASE_URL` | No | API URL for self-hosted instances. Defaults to `https://api.plane.so` | - -## Scope options - -Control where the MCP server configuration is stored by adding a `--scope` flag: - -```bash -# Available only to you in the current project (default) -claude mcp add --scope local --transport http plane https://mcp.plane.so/http/mcp - -# Shared via .mcp.json, committed to version control -claude mcp add --scope project --transport http plane https://mcp.plane.so/http/mcp - -# Available across all your projects -claude mcp add --scope user --transport http plane https://mcp.plane.so/http/mcp -``` - -## Verify the connection - -After setup, verify the Plane MCP Server is connected: - -```bash -# List all configured MCP servers -claude mcp list - -# Get details for the Plane server -claude mcp get plane -``` - -Inside Claude Code, run `/mcp` to check the server status and see the available tools. - -## Available tools - -The Plane MCP Server exposes 55+ tools across these categories: - -| Category | Tools | Examples | -| ------------------------ | ----- | --------------------------------------------------------------- | -| **Projects** | 9 | List, create, update, delete projects; get members and features | -| **Work Items** | 7 | Create, list, search, update, delete work items | -| **Cycles** | 12 | Manage cycles, add/remove work items, transfer, archive | -| **Modules** | 11 | Manage modules, add/remove work items, archive | -| **Initiatives** | 5 | Create and manage workspace-level initiatives | -| **Intake** | 5 | Manage intake work items for triage | -| **Work Item Properties** | 5 | Manage custom properties on work items | -| **Users** | 1 | Get current authenticated user info | - -## Examples - -Here are common tasks you can perform by chatting with Claude Code after connecting the Plane MCP Server. - -### List projects in your workspace - -**Prompt:** - -``` -List all projects in my workspace. -``` - -Claude Code calls `list_projects` and returns a summary of all projects including their identifiers, lead, and status. - -### Create a work item - -**Prompt:** - -``` -Create a bug in project WEB titled "Fix login redirect loop" and assign it to me. -``` - -Claude Code calls `get_me` to find your user ID, then `create_work_item` with the project ID, name, and assignee. - -### Search across work items - -**Prompt:** - -``` -Search for work items related to "authentication" across the workspace. -``` - -Claude Code calls `search_work_items` with the query string and returns matching results from all projects. - -### Plan a cycle - -**Prompt:** - -``` -Create a new cycle called "Sprint 24" in project WEB starting today and -ending in 2 weeks. Add work items WEB-102, WEB-115, and WEB-118 to it. -``` - -Claude Code calls `create_cycle` with the name and dates, then `add_work_items_to_cycle` to attach the specified items. - -### Triage intake items - -**Prompt:** - -``` -Show me all intake items in project MOBILE and accept the ones related to -crash reports. -``` - -Claude Code calls `list_intake_work_items` to retrieve pending items, then `update_intake_work_item` to accept the relevant ones. - -### Get a project overview - -**Prompt:** - -``` -Give me a summary of project BACKEND — what cycles are active, how many -open work items are there, and who are the members? -``` - -Claude Code calls `retrieve_project`, `list_cycles`, `list_work_items`, and `get_project_members` to assemble a full overview. - -### Manage modules - -**Prompt:** - -``` -Create a module called "Auth Revamp" in project WEB and add all work items -tagged with the "auth" label to it. -``` - -Claude Code calls `create_module`, then `list_work_items` with label filtering, and finally `add_work_items_to_module` to associate the items. - -### Move work items between cycles - -**Prompt:** - -``` -Transfer all incomplete work items from "Sprint 23" to "Sprint 24" in -project WEB. -``` - -Claude Code calls `list_cycles` to find both cycle IDs, then `transfer_cycle_work_items` to move unfinished items. - -## Managing the server - -```bash -# Remove the Plane MCP server -claude mcp remove plane - -# Re-add with a different transport -claude mcp add --transport http plane https://mcp.plane.so/http/mcp -``` - -## Troubleshooting - -### MCP server not connecting - -If the server fails to start, launch Claude Code with the debug flag: - -```bash -claude --mcp-debug -``` - -This shows detailed logs during MCP server initialization. - -### Authentication errors (OAuth) - -Clear saved OAuth tokens and re-authenticate: - -```bash -rm -rf ~/.mcp-auth -``` - -Then restart Claude Code and run `/mcp` to authenticate again. - -### Server timeout on startup - -If the MCP server times out during initialization, increase the timeout: - -```bash -MCP_TIMEOUT=10000 claude -``` - -This sets a 10-second startup timeout (default is lower). - -### Python or Node.js not found - -Ensure the required runtime is installed and available on your `PATH`: - -```bash -# For HTTP transports -node --version # Should be 22+ - -# For Stdio transport -python --version # Should be 3.10+ -uvx --version -``` - -### Getting help - -If issues persist: - -1. Check your credentials and workspace slug -2. Run `claude mcp list` to verify the configuration -3. Contact support at support@plane.so for Plane-specific issues -4. Visit the [Plane MCP Server repository](https://github.com/makeplane/plane-mcp-server) for known issues diff --git a/docs/dev-tools/mcp-server-self-host.md b/docs/dev-tools/mcp-server-self-host.md new file mode 100644 index 00000000..307c92a9 --- /dev/null +++ b/docs/dev-tools/mcp-server-self-host.md @@ -0,0 +1,400 @@ +--- +title: Self-host MCP Server +description: Deploy your own Plane MCP Server with Docker Compose or Helm. Create an OAuth app, configure credentials, and connect AI clients to your self-hosted instance. +keywords: plane mcp server, self-hosted mcp, plane mcp deployment, docker compose mcp, helm mcp, plane oauth mcp, plane ai integration, mcp server setup +--- + +# Self-host MCP Server + +Run your own instance of the Plane MCP Server. Required if you want OAuth auth against a self-hosted Plane instance, or if you want full control over your MCP infrastructure. Plane Cloud users who just want OAuth/PAT/Stdio against `mcp.plane.so` should use the [setup guide](/dev-tools/mcp-server) instead. + +::: info Beta +The Plane MCP Server is in **Beta**. Send feedback to support@plane.so. +::: + +--- + +## Prerequisites + +- A running **Plane instance** (self-hosted or cloud) with workspace admin access. +- **Docker** + Docker Compose v2+, _or_ **Kubernetes** v1.21+ with Helm v3+. +- A public URL for the MCP server (e.g. `https://mcp.yourdomain.com`). OAuth callbacks need to reach it. + +--- + +## Step 1: Create an OAuth application in Plane + +The MCP server authenticates with Plane via OAuth 2.0. Register an app to get the credentials. + +1. Go to **Workspace Settings → Integrations**: + + ```text + https:////settings/integrations/ + ``` + +2. Click **Build your own**. + +3. Fill in the application details: + + | Field | Value | + | ---------------- | ------------------------------------------------------- | + | **App Name** | Anything (e.g. `Plane MCP Server`) | + | **Setup URL** | Your MCP server URL (e.g. `https://mcp.yourdomain.com`) | + | **Redirect URI** | All three URIs listed below, space-separated | + | **Webhook URL** | Leave empty unless you need webhook events | + + ::: tip Redirect URIs — add all three + The server exposes callbacks on three paths to support all transports and MCP clients: + + | Transport | Redirect URI | + | --------------- | ------------------------------------- | + | OAuth callback | `/callback` | + | HTTP with OAuth | `/http/auth/callback` | + | SSE (legacy) | `/auth/callback` | + + Example with `https://mcp.yourdomain.com`, paste into the Redirect URI field **space-separated**: + + ```text + https://mcp.yourdomain.com/callback https://mcp.yourdomain.com/http/auth/callback https://mcp.yourdomain.com/auth/callback + ``` + + ::: + +4. Select the **read** and **write** scopes under **Scopes & Permissions**. Both are required. + +5. Save. Store the generated **Client ID** and **Client Secret** securely. + +::: warning +Never expose the Client Secret in client-side code or commit it to version control. +::: + +More on creating OAuth apps: [Create an OAuth Application](/dev-tools/build-plane-app/create-oauth-application). + +--- + +## Step 2: Deploy the MCP server + +### Option A: Docker Compose + +1. Create a project directory with this `docker-compose.yaml`: + + ```yaml + name: plane-mcp + + services: + mcp: + image: makeplane/plane-mcp-server:${APP_RELEASE_VERSION:-latest} + restart: always + ports: + - "8211:8211" + env_file: + - variables.env + environment: + REDIS_HOST: valkey + REDIS_PORT: "6379" + depends_on: + valkey: + condition: service_healthy + + valkey: + image: valkey/valkey:8-alpine + restart: always + volumes: + - valkey-data:/data + healthcheck: + test: ["CMD", "valkey-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + volumes: + valkey-data: + ``` + +2. Create `variables.env` with the OAuth credentials from Step 1: + + ```env + # Image tag (pin to a specific version in production) + APP_RELEASE_VERSION=latest + + # Plane API URL — Cloud or your self-hosted instance + PLANE_BASE_URL=https://api.plane.so + + # Internal URL for server-to-server calls (optional, same-network setups) + # PLANE_INTERNAL_BASE_URL= + + # OAuth credentials from Step 1 + PLANE_OAUTH_PROVIDER_CLIENT_ID=your-client-id + PLANE_OAUTH_PROVIDER_CLIENT_SECRET=your-client-secret + + # Public URL where MCP clients reach this server + PLANE_OAUTH_PROVIDER_BASE_URL=https://mcp.yourdomain.com + ``` + +3. Start: + + ```bash + docker compose up -d + ``` + +4. Verify the container is running and the MCP endpoint responds: + + ```bash + docker compose logs -f mcp # follow startup logs + curl http://localhost:8211/http/mcp # expect: 401 (auth required) or MCP protocol response + ``` + +::: warning Production: terminate TLS in front +The container speaks plain HTTP on `:8211`. Put it behind a reverse proxy (nginx, Caddy, Traefik, Cloudflare) with TLS — OAuth callbacks fail without HTTPS, and `PLANE_OAUTH_PROVIDER_BASE_URL` must be the public `https://` URL of that proxy. +::: + +#### Configuration reference + +| Variable | Required | Description | +| ------------------------------------ | -------- | -------------------------------------------------------------------------------- | +| `APP_RELEASE_VERSION` | No | Image tag to deploy (default: `latest`). Pin in production. | +| `PLANE_BASE_URL` | No | Plane API URL. Defaults to `https://api.plane.so`. | +| `PLANE_INTERNAL_BASE_URL` | No | Internal Plane URL for server-to-server calls. Falls back to `PLANE_BASE_URL`. | +| `PLANE_OAUTH_PROVIDER_CLIENT_ID` | Yes | OAuth Client ID from Step 1. | +| `PLANE_OAUTH_PROVIDER_CLIENT_SECRET` | Yes | OAuth Client Secret from Step 1. | +| `PLANE_OAUTH_PROVIDER_BASE_URL` | Yes | Public URL of **this MCP server** (not Plane). | +| `MCP_PATH_PREFIX` | No | Prefix prepended to all routes — use when reverse-proxying alongside other apps. | + +#### Upgrading + +```bash +docker compose pull +docker compose up -d +``` + +--- + +### Option B: Helm chart + +1. Add the Plane Helm repo: + + ```bash + helm repo add plane https://helm.plane.so + helm repo update + ``` + +2. Create `values.yaml`: + + ```yaml + ingress: + enabled: true + host: mcp.yourdomain.com + ingressClass: nginx + ssl: + enabled: true + issuer: cloudflare # cloudflare | digitalocean | http + email: you@yourdomain.com + + services: + api: + plane_base_url: "https://api.plane.so" + plane_oauth: + enabled: true + client_id: "" + client_secret: "" + provider_base_url: "https://mcp.yourdomain.com" + ``` + +3. Install: + + ```bash + helm install plane-mcp plane/plane-mcp-server \ + --namespace plane-mcp \ + --create-namespace \ + -f values.yaml + ``` + +#### Helm values reference + +| Value | Default | Description | +| -------------------------------------------- | ----------------- | --------------------------------------------------- | +| `dockerRegistry.default_tag` | `latest` | Image tag to deploy | +| `ingress.enabled` | `true` | Enable ingress | +| `ingress.host` | `mcp.example.com` | Public hostname | +| `ingress.ingressClass` | `nginx` | Ingress class name | +| `ingress.ssl.enabled` | `false` | Enable TLS via cert-manager | +| `ingress.ssl.issuer` | `cloudflare` | ACME issuer (`cloudflare`, `digitalocean`, `http`) | +| `services.api.replicas` | `1` | Number of MCP server replicas | +| `services.api.plane_base_url` | `""` | Plane API URL | +| `services.api.plane_oauth.enabled` | `false` | Enable OAuth endpoints | +| `services.api.plane_oauth.client_id` | `""` | OAuth Client ID | +| `services.api.plane_oauth.client_secret` | `""` | OAuth Client Secret | +| `services.api.plane_oauth.provider_base_url` | `""` | Public URL this server is reachable on | +| `services.redis.local_setup` | `true` | Deploy Valkey in-cluster | +| `services.redis.external_redis_url` | `""` | External Valkey/Redis URL (if not using in-cluster) | + +#### Upgrading + +```bash +helm upgrade plane-mcp plane/plane-mcp-server \ + --namespace plane-mcp \ + -f values.yaml +``` + +#### Uninstalling + +```bash +helm uninstall plane-mcp --namespace plane-mcp +``` + +--- + +## Step 3: Connect your AI tools + +Replace `https://mcp.yourdomain.com` with your actual MCP server URL. + +### Available endpoints + +| Endpoint | Auth | Description | +| --------------------------------------------- | ---------- | -------------------------------------- | +| `https://mcp.yourdomain.com/http/mcp` | OAuth | OAuth-based MCP endpoint (recommended) | +| `https://mcp.yourdomain.com/http/api-key/mcp` | PAT header | Personal Access Token endpoint | +| `https://mcp.yourdomain.com/sse` | OAuth | Legacy SSE endpoint (deprecated) | + +### Cursor (native HTTP) + +```json +{ + "mcpServers": { + "plane": { + "url": "https://mcp.yourdomain.com/http/mcp", + "type": "http" + } + } +} +``` + +### Windsurf (native HTTP) + +Windsurf uses `serverUrl` (not `url`) for remote HTTP servers. + +```json +{ + "mcpServers": { + "plane": { + "serverUrl": "https://mcp.yourdomain.com/http/mcp" + } + } +} +``` + +### Zed (native HTTP) + +```json +{ + "context_servers": { + "plane-mcp-server": { + "url": "https://mcp.yourdomain.com/http/mcp" + } + } +} +``` + +### VS Code (native HTTP) + +`.vscode/mcp.json`: + +```json +{ + "servers": { + "plane": { + "url": "https://mcp.yourdomain.com/http/mcp", + "type": "http" + } + } +} +``` + +### Claude Code + +```bash +claude mcp add --transport http plane https://mcp.yourdomain.com/http/mcp +``` + +### Claude Desktop (via mcp-remote bridge) + +Claude Desktop doesn't speak remote HTTP. Bridge it with `mcp-remote`. Requires Node.js 18+. + +```json +{ + "mcpServers": { + "plane": { + "command": "npx", + "args": ["mcp-remote@latest", "https://mcp.yourdomain.com/http/mcp"] + } + } +} +``` + +### PAT auth (CI, scripts, headless) + +Swap the endpoint to `/http/api-key/mcp` and add auth headers. Cursor shape: + +```json +{ + "mcpServers": { + "plane": { + "url": "https://mcp.yourdomain.com/http/api-key/mcp", + "type": "http", + "headers": { + "Authorization": "Bearer your_api_key_here", + "x-workspace-slug": "your-workspace-slug" + } + } + } +} +``` + +→ For per-client PAT shapes (Windsurf uses `serverUrl`, VS Code uses `servers`, Zed uses `context_servers`, Claude Code uses `claude mcp add-json`), see the [setup guide PAT section](/dev-tools/mcp-server#pat-token) — same configs, just swap the URL to your domain. + +--- + +## Troubleshooting + +**Server not starting?** + +```bash +docker compose logs mcp +``` + +**Valkey connection issues?** + +```bash +docker compose exec valkey valkey-cli ping +# Expect: PONG +``` + +**OAuth authentication errors?** + +- Verify all three **redirect URIs** are added to your Plane OAuth app: `/callback`, `/http/auth/callback`, `/auth/callback`. +- Confirm `PLANE_OAUTH_PROVIDER_CLIENT_ID` / `_CLIENT_SECRET` in env match values from Plane. +- Confirm `PLANE_OAUTH_PROVIDER_BASE_URL` is the publicly accessible URL of **this MCP server** (not Plane). +- Clear cached auth tokens on the client side: + + ```bash + rm -rf ~/.mcp-auth + ``` + +**Token loss on restart** — tokens are in-memory if Redis/Valkey isn't reachable. Verify the `REDIS_HOST` / `REDIS_PORT` env vars are correct and the Valkey container is healthy. + +**Reset Docker Compose** (deletes Valkey data): + +```bash +docker compose down -v +docker compose up -d +``` + +**Still stuck?** + +1. Verify OAuth credentials and workspace settings. +2. Check the [plane-mcp-server repo](https://github.com/makeplane/plane-mcp-server) for known issues. +3. Contact `support@plane.so`. + +--- + +→ For the tool list, see [MCP Server Tool Reference](/dev-tools/mcp-server-tools). For client connection details against `mcp.plane.so`, see the [MCP Server setup guide](/dev-tools/mcp-server). diff --git a/docs/dev-tools/mcp-server-tools.md b/docs/dev-tools/mcp-server-tools.md new file mode 100644 index 00000000..3d558667 --- /dev/null +++ b/docs/dev-tools/mcp-server-tools.md @@ -0,0 +1,491 @@ +--- +title: MCP Server Tool Reference +description: Complete reference for all 136 Plane MCP tools across 20 categories, with parameter details and example workflows. +keywords: plane, mcp server, tools, api reference, work items, cycles, modules, projects +--- + +# MCP Server Tool Reference + +136 tools across 20 categories. All tools work identically regardless of transport mode (OAuth, PAT, or Stdio). + +→ Not set up yet? See the [MCP Server setup guide](/dev-tools/mcp-server). + +--- + +## Tool categories + +| Category | Count | What you can do | +| ------------------------ | ----- | -------------------------------------------------------------- | +| **Projects** | 20 | Create, update, archive projects; estimates, members, features | +| **Work Items** | 14 | Create, search, update, archive; assignees, labels | +| **Cycles** | 13 | Manage sprints; transfer, archive, complete | +| **Modules** | 11 | Manage feature groups; archive | +| **Pages** | 9 | Workspace and project pages; link to work items | +| **Milestones** | 8 | Create and manage milestones | +| **Work Item Properties** | 8 | Custom field definitions and values | +| **Work Item Comments** | 5 | Create, update, delete comments | +| **Work Item Links** | 5 | Attach external URLs | +| **Work Item Types** | 5 | Custom type definitions | +| **States** | 5 | Workflow states | +| **Labels** | 5 | Work item labels | +| **Initiatives** | 5 | Workspace-level strategic goals | +| **Intake** | 5 | Triage queue | +| **Epics** | 5 | Group related work items | +| **Worklogs** | 4 | Time tracking | +| **Work Item Relations** | 3 | Blocks, duplicate, relates-to | +| **Workspaces** | 3 | Members and features | +| **Work Item Activities** | 2 | Change history | +| **Users** | 1 | Authenticated user info | + +--- + +## Users + +| Tool | Description | +| -------- | -------------------------------------------------------- | +| `get_me` | Returns the authenticated user's profile. No parameters. | + +--- + +## Workspaces + +No `project_id` required — workspace-scoped. + +| Tool | Description | +| --------------------------- | ------------------------------------ | +| `get_workspace_members` | List all workspace members | +| `get_workspace_features` | Get enabled workspace features | +| `update_workspace_features` | Enable or disable workspace features | + +--- + +## Projects + +**CRUD** + +| Tool | Description | +| ------------------- | ---------------------------------------------------------------------------------------- | +| `list_projects` | List all projects in the workspace | +| `create_project` | Create a project. Requires `name` and `identifier` (uppercase, max 12 chars, e.g. `ENG`) | +| `retrieve_project` | Get a project by ID | +| `update_project` | Update project fields | +| `delete_project` | Delete a project | +| `archive_project` | Archive a project | +| `unarchive_project` | Restore an archived project | + +**Members & features** + +| Tool | Description | +| ----------------------------- | --------------------------------------- | +| `get_project_members` | List members in a project | +| `get_project_features` | Get enabled features for a project | +| `update_project_features` | Enable or disable project features | +| `get_project_worklog_summary` | Get time-tracking summary for a project | + +**Estimates** + +Workflow: `get_project_estimate` → `list_project_estimate_points` → pass a point `id` to `update_work_item(estimate_point=...)`. + +| Tool | Description | +| -------------------------------- | ------------------------------------- | +| `get_project_estimate` | Get the estimate scheme for a project | +| `list_project_estimate_points` | List all estimate point values | +| `create_project_estimate` | Create an estimate scheme | +| `update_project_estimate` | Update estimate scheme settings | +| `delete_project_estimate` | Delete the estimate scheme | +| `link_estimate_to_project` | Link an estimate scheme to a project | +| `create_project_estimate_points` | Add estimate point values | +| `update_project_estimate_point` | Update a single estimate point | +| `delete_project_estimate_point` | Remove an estimate point | + +--- + +## Work Items + +**CRUD** + +| Tool | Description | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `list_work_items` | List work items. Supports filters: `query`, `assignee_ids`, `state_ids`, `state_groups`, `priorities`, `label_ids`, `type_ids`, `cycle_ids`, `module_ids`, `is_archived`, `workspace_search` | +| `create_work_item` | Create a work item | +| `retrieve_work_item` | Get a work item by UUID | +| `retrieve_work_item_by_identifier` | Get by human ID. Requires `project_identifier` (e.g. `ENG`) and `work_item_identifier` (e.g. `42`) | +| `update_work_item` | Update work item fields | +| `delete_work_item` | Delete a work item | +| `search_work_items` | Full-text search across work items | + +**Assignees & labels** + +| Tool | Description | +| --------------------------- | ---------------------------- | +| `add_work_item_assignee` | Assign a user to a work item | +| `remove_work_item_assignee` | Unassign a user | +| `add_work_item_label` | Add a label to a work item | +| `remove_work_item_label` | Remove a label | + +**Archive** + +| Tool | Description | +| -------------------------- | ----------------------------- | +| `list_archived_work_items` | List archived work items | +| `archive_work_item` | Archive a work item | +| `unarchive_work_item` | Restore an archived work item | + +--- + +## Work Item Activities + +Both tools require `project_id` and `work_item_id`. + +| Tool | Description | +| ----------------------------- | ------------------------------------- | +| `list_work_item_activities` | List all change events on a work item | +| `retrieve_work_item_activity` | Get a single activity entry | + +--- + +## Work Item Comments + +| Tool | Description | +| ---------------------------- | -------------------------------------------------------------------------------- | +| `list_work_item_comments` | List all comments on a work item | +| `retrieve_work_item_comment` | Get a single comment | +| `create_work_item_comment` | Create a comment. Requires `comment_html` (e.g. `

Fixed in commit abc123

`) | +| `update_work_item_comment` | Update a comment | +| `delete_work_item_comment` | Delete a comment | + +--- + +## Work Item Links + +External URLs attached to a work item (e.g. Figma designs, PRs). + +| Tool | Description | +| ------------------------- | ------------------------------------------------ | +| `list_work_item_links` | List all links on a work item | +| `retrieve_work_item_link` | Get a single link | +| `create_work_item_link` | Add a link. Requires `url`. `title` is optional. | +| `update_work_item_link` | Update a link | +| `delete_work_item_link` | Remove a link | + +--- + +## Work Item Relations + +| Tool | Description | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `list_work_item_relations` | List all relations for a work item | +| `create_work_item_relation` | Create a relation. Requires `related_work_item_id` and `relation_type`: `blocking` · `blocked_by` · `duplicate_of` · `duplicate` · `relates_to` | +| `remove_work_item_relation` | Remove a relation | + +--- + +## Work Item Properties + +Custom fields defined per work item type. Use `list_work_item_types` to get a `work_item_type_id`. + +**Property definitions** + +All require `project_id` and `work_item_type_id`. Retrieve / update / delete also require `work_item_property_id`. + +| Tool | Description | +| ----------------------------- | -------------------------------------------------------------------------------- | +| `list_work_item_properties` | List all custom properties for a work item type | +| `create_work_item_property` | Create a property. Requires `display_name` and `property_type` (see table below) | +| `retrieve_work_item_property` | Get a property by ID | +| `update_work_item_property` | Update a property | +| `delete_work_item_property` | Delete a property | + +**`property_type` values** + +| Type | Extra settings | +| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `TEXT` | `settings: {"display_format": "single-line" \| "multi-line" \| "readonly"}` | +| `DATETIME` | `settings: {"display_format": "MMM dd, yyyy" \| "dd/MM/yyyy" \| "MM/dd/yyyy" \| "yyyy/MM/dd"}` | +| `RELATION` | `relation_type: "ISSUE"` or `"USER"` | +| `DECIMAL` · `BOOLEAN` · `OPTION` · `URL` · `EMAIL` · `FILE` · `FORMULA` | — | + +**Property values** + +All require `project_id`, `work_item_id`, `property_id`. + +| Tool | Description | +| --------------------------------- | -------------------------------- | +| `get_work_item_property_value` | Get the current value | +| `set_work_item_property_value` | Set or update the value (upsert) | +| `delete_work_item_property_value` | Clear the value | + +**Value format by property type** + +| Property type | Value format | +| ---------------------------------------------- | ------------ | +| `TEXT` · `URL` · `EMAIL` · `DATETIME` · `FILE` | `string` | +| `DECIMAL` | `number` | +| `BOOLEAN` | `boolean` | +| `OPTION` · `RELATION` (single) | UUID string | +| `OPTION` · `RELATION` (multi-value) | UUID array | + +--- + +## Work Item Types + +All require `project_id`. CRUD operations (except list) also require `type_id`. + +| Tool | Description | +| ------------------------- | ------------------------------------- | +| `list_work_item_types` | List all work item types in a project | +| `create_work_item_type` | Create a custom type | +| `retrieve_work_item_type` | Get a type by ID | +| `update_work_item_type` | Update a type | +| `delete_work_item_type` | Delete a type | + +--- + +## Worklogs + +Time tracking. All durations are in **minutes**. + +| Tool | Description | +| ----------------- | ---------------------------------------------------------------------------------- | +| `list_work_logs` | List all worklogs for a work item | +| `create_work_log` | Log time. Requires `project_id`, `work_item_id`, `duration` (integer, minutes ≥ 0) | +| `update_work_log` | Update a worklog entry | +| `delete_work_log` | Delete a worklog entry | + +--- + +## States + +| Tool | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `list_states` | List all states in a project | +| `create_state` | Create a state. Requires `name`, `color` (hex, e.g. `#FF5733`), and `group`: `backlog` · `unstarted` · `started` · `completed` · `cancelled` | +| `retrieve_state` | Get a state by ID | +| `update_state` | Update a state | +| `delete_state` | Delete a state | + +--- + +## Labels + +| Tool | Description | +| ---------------- | ------------------------------------------------------------------------- | +| `list_labels` | List all labels in a project | +| `create_label` | Create a label. Requires `name` and `color`. `parent` (UUID) is optional. | +| `retrieve_label` | Get a label by ID | +| `update_label` | Update a label | +| `delete_label` | Delete a label | + +--- + +## Cycles + +Time-boxed iterations (sprints). + +**CRUD** + +| Tool | Description | +| ---------------------- | ------------------------------------------------------------------------------ | +| `list_cycles` | List active cycles | +| `list_archived_cycles` | List archived cycles | +| `create_cycle` | Create a cycle. Requires `name`. `start_date` and `end_date` are `YYYY-MM-DD`. | +| `retrieve_cycle` | Get a cycle by ID | +| `update_cycle` | Update cycle fields | +| `delete_cycle` | Delete a cycle | + +**Work items** + +| Tool | Description | +| ----------------------------- | ------------------------------- | +| `list_cycle_work_items` | List work items in a cycle | +| `add_work_items_to_cycle` | Add work items to a cycle | +| `remove_work_item_from_cycle` | Remove a work item from a cycle | + +**Lifecycle** + +| Tool | Description | +| --------------------------- | ----------------------------------------------------------------------------------------------------- | +| `transfer_cycle_work_items` | Move all incomplete items to another cycle. Requires `cycle_id` (source) and `new_cycle_id` (target). | +| `complete_cycle` | Mark a cycle as complete | +| `archive_cycle` | Archive a cycle | +| `unarchive_cycle` | Restore an archived cycle | + +--- + +## Modules + +Feature groupings within a project. + +**CRUD** + +| Tool | Description | +| ----------------------- | -------------------------- | +| `list_modules` | List all modules | +| `list_archived_modules` | List archived modules | +| `create_module` | Create a module | +| `retrieve_module` | Get a module by ID | +| `update_module` | Update module fields | +| `delete_module` | Delete a module | +| `archive_module` | Archive a module | +| `unarchive_module` | Restore an archived module | + +**Work items** + +| Tool | Description | +| ------------------------------ | -------------------------------- | +| `list_module_work_items` | List work items in a module | +| `add_work_items_to_module` | Add work items to a module | +| `remove_work_item_from_module` | Remove a work item from a module | + +--- + +## Epics + +Large work items that group related items. The server resolves the Epic work item type automatically. All require `project_id`. Specific-epic operations also require `epic_id`. + +| Tool | Description | +| --------------- | --------------------------- | +| `list_epics` | List all epics in a project | +| `create_epic` | Create an epic | +| `retrieve_epic` | Get an epic by ID | +| `update_epic` | Update epic fields | +| `delete_epic` | Delete an epic | + +--- + +## Milestones + +| Tool | Description | +| ---------------------------------- | ---------------------------------- | +| `list_milestones` | List all milestones | +| `create_milestone` | Create a milestone | +| `retrieve_milestone` | Get a milestone by ID | +| `update_milestone` | Update milestone fields | +| `delete_milestone` | Delete a milestone | +| `list_milestone_work_items` | List work items in a milestone | +| `add_work_items_to_milestone` | Add work items to a milestone | +| `remove_work_items_from_milestone` | Remove work items from a milestone | + +--- + +## Initiatives + +Workspace-scoped strategic goals. No `project_id` required. Specific-initiative operations require `initiative_id`. + +| Tool | Description | +| --------------------- | ------------------------------------- | +| `list_initiatives` | List all initiatives in the workspace | +| `create_initiative` | Create an initiative | +| `retrieve_initiative` | Get an initiative by ID | +| `update_initiative` | Update initiative fields | +| `delete_initiative` | Delete an initiative | + +--- + +## Intake + +Triage queue for incoming work before it enters a project. + +| Tool | Description | +| --------------------------- | -------------------------------------------------------- | +| `list_intake_work_items` | List all intake items in a project | +| `create_intake_work_item` | Create an intake item. Requires `project_id` and `name`. | +| `retrieve_intake_work_item` | Get an intake item by ID | +| `update_intake_work_item` | Update or triage an item (accept / decline / snooze) | +| `delete_intake_work_item` | Delete an intake item | + +--- + +## Pages + +Wiki-style documents, workspace-scoped or project-scoped. + +**Workspace pages** + +| Tool | Description | +| ------------------------- | ------------------------------ | +| `list_workspace_pages` | List all workspace-level pages | +| `retrieve_workspace_page` | Get a workspace page by ID | +| `create_workspace_page` | Create a workspace page | + +**Project pages** + +| Tool | Description | +| ----------------------- | --------------------------- | +| `list_project_pages` | List all pages in a project | +| `retrieve_project_page` | Get a project page by ID | +| `create_project_page` | Create a project page | + +**Work item page links** + +| Tool | Description | +| ---------------------------- | ------------------------------------------------------------------------------- | +| `list_work_item_pages` | List pages linked to a work item | +| `attach_page_to_work_item` | Link a page. Requires `project_id`, `work_item_id`, `page_id`. | +| `detach_page_from_work_item` | Unlink a page. Requires `work_item_page_id` (the link UUID, not the page UUID). | + +--- + +## Example workflows + +**Look up a work item** + +> What is ENG-42 about? + +→ `retrieve_work_item_by_identifier(project_identifier="ENG", work_item_identifier="42")` + +--- + +**Create and assign a work item** + +> Create a high-priority bug in ENG: "Login times out on Safari". Assign to me. + +→ `get_me` → `create_work_item` + +--- + +**Update state and leave a comment** + +> Mark ENG-88 as done and comment "Fixed in commit abc1234, needs QA." + +→ `list_states` → `update_work_item` + `create_work_item_comment` + +--- + +**Plan a sprint** + +> Create Sprint 15 in ENG starting 2025-06-02 ending 2025-06-15. Move all incomplete issues from Sprint 14. + +→ `create_cycle` → `list_cycles` → `transfer_cycle_work_items` + +--- + +**Filter work items** + +> Show all high-priority bugs assigned to me still in progress. + +→ `list_work_items(priorities=["high"], state_groups=["started"], assignee_ids=[...])` + +--- + +**Log time** + +> Log 90 minutes on ENG-42: "Implemented retry logic." + +→ `retrieve_work_item_by_identifier` → `create_work_log(duration=90)` + +--- + +**Code + project management** + +> Look up ENG-42, implement what it describes in src/auth.ts, then mark it as done and log 2 hours. + +→ `retrieve_work_item_by_identifier` → (code edits) → `update_work_item(state_id=...)` + `create_work_log(duration=120)` + +--- + +**Triage intake** + +> Show all intake items in MOBILE and accept the ones related to crash reports. + +→ `list_intake_work_items` → `update_intake_work_item` (status: accepted) diff --git a/docs/dev-tools/mcp-server.md b/docs/dev-tools/mcp-server.md index 7b18d427..e82c9e55 100644 --- a/docs/dev-tools/mcp-server.md +++ b/docs/dev-tools/mcp-server.md @@ -1,377 +1,217 @@ --- -title: MCP server -description: Setup MCP Server for Plane. Integrate Plane with Model Context Protocol for AI-powered project management. -keywords: plane, developer tools, integrations, extensions, mcp server, protocol, integration +title: MCP Server +description: Connect Cursor, VS Code, Claude, Windsurf, and Zed to your Plane workspace. Create work items, manage cycles, search across projects — all through natural language. +keywords: plane, mcp server, ai, cursor, claude, vs code, windsurf, project management, mcp --- -# MCP server +# MCP Server -The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that defines how AI applications discover and call external tools. Any client that speaks MCP can talk to any server that speaks MCP. +The Plane MCP Server lets AI tools interact with your Plane workspace through the [Model Context Protocol](https://modelcontextprotocol.io). Ask your AI tool to create work items, search across projects, plan sprints, or log time — it handles the API calls. -The Plane MCP Server is a bridge that lets AI models interact with Plane. It exposes Plane's full API surface as MCP tools, so your AI tool can create work items, manage sprints, track time, and organise work without you leaving your editor or chat interface. - -## Transport modes - -The server supports four transport modes. The right one depends on your deployment and usecase. - -| Transport | For | Auth method | How to start | -| ------------------- | --------------------------------- | -------------------------- | ------------------------ | -| HTTP with OAuth | Plane Cloud users, simplest setup | Browser-based OAuth flow | `plane-mcp-server http` | -| HTTP with PAT Token | Automated workflows, CI/CD | API key in request headers | `plane-mcp-server http` | -| Local Stdio | Local dev, self-hosted Plane | Environment variables | `plane-mcp-server stdio` | -| SSE (Legacy) | Existing integrations | Browser-based OAuth flow | `plane-mcp-server http` | - -## Authentication model - -The server has three authentication mechanisms, one per transport variant. - -### OAuth auth (HTTP with OAuth, SSE) - -For cloud deployments, the server acts as an **OAuth proxy** to Plane's OAuth system: - -1. The MCP client redirects the user to the Plane OAuth authorization page -2. The user logs into Plane and grants access -3. Plane returns an OAuth token which the server validates by calling `/api/v1/users/me/` -4. Subsequent MCP requests carry this token, from which the server extracts the workspace slug - -The server supports OAuth redirect URIs for all major MCP clients: -`cursor://`, `vscode://`, `vscode-insiders://`, `windsurf://`, `claude://` - -### Header auth (HTTP with PAT Token) - -For automated workflows, the MCP client sends two headers with every request: - -- `x-api-key` - a Plane API token -- `x-workspace-slug` - the workspace identifier - -The server validates the API key against Plane's `/api/v1/users/me/` endpoint on each request. No browser interaction required. - -### Environment variable auth (stdio) - -For stdio mode, credentials are read from environment variables at startup: - -- `PLANE_API_KEY` - your Plane API token -- `PLANE_WORKSPACE_SLUG` - your workspace identifier -- `PLANE_BASE_URL` - API URL for self-hosted instances (defaults to `https://api.plane.so`) - -## Identifier system - -Plane uses two kinds of identifiers for work items. - -- **Readable identifier** - human-friendly, e.g., `ENG-42` - - Composed of the project identifier (`ENG`) and a sequence number (`42`) - - Used in URLs, UI, and team communication - -- **UUID** - machine-friendly, e.g., `3fa85f64-5717-4562-b3fc-2c963f66afa6` - - Used by all other tools for `project_id`, `work_item_id`, `cycle_id`, etc. - - Returned by every API response +::: info Beta +The Plane MCP Server is in **Beta**. API surface may change. Send issues to support@plane.so. +::: --- -## How-to guides - -Plane hosts the MCP server for you at **`https://mcp.plane.so`**. If you run your own instance of the MCP server, replace `https://mcp.plane.so` with your own server's public URL (e.g., `https://mcp.yourcompany.com`) in all client config examples below. - -### Prerequisites - -**For all modes:** - -- A Plane account with access to at least one workspace - -**For stdio mode (local and self-hosted deployments)** - -- Python 3.10+ installed (`python --version`) -- `uv` package manager (recommended). See [Installing uv](https://docs.astral.sh/uv/getting-started/installation/) - -#### Get your API key (required for stdio and PAT token modes) - -1. Open Plane and go to your workspace. -2. Generate a token. You can use either: - - **Personal Access Token** - go to **Profile Settings → API Tokens**. - - **Workspace Access Token** - go to **Workspace Settings → Access Tokens**. -3. Click **Add access token**, name it (e.g., "MCP Server"), click **Generate token**. -4. Copy the token as it will not be shown again. - -#### Get your workspace slug - -The slug is the short identifier in your Plane URL. For: - -``` -https://app.plane.so/acme-corp/ -``` - -the slug is `acme-corp`. - -### Claude Desktop - -Config file: `/claude_desktop_config.json`. +## Quick start (OAuth) -Quit Claude Desktop before editing, then relaunch and click the hammer icon (🔨) to confirm Plane tools are listed. +OAuth is the fastest path — browser login, no API key needed. Pick your client below. -#### Stdio +### Cursor -Spawns the server as a local subprocess. Credentials come from environment variables. +Edit `~/.cursor/mcp.json`: ```json { "mcpServers": { "plane": { - "command": "uvx", - "args": ["plane-mcp-server", "stdio"], - "env": { - "PLANE_API_KEY": "your_api_key_here", - "PLANE_WORKSPACE_SLUG": "your-workspace-slug", - "PLANE_BASE_URL": "https://plane.yourcompany.com" - } + "url": "https://mcp.plane.so/http/mcp", + "type": "http" } } } ``` -#### HTTP with OAuth +Open **Settings → MCP** and restart Cursor to confirm Plane is listed. -Connects to a remote MCP server. Claude Desktop opens a browser window for the Plane OAuth flow on first use. +### VS Code -```json -{ - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/http/mcp", - "type": "http" - } - } -} -``` +Requires GitHub Copilot. Open Copilot chat (`Ctrl+Alt+I`) and switch to **Agent** mode. -#### HTTP with PAT Token +Edit `.vscode/mcp.json` (workspace) or `settings.json` under `"mcp"` (user-level). -Connects to the PAT endpoint using API key headers. No browser interaction required - suitable for shared team setups where users authenticate via their own API key. +::: warning +VS Code uses `"servers"`, not `"mcpServers"`. +::: ```json { - "mcpServers": { + "servers": { "plane": { - "url": "https://mcp.plane.so/http/api-key/mcp", - "type": "http", - "headers": { - "x-api-key": "your_api_key_here", - "x-workspace-slug": "your-workspace-slug" - } + "url": "https://mcp.plane.so/http/mcp", + "type": "http" } } } ``` -#### SSE (Legacy) +### Windsurf -For existing integrations already using the SSE transport. +Edit `~/.codeium/windsurf/mcp_config.json`. Windsurf uses `serverUrl` (not `url`) +for remote HTTP servers. ```json { "mcpServers": { "plane": { - "url": "https://mcp.plane.so/sse", - "type": "sse" + "serverUrl": "https://mcp.plane.so/http/mcp" } } } ``` ---- - -### Claude Code (CLI) - -Claude Code manages MCP servers via `claude mcp add` or a settings file. - -#### Stdio +Restart Windsurf and open the Cascade panel. -```bash -claude mcp add plane \ - -e PLANE_API_KEY=your_api_key_here \ - -e PLANE_WORKSPACE_SLUG=your-workspace-slug \ - -e PLANE_BASE_URL=https://plane.yourcompany.com \ - -- uvx plane-mcp-server stdio -``` +### Zed -Settings file (`.claude/settings.json` for project scope, `~/.claude/settings.json` for user scope): +Edit `~/.config/zed/settings.json` under `"context_servers"`. With no `headers`, Zed prompts you for OAuth on first use. ```json { - "mcpServers": { - "plane": { - "command": "uvx", - "args": ["plane-mcp-server", "stdio"], - "env": { - "PLANE_API_KEY": "your_api_key_here", - "PLANE_WORKSPACE_SLUG": "your-workspace-slug" - } + "context_servers": { + "plane-mcp-server": { + "url": "https://mcp.plane.so/http/mcp" } } } ``` -#### HTTP with OAuth +Open the AI panel (`Cmd+Shift+A`) to use Plane tools. -```bash -claude mcp add plane \ - --transport http \ - --url https://mcp.plane.so/http/mcp -``` +### Claude Code -Claude Code will open a browser for the Plane OAuth flow. Settings file equivalent: +Run in your terminal: -```json -{ - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/http/mcp", - "type": "http" - } - } -} +```bash +claude mcp add --transport http plane https://mcp.plane.so/http/mcp ``` -#### HTTP with PAT Token +Start a session and run `/mcp` to authenticate via browser. -```bash -claude mcp add plane \ - --transport http \ - --url https://mcp.plane.so/http/api-key/mcp \ - --header "x-api-key: your_api_key_here" \ - --header "x-workspace-slug: your-workspace-slug" -``` +**Sharing with your team?** Add `--scope project` — writes to `.mcp.json` in your repo root so the whole team picks it up via git. -Settings file equivalent: +::: warning +MCP configs live in `~/.claude.json` or `.mcp.json` — **not** `.claude/settings.json`. Always use `claude mcp add` / `claude mcp add-json` so the CLI writes to the right file. +::: -```json -{ - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/http/api-key/mcp", - "type": "http", - "headers": { - "x-api-key": "your_api_key_here", - "x-workspace-slug": "your-workspace-slug" - } - } - } -} -``` +### Claude Desktop -#### SSE (Legacy) +Claude Desktop doesn't support remote HTTP natively. Use `mcp-remote` — a local proxy +that bridges Claude Desktop to Plane's cloud server over Streamable HTTP. +**Requires Node.js 18+.** -```bash -claude mcp add plane \ - --transport sse \ - --url https://mcp.plane.so/sse -``` +Config file: -Settings file equivalent: +- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` + +Quit Claude Desktop before editing, then relaunch and check the 🔨 icon. ```json { "mcpServers": { "plane": { - "url": "https://mcp.plane.so/sse", - "type": "sse" + "command": "npx", + "args": ["mcp-remote@latest", "https://mcp.plane.so/http/mcp"] } } } ``` -Verify any configuration with: +On first launch, `mcp-remote` opens a browser for the Plane OAuth flow. -```bash -claude mcp list -``` +::: tip No Node.js? +Use the SSE fallback instead: `"url": "https://mcp.plane.so/sse", "type": "sse"`. +::: -#### Using Plane in Claude Code sessions +### Claude.ai -```bash -claude +Available on eligible Claude.ai plans. -> Look up work item ENG-42 and implement what it describes. +**Pro / Max:** -> After fixing the bug, mark ENG-42 as done and log 90 minutes of work. +1. **Customize → Connectors → +** (add button) +2. **Add custom connector** +3. URL: `https://mcp.plane.so/http/mcp` +4. Complete the Plane OAuth flow in the browser. -> Create work items for each TODO in src/auth.ts and add them to the current sprint. -``` +**Team / Enterprise** (admins only): **Organization settings → Connectors → Add custom connector** with the same URL. --- -### Claude.ai / Claude Chat (Web) - -Claude.ai supports remote MCP servers for eligible plans. Because it runs in a browser it cannot spawn local processes, stdio is not available here. +## PAT Token -#### HTTP with OAuth +Sends two headers with every request — no browser interaction. Use for CI/CD, shared team configs, automated scripts. -1. Go to **Customize → Connectors ** in Claude.ai. -2. Click **Add custom connector**. -3. Enter the server URL: `https://mcp.plane.so/http/mcp` -4. Claude.ai redirects you through the Plane OAuth flow. +- `Authorization: Bearer ` +- `x-workspace-slug: ` -#### HTTP with PAT Token +**Prerequisites:** -If your Claude.ai plan supports custom headers in integrations: +- **API key** — In Plane: **Profile Settings → API Tokens** (personal) or **Workspace Settings → Access Tokens** (workspace-level). Click **Add token**, name it, **Generate**. Copy it now — it won't be shown again. +- **Workspace slug** — Found in your Plane URL. For `https://app.plane.so/acme-corp/`, the slug is `acme-corp`. -- URL: `https://mcp.plane.so/http/api-key/mcp` -- Headers: `x-api-key: your_api_key_here`, `x-workspace-slug: your-workspace-slug` - -#### SSE (Legacy) - -- URL: `https://mcp.plane.so/sse` - ---- +::: info Clients that don't support custom headers +**Claude Desktop** — use [Stdio](#stdio) instead.
+**Claude.ai** — use [OAuth](#claude-ai) instead (UI doesn't expose arbitrary headers). +::: ### Cursor -Config file: `~/.cursor/mcp.json` - -Open Cursor → **Settings** → search **MCP** → open the config file. Restart Cursor (`Cmd/Ctrl + Shift + P → Reload Window`) after saving. - -#### Stdio - ```json { "mcpServers": { "plane": { - "command": "uvx", - "args": ["plane-mcp-server", "stdio"], - "env": { - "PLANE_API_KEY": "your_api_key_here", - "PLANE_WORKSPACE_SLUG": "your-workspace-slug", - "PLANE_BASE_URL": "https://plane.yourcompany.com" + "url": "https://mcp.plane.so/http/api-key/mcp", + "type": "http", + "headers": { + "Authorization": "Bearer your_api_key_here", + "x-workspace-slug": "your-workspace-slug" } } } } ``` -#### HTTP with OAuth +### Windsurf -The `cursor://` redirect URI is registered natively in the OAuth provider. +Windsurf uses `serverUrl` (not `url`) for remote HTTP servers. ```json { "mcpServers": { "plane": { - "url": "https://mcp.plane.so/http/mcp", - "type": "http" + "serverUrl": "https://mcp.plane.so/http/api-key/mcp", + "headers": { + "Authorization": "Bearer your_api_key_here", + "x-workspace-slug": "your-workspace-slug" + } } } } ``` -#### HTTP with PAT Token +### VS Code ```json { - "mcpServers": { + "servers": { "plane": { "url": "https://mcp.plane.so/http/api-key/mcp", "type": "http", "headers": { - "x-api-key": "your_api_key_here", + "Authorization": "Bearer your_api_key_here", "x-workspace-slug": "your-workspace-slug" } } @@ -379,32 +219,56 @@ The `cursor://` redirect URI is registered natively in the OAuth provider. } ``` -#### SSE (Legacy) +### Zed ```json { - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/sse", - "type": "sse" + "context_servers": { + "plane-mcp-server": { + "url": "https://mcp.plane.so/http/api-key/mcp", + "headers": { + "Authorization": "Bearer your_api_key_here", + "x-workspace-slug": "your-workspace-slug" + } } } } ``` +### Claude Code + +```bash +claude mcp add-json plane '{ + "type": "http", + "url": "https://mcp.plane.so/http/api-key/mcp", + "headers": { + "Authorization": "Bearer your_api_key_here", + "x-workspace-slug": "your-workspace-slug" + } +}' +``` + --- -### VS Code +## Stdio + +Runs `plane-mcp-server` locally per-client as a subprocess. Use when remote HTTP isn't an option — +air-gapped envs, local dev, or self-hosted Plane without a deployed MCP server. +Uses the same API key + workspace slug as [PAT](#pat-token). -VS Code supports MCP through GitHub Copilot (requires a Copilot subscription). Open the Copilot chat panel (`Ctrl+Alt+I`), switch to **Agent** mode. The `vscode://` and `vscode-insiders://` redirect URIs are registered in the OAuth provider. +**Prerequisites:** Python 3.10+ and `uvx` installed and available on your `PATH`. -Config can be set at workspace level (`.vscode/mcp.json`) or user level (VS Code `settings.json` under the `"mcp"` key). Examples below use `.vscode/mcp.json`. +| Variable | Required | Description | +| ---------------------- | -------- | --------------------------------------------------------------------- | +| `PLANE_API_KEY` | Yes | API key from your workspace settings | +| `PLANE_WORKSPACE_SLUG` | Yes | Your workspace slug | +| `PLANE_BASE_URL` | No | API URL for self-hosted instances. Defaults to `https://api.plane.so` | -#### Stdio +### Cursor / Windsurf ```json { - "servers": { + "mcpServers": { "plane": { "command": "uvx", "args": ["plane-mcp-server", "stdio"], @@ -418,58 +282,55 @@ Config can be set at workspace level (`.vscode/mcp.json`) or user level (VS Code } ``` -#### HTTP with OAuth +### VS Code ```json { "servers": { "plane": { - "url": "https://mcp.plane.so/http/mcp", - "type": "http" + "command": "uvx", + "args": ["plane-mcp-server", "stdio"], + "env": { + "PLANE_API_KEY": "your_api_key_here", + "PLANE_WORKSPACE_SLUG": "your-workspace-slug", + "PLANE_BASE_URL": "https://plane.yourcompany.com" + } } } } ``` -#### HTTP with PAT Token +### Zed ```json { - "servers": { - "plane": { - "url": "https://mcp.plane.so/http/api-key/mcp", - "type": "http", - "headers": { - "x-api-key": "your_api_key_here", - "x-workspace-slug": "your-workspace-slug" + "context_servers": { + "plane-mcp-server": { + "command": "uvx", + "args": ["plane-mcp-server", "stdio"], + "env": { + "PLANE_API_KEY": "your_api_key_here", + "PLANE_WORKSPACE_SLUG": "your-workspace-slug", + "PLANE_BASE_URL": "https://plane.yourcompany.com" } } } } ``` -#### SSE (Legacy) +### Claude Code -```json -{ - "servers": { - "plane": { - "url": "https://mcp.plane.so/sse", - "type": "sse" - } - } -} +```bash +claude mcp add plane \ + -e PLANE_API_KEY=your_api_key_here \ + -e PLANE_WORKSPACE_SLUG=your-workspace-slug \ + -e PLANE_BASE_URL=https://plane.yourcompany.com \ + -- uvx plane-mcp-server stdio ``` ---- - -### Windsurf - -Config file: `~/.codeium/windsurf/mcp_config.json` +Add `--scope project` to write to `.mcp.json` (team-shared via git) instead of `~/.claude.json` (your local copy). -Restart Windsurf after saving, then open the Cascade panel. The `windsurf://` redirect URI is registered in the OAuth provider. - -#### Stdio +### Claude Desktop ```json { @@ -487,993 +348,103 @@ Restart Windsurf after saving, then open the Cascade panel. The `windsurf://` re } ``` -#### HTTP with OAuth +--- -```json -{ - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/http/mcp", - "type": "http" - } - } -} -``` +## Other clients (mcp-remote bridge) -#### HTTP with PAT Token +Any MCP client that supports stdio but not remote HTTP can use `mcp-remote` as a proxy bridge. It runs locally as a subprocess and forwards requests to `https://mcp.plane.so/http/mcp` over Streamable HTTP, handling the OAuth flow on first run. -```json -{ - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/http/api-key/mcp", - "type": "http", - "headers": { - "x-api-key": "your_api_key_here", - "x-workspace-slug": "your-workspace-slug" - } - } - } -} -``` +**Requires Node.js 18+.** -#### SSE (Legacy) +| Setting | Value | +| --------- | ------------------------------------------------- | +| Command | `npx` | +| Arguments | `mcp-remote@latest https://mcp.plane.so/http/mcp` | + +For clients with JSON config: ```json { - "mcpServers": { - "plane": { - "url": "https://mcp.plane.so/sse", - "type": "sse" - } - } + "command": "npx", + "args": ["mcp-remote@latest", "https://mcp.plane.so/http/mcp"] } ``` --- -### Zed +## Self-hosted Plane -Config file: `~/.config/zed/settings.json` under `"context_servers"`. Zed uses a different schema from other clients - the stdio command goes inside a `"command"` object with `"path"` instead of `"command"`. +If your Plane lives at something like `plane.yourcompany.com` instead of `app.plane.so`, point your client at it with `PLANE_BASE_URL` — drop it into any [Stdio](#stdio) config's `env` block. -#### Stdio +Sanity-check before wiring up a client: -```json -{ - "context_servers": { - "plane-mcp-server": { - "command": { - "path": "uvx", - "args": ["plane-mcp-server", "stdio"], - "env": { - "PLANE_API_KEY": "your_api_key_here", - "PLANE_WORKSPACE_SLUG": "your-workspace-slug", - "PLANE_BASE_URL": "https://plane.yourcompany.com" - } - }, - "settings": {} - } - } -} +```bash +curl -H "x-api-key: YOUR_API_KEY" \ + "https://plane.yourcompany.com/api/v1/users/me/" ``` -#### HTTP with OAuth +`200` = key + URL good. -```json -{ - "context_servers": { - "plane-mcp-server": { - "url": "https://mcp.plane.so/http/mcp", - "settings": {} - } - } -} -``` - -#### HTTP with PAT Token - -```json -{ - "context_servers": { - "plane-mcp-server": { - "url": "https://mcp.plane.so/http/api-key/mcp", - "headers": { - "x-api-key": "your_api_key_here", - "x-workspace-slug": "your-workspace-slug" - }, - "settings": {} - } - } -} -``` - -#### SSE (Legacy) - -```json -{ - "context_servers": { - "plane-mcp-server": { - "url": "https://mcp.plane.so/sse", - "settings": {} - } - } -} -``` - -Open the AI panel (`Cmd + Shift + A`) to use Plane tools in conversation. - -## Self-hosted Plane deployments - -Set `PLANE_BASE_URL` to the public URL of your Plane instance (e.g., https://plane.yourcompany.com). This is used for user-facing OAuth redirects and API calls in stdio mode. - -In HTTP/SSE mode, the server also makes internal server-to-server calls to Plane for token validation. If your infrastructure routes internal traffic differently from public traffic (e.g., via a private network, service mesh, or internal load balancer), set `PLANE_INTERNAL_BASE_URL` to the internal address. When set, all server-to-server calls use this URL and only OAuth redirects use `PLANE_BASE_URL`. - -If `PLANE_INTERNAL_BASE_URL` is not set, it falls back to PLANE_BASE_URL for all calls. - -Before connecting a client, verify your credentials reach the instance: - -```bash -curl -H "x-api-key: YOUR_API_KEY" \ - "https://plane.yourcompany.com/api/v1/users/me/" -``` - -A `200` response confirms the API key and URL are correct. - ---- - -## Tool reference - -The server exposes 100+ tools across 20 modules. All tools are registered identically regardless of transport mode. The same tools are available via stdio, HTTP/OAuth, HTTP/PAT, and SSE. - -### Users - -#### `get_me` - -Returns the profile of the currently authenticated user. No parameters. - ---- - -### Workspaces - -#### `get_workspace_members` - -Returns all members of the workspace. - -#### `get_workspace_features` - -Returns enabled features for the workspace. - -#### `update_workspace_features` - -Updates workspace-level feature flags. - ---- - -### Projects - -#### `list_projects` - -Returns all projects the current user is a member of. - -#### `create_project` - -Creates a new project. - -| Parameter | Type | Required | Description | -| ------------- | ------ | -------- | ------------------------------------------------ | -| `name` | string | **Yes** | Project display name | -| `identifier` | string | **Yes** | Short uppercase code, max 12 chars (e.g., `ENG`) | -| `description` | string | No | Project description | -| `network` | string | No | `0` (secret) or `2` (public) | - -#### `retrieve_project` - -Returns details of a single project. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `update_project` - -Updates project fields. All fields are optional. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| other fields | partial | No | - -#### `delete_project` - -Deletes a project. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `get_project_worklog_summary` - -Returns time-tracking summary for a project. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `get_project_members` - -Returns all members of a project. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `get_project_features` - -Returns the feature configuration for a project (modules, cycles, pages, etc.). - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `update_project_features` - -Updates which features are enabled on a project. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| feature fields | partial | No | - ---- - -### Work Items - -#### `list_work_items` - -Lists work items in a project, or searches across the workspace when filters are provided. - -When any filter parameter is set, the tool uses Plane's advanced search endpoint (supports workspace-wide search). Without filters it uses the standard paginated list endpoint. - -| Parameter | Type | Required | Description | -| ------------------ | ----------- | ----------- | --------------------------------------------------------------- | -| `project_id` | UUID string | Conditional | Required when no filters are provided | -| `query` | string | No | Free-text search across name and description | -| `assignee_ids` | UUID[] | No | Filter by assignee | -| `state_ids` | UUID[] | No | Filter by state | -| `state_groups` | string[] | No | `backlog` · `unstarted` · `started` · `completed` · `cancelled` | -| `priorities` | string[] | No | `urgent` · `high` · `medium` · `low` · `none` | -| `label_ids` | UUID[] | No | Filter by label | -| `type_ids` | UUID[] | No | Filter by work item type | -| `cycle_ids` | UUID[] | No | Filter by cycle | -| `module_ids` | UUID[] | No | Filter by module | -| `is_archived` | boolean | No | Filter by archived status | -| `created_by_ids` | UUID[] | No | Filter by creator | -| `workspace_search` | boolean | No | Search across all projects (requires filters) | -| `limit` | integer | No | Max results when using filters | -| `cursor` | string | No | Pagination cursor (list mode) | -| `per_page` | integer | No | Results per page, 1–100 (list mode) | -| `expand` | string | No | Comma-separated fields to expand | -| `fields` | string | No | Comma-separated fields to include | -| `order_by` | string | No | Sort field | - -#### `create_work_item` - -Creates a new work item in a project. - -| Parameter | Type | Required | Description | -| ------------------ | ----------- | -------- | --------------------------------------------- | -| `project_id` | UUID string | **Yes** | Target project | -| `name` | string | **Yes** | Work item title | -| `description_html` | string | No | HTML body | -| `state_id` | UUID string | No | Initial state | -| `priority` | string | No | `urgent` · `high` · `medium` · `low` · `none` | -| `assignee_ids` | UUID[] | No | Assigned members | -| `label_ids` | UUID[] | No | Labels | -| `type_id` | UUID string | No | Work item type | -| `parent_id` | UUID string | No | Parent work item (sub-item) | -| `start_date` | string | No | `YYYY-MM-DD` | -| `due_date` | string | No | `YYYY-MM-DD` | - -#### `retrieve_work_item` - -Returns a single work item by UUID. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `retrieve_work_item_by_identifier` - -Returns a work item using its human-readable identifier (e.g., `ENG-42`). - -| Parameter | Type | Required | Description | -| ---------------------- | ------ | -------- | --------------------------- | -| `project_identifier` | string | **Yes** | Project prefix, e.g., `ENG` | -| `work_item_identifier` | string | **Yes** | Issue number, e.g., `42` | - -#### `update_work_item` - -Updates one or more fields on a work item. Only supplied fields are changed. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| other fields | partial | No | - -#### `delete_work_item` - -Permanently deletes a work item. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `search_work_items` - -Searches work items by text query within a project. - -| Parameter | Type | Required | Description | -| ------------ | ----------- | -------- | ----------- | -| `project_id` | UUID string | **Yes** | | -| `query` | string | **Yes** | Search text | - ---- - -### Work Item Activities - -#### `list_work_item_activities` - -Returns the activity log (history of changes) for a work item. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `retrieve_work_item_activity` - -Returns a single activity entry. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `activity_id` | UUID string | **Yes** | +::: tip Running your own MCP server too? +Whether your Plane is Cloud or self-hosted, you can skip `mcp.plane.so` and deploy `plane-mcp-server` yourself — Docker Compose, Helm, OAuth app setup: [Self-host MCP Server](/dev-tools/mcp-server-self-host). +::: --- -### Work Item Comments - -#### `list_work_item_comments` - -Returns all comments on a work item. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `retrieve_work_item_comment` - -Returns a single comment. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `comment_id` | UUID string | **Yes** | - -#### `create_work_item_comment` - -Adds a comment to a work item. Comments are stored as HTML. - -| Parameter | Type | Required | Description | -| -------------- | ----------- | -------- | --------------------------------------------------- | -| `project_id` | UUID string | **Yes** | | -| `work_item_id` | UUID string | **Yes** | | -| `comment_html` | string | **Yes** | HTML content, e.g., `

Fixed in commit abc123

` | - -#### `update_work_item_comment` - -Updates a comment's content. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `comment_id` | UUID string | **Yes** | -| `comment_html` | string | **Yes** | - -#### `delete_work_item_comment` - -Deletes a comment. - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `comment_id` | UUID string | **Yes** | - ---- - -### Work Item Links - -External URLs attached to a work item (e.g., Figma designs, PRs, docs). - -#### `list_work_item_links` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `retrieve_work_item_link` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `link_id` | UUID string | **Yes** | - -#### `create_work_item_link` - -| Parameter | Type | Required | Description | -| -------------- | ----------- | -------- | -------------------------- | -| `project_id` | UUID string | **Yes** | | -| `work_item_id` | UUID string | **Yes** | | -| `url` | string | **Yes** | External URL | -| `title` | string | No | Display title for the link | - -#### `update_work_item_link` - -| Parameter | Type | Required | -| --------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `link_id` | UUID string | **Yes** | -| `url` / `title` | string | No | - -#### `delete_work_item_link` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `link_id` | UUID string | **Yes** | - ---- - -### Work Item Relations - -Relations between work items (e.g., "blocks", "is blocked by", "duplicate of"). - -#### `list_work_item_relations` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `create_work_item_relation` - -| Parameter | Type | Required | Description | -| ---------------------- | ----------- | -------- | ----------------------------------------------------------------------- | -| `project_id` | UUID string | **Yes** | | -| `work_item_id` | UUID string | **Yes** | Source work item | -| `related_work_item_id` | UUID string | **Yes** | Target work item | -| `relation_type` | string | **Yes** | `blocking` · `blocked_by` · `duplicate_of` · `duplicate` · `relates_to` | - -#### `remove_work_item_relation` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `relation_id` | UUID string | **Yes** | - ---- - -### Work Item Properties - -Custom fields defined per project. - -#### `list_work_item_properties` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `create_work_item_property` - -| Parameter | Type | Required | Description | -| --------------- | ----------- | -------- | ------------------------ | -| `project_id` | UUID string | **Yes** | | -| `name` | string | **Yes** | Property name | -| `property_type` | string | **Yes** | Type of the custom field | - -#### `retrieve_work_item_property` - -| Parameter | Type | Required | -| ------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `property_id` | UUID string | **Yes** | - -#### `update_work_item_property` - -| Parameter | Type | Required | -| ------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `property_id` | UUID string | **Yes** | - -#### `delete_work_item_property` - -| Parameter | Type | Required | -| ------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `property_id` | UUID string | **Yes** | - ---- - -### Work Item Types - -Custom work item type definitions (e.g., Bug, Feature, Task, Epic). - -#### `list_work_item_types` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `create_work_item_type` - -| Parameter | Type | Required | -| ------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `name` | string | **Yes** | -| `description` | string | No | -| `is_active` | boolean | No | - -#### `retrieve_work_item_type` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `type_id` | UUID string | **Yes** | - -#### `update_work_item_type` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `type_id` | UUID string | **Yes** | - -#### `delete_work_item_type` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `type_id` | UUID string | **Yes** | - ---- - -### Worklogs - -Time tracking for work items. All durations are in **minutes**. - -#### `list_work_logs` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `create_work_log` - -| Parameter | Type | Required | Description | -| -------------- | ----------- | -------- | -------------------- | -| `project_id` | UUID string | **Yes** | | -| `work_item_id` | UUID string | **Yes** | | -| `duration` | integer | **Yes** | Minutes logged (≥ 0) | -| `description` | string | No | What was done | - -#### `update_work_log` - -| Parameter | Type | Required | -| -------------------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `work_log_id` | UUID string | **Yes** | -| `duration` / `description` | - | No | - -#### `delete_work_log` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | -| `work_log_id` | UUID string | **Yes** | - ---- - -### States - -Workflow states for a project's work items. - -#### `list_states` / `create_state` / `retrieve_state` / `update_state` / `delete_state` - -All state tools accept `project_id`. Create and update accept: - -| Field | Type | Required | Description | -| ------------- | ------ | ---------------- | --------------------------------------------------------------- | -| `name` | string | **Yes** (create) | Display name | -| `color` | string | **Yes** (create) | Hex color code, e.g., `#FF5733` | -| `group` | string | **Yes** (create) | `backlog` · `unstarted` · `started` · `completed` · `cancelled` | -| `description` | string | No | | - ---- - -### Labels - -Tags for work items. - -#### `list_labels` / `create_label` / `retrieve_label` / `update_label` / `delete_label` - -All label tools accept `project_id`. Create and update accept: - -| Field | Type | Required | -| -------- | ----------- | ---------------- | -| `name` | string | **Yes** (create) | -| `color` | string | **Yes** (create) | -| `parent` | UUID string | No | - ---- - -### Cycles - -Time-boxed iterations (sprints). - -#### `list_cycles` - -Returns all cycles in a project including upcoming, active, and completed. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `list_archived_cycles` - -Returns archived cycles only. - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `create_cycle` - -| Parameter | Type | Required | Description | -| ------------- | ----------- | -------- | ------------ | -| `project_id` | UUID string | **Yes** | | -| `name` | string | **Yes** | Cycle name | -| `start_date` | string | No | `YYYY-MM-DD` | -| `end_date` | string | No | `YYYY-MM-DD` | -| `description` | string | No | | - -#### `retrieve_cycle` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `cycle_id` | UUID string | **Yes** | - -#### `update_cycle` / `delete_cycle` - -Accept `project_id` and `cycle_id`. - -#### `add_work_items_to_cycle` - -| Parameter | Type | Required | -| --------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `cycle_id` | UUID string | **Yes** | -| `work_item_ids` | UUID[] | **Yes** | - -#### `remove_work_item_from_cycle` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `cycle_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `list_cycle_work_items` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `cycle_id` | UUID string | **Yes** | - -#### `transfer_cycle_work_items` - -Moves all incomplete work items from one cycle to another. - -| Parameter | Type | Required | Description | -| -------------- | ----------- | -------- | ------------ | -| `project_id` | UUID string | **Yes** | | -| `cycle_id` | UUID string | **Yes** | Source cycle | -| `new_cycle_id` | UUID string | **Yes** | Target cycle | - ---- - -### Modules - -Feature groupings within a project. - -#### `list_modules` / `list_archived_modules` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `create_module` - -| Parameter | Type | Required | -| ------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `name` | string | **Yes** | -| `description` | string | No | -| `start_date` | string | No | -| `target_date` | string | No | -| `lead` | UUID string | No | -| `members` | UUID[] | No | - -#### `retrieve_module` / `update_module` / `delete_module` / `archive_module` - -Accept `project_id` and `module_id`. - -#### `add_work_items_to_module` - -| Parameter | Type | Required | -| --------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `module_id` | UUID string | **Yes** | -| `work_item_ids` | UUID[] | **Yes** | - -#### `remove_work_item_from_module` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `module_id` | UUID string | **Yes** | -| `work_item_id` | UUID string | **Yes** | - -#### `list_module_work_items` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `module_id` | UUID string | **Yes** | - ---- - -### Epics - -Large work items that group related items. The server resolves the Epic work item type automatically. - -#### `list_epics` / `create_epic` / `retrieve_epic` / `update_epic` / `delete_epic` - -| Parameter | Type | Required | -| ----------------------- | ----------- | ------------------- | -| `project_id` | UUID string | **Yes** | -| `epic_id` | UUID string | Varies by operation | -| name, description, etc. | - | Varies | - ---- - -### Milestones - -Point-in-time goals within a project. - -#### `list_milestones` / `create_milestone` / `retrieve_milestone` / `update_milestone` / `delete_milestone` - -| Parameter | Type | Required | -| -------------- | ----------- | ---------------- | -| `project_id` | UUID string | **Yes** | -| `milestone_id` | UUID string | Varies | -| `name` | string | **Yes** (create) | - -#### `add_work_items_to_milestone` - -| Parameter | Type | Required | -| --------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `milestone_id` | UUID string | **Yes** | -| `work_item_ids` | UUID[] | **Yes** | - -#### `remove_work_items_from_milestone` - -| Parameter | Type | Required | -| --------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `milestone_id` | UUID string | **Yes** | -| `work_item_ids` | UUID[] | **Yes** | - -#### `list_milestone_work_items` - -| Parameter | Type | Required | -| -------------- | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `milestone_id` | UUID string | **Yes** | - ---- - -### Initiatives - -Workspace-scoped strategic goals that span multiple projects. - -#### `list_initiatives` / `create_initiative` / `retrieve_initiative` / `update_initiative` / `delete_initiative` - -Initiatives are workspace-scoped - no `project_id` required. `retrieve_initiative`, `update_initiative`, and `delete_initiative` accept an `initiative_id` UUID. - ---- - -### Intake - -Triage queue for incoming work items before they enter a project. - -#### `list_intake_work_items` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | - -#### `create_intake_work_item` - -| Parameter | Type | Required | -| ------------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `name` | string | **Yes** | -| `description_html` | string | No | - -#### `retrieve_intake_work_item` / `update_intake_work_item` / `delete_intake_work_item` - -Accept `project_id` and `work_item_id`. - ---- - -### Pages - -Wiki-style documents. Pages can be workspace-scoped or project-scoped. - -#### `retrieve_workspace_page` - -| Parameter | Type | Required | -| --------- | ----------- | -------- | -| `page_id` | UUID string | **Yes** | - -#### `retrieve_project_page` - -| Parameter | Type | Required | -| ------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `page_id` | UUID string | **Yes** | - -#### `create_workspace_page` - -| Parameter | Type | Required | -| ------------------ | ------ | -------- | -| `name` | string | **Yes** | -| `description_html` | string | No | - -#### `create_project_page` - -| Parameter | Type | Required | -| ------------------ | ----------- | -------- | -| `project_id` | UUID string | **Yes** | -| `name` | string | **Yes** | -| `description_html` | string | No | - ---- - -## Common workflows - -### Look up a work item by ID - -``` -What is work item ENG-42 about? -``` - -Model calls `retrieve_work_item_by_identifier` with `project_identifier="ENG"` and `work_item_identifier="42"`. - -### Create a work item - -``` -Create a high-priority bug in the ENG project called "Login times out on Safari". -Description: The OAuth callback redirects to a blank page on Safari 17+. -Assign it to me. -``` - -Model calls `list_projects` → `retrieve_work_item_by_identifier` (or `get_me` to resolve "me") → `create_work_item`. - -### Update work item state - -``` -Mark ENG-88 as done and add a comment: "Fixed in commit abc1234, needs QA." -``` +## Troubleshooting -Model resolves the UUID, calls `list_states` to find the Done state UUID, calls `update_work_item` and `create_work_item_comment`. +| Symptom | Likely cause | Fix | +| ----------------------------------- | ------------------------ | ------------------------------------------------------------------------------------ | +| `401 Unauthorized` | Wrong or revoked API key | Regenerate token in Plane settings | +| `401 Unauthorized` | Expired OAuth token | Re-authenticate via OAuth flow | +| `403 Forbidden` | Insufficient role | Check your role in the workspace or project | +| `404 Not Found` | Wrong workspace slug | Verify slug in your Plane URL | +| `404 Not Found` | UUID doesn't exist | Check if the resource was deleted | +| `400 Validation error` | Missing required field | Check required params and value types | +| Server not listed in Claude Desktop | Wrong transport type | Use `npx mcp-remote@latest` or SSE — Claude Desktop doesn't support `"type": "http"` | +| Server config skipped | JSON syntax error | Validate JSON — check for trailing commas | +| Connection timeout | Can't reach Plane API | Verify `PLANE_BASE_URL` and network | -### Sprint planning +### Test Plane API connectivity +```bash +curl -H "x-api-key: YOUR_API_KEY" "https://api.plane.so/api/v1/users/me/" ``` -Create a cycle called "Sprint 15" in ENG starting 2025-06-02, ending 2025-06-15. -Then move all incomplete issues from Sprint 14 into it. -``` - -Model calls `create_cycle` then `list_cycles` to find Sprint 14's UUID, then `transfer_cycle_work_items`. -### Log time +### Debug Stdio startup -``` -Log 90 minutes on ENG-42: "Implemented retry logic for the upload endpoint." +```bash +PLANE_API_KEY=your_key PLANE_WORKSPACE_SLUG=your-slug plane-mcp-server stdio ``` -### Search across the workspace +### Test local HTTP server +```bash +curl http://localhost:8211/http/mcp +# Expect: MCP protocol response or 401 ``` -Show me all high-priority bugs assigned to me that are still in progress. -``` - -Model calls `list_work_items` with filters `priorities=["high"]`, `state_groups=["started"]`, and the current user's UUID as `assignee_ids`. -### Manage a module +### Claude Code: enable debug logging +```bash +claude --mcp-debug ``` -Add ENG-55, ENG-56, and ENG-57 to the "Checkout Redesign" module. -``` - -Model calls `list_modules` to find the UUID, then `add_work_items_to_module` with the resolved work item UUIDs. ---- - -## Troubleshooting - -The server propagates errors from the Plane SDK as MCP tool errors. - -| Scenario | HTTP Status | Cause | Resolution | -| --------------------------------- | ----------- | --------------------------------------- | --------------------------------------------------- | -| Invalid API key | 401 | `PLANE_API_KEY` is wrong or revoked | Regenerate the token in Plane settings | -| Invalid OAuth token | 401 | Token expired or revoked | Re-authorise through OAuth flow | -| Missing `x-workspace-slug` header | - | Header auth missing workspace | Include `x-workspace-slug` header | -| Wrong workspace slug | 404 | Slug doesn't exist | Check the exact slug in your Plane URL | -| Insufficient permissions | 403 | User role too low | Check your role in the workspace/project | -| Resource not found | 404 | UUID or identifier doesn't exist | Verify the ID; check if resource was deleted | -| Validation error | 400 | Required field missing or invalid value | Check required fields and value constraints | -| Redis unavailable | - | Token storage down | Set `REDIS_HOST`/`REDIS_PORT` or omit for in-memory | -| Network error | - | Cannot reach Plane API | Verify `PLANE_BASE_URL` and connectivity | - -**Verify connectivity (stdio/PAT):** +### Claude Code: re-authenticate OAuth ```bash -curl -H "x-api-key: YOUR_KEY" \ - "https://api.plane.so/api/v1/users/me/" +rm -rf ~/.mcp-auth ``` -**Run stdio mode manually to debug startup:** +Restart Claude Code and run `/mcp` to authenticate again. -```bash -PLANE_API_KEY=your_key PLANE_WORKSPACE_SLUG=your-slug plane-mcp-server stdio -``` +--- -**Test the HTTP server is running:** +**Related:** -```bash -curl http://localhost:8211/http/mcp -# Should return MCP protocol response or 401 -``` +- [MCP Server Tool Reference](/dev-tools/mcp-server-tools) — all 136 tools, parameters, example workflows +- [Self-host MCP Server](/dev-tools/mcp-server-self-host) — Docker, Helm, OAuth app setup --- -_Plane MCP Server is open source and licensed under MIT. Source at [github.com/makeplane/plane-mcp-server](https://github.com/makeplane/plane-mcp-server)._ +_Open source, MIT licensed. Source at [github.com/makeplane/plane-mcp-server](https://github.com/makeplane/plane-mcp-server)._