You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Feature: let the MCP server act as its own authorization server (PAT as the login step), so CE works with header-less connector clients
Summary
Connector-style MCP clients — Claude's "Add custom connector", ChatGPT connectors — accept only a URL plus optional OAuth client ID/secret. There is no field for custom headers. That makes the PAT transport at /http/api-key/mcp structurally unusable for this entire class of client: it needs Authorization: Bearer <pat> and X-Workspace-Slug, and the client has no way to send either.
On Plane CE the OAuth transport at /http/mcp doesn't work either, because it proxies to /auth/o/* endpoints CE doesn't have. So for a self-hosted CE instance the remote-transport matrix is empty:
Transport
Mount
CE status
OAuth HTTP
/http/mcp
upstream /auth/o/* → 404
OAuth SSE
/sse
same
PAT header
/http/api-key/mcp
works, but unreachable from connector clients (no header field)
This is the gap I'd like to propose closing inside this repo, without waiting on makeplane/plane#8782.
Evidence
Against a self-hosted CE instance (plane-aio-community:v1.4.1, host genericised):
$ for p in /auth/o/authorize-app/ /auth/o/token/ /auth/o/app-installation/ /api/v1/users/me/; do
printf "%-30s " "$p"; curl -s -o /dev/null -w "%{http_code}\n" "https://plane.example.com$p"
done
/auth/o/authorize-app/ 404
/auth/o/token/ 404
/auth/o/app-installation/ 404
/api/v1/users/me/ 401
$ curl -s -X POST "https://plane.example.com/auth/o/token/" -d 'grant_type=authorization_code&code=x'
{"error": "Page not found."}
The 401 on /api/v1/users/me/ and the Django-shaped JSON 404 confirm the requests reach the API — the routes genuinely don't exist, it isn't a proxy misconfiguration. The CE proxy config forwards /auth/* to the API service, so there is nothing to fix on the deployment side.
For the record, this is unchanged in 0.3.0. Diffing 0.3.0 against 0.2.10: __main__.py, client.py, auth/plane_oauth_provider.py, auth/plane_header_auth_provider.py and storage.py are byte-identical; server.py only factors the shared middleware into _configured(). The 139 → 28 tool consolidation is unrelated to auth.
Proposal
Add a transport where the MCP server is the authorization server, and the login step is the user pasting their own Plane PAT, which the server validates against the instance and exchanges for a per-user, revocable token.
The pieces are already here:
Protocol plumbing — fastmcp 3.2.0 ships fastmcp/server/auth/providers/in_memory.py::InMemoryOAuthProvider with DCR, PKCE, auth codes, refresh and revocation. It is explicitly a test double (in-memory, authorize() auto-approves with no user interaction), so this isn't "just use it" — but the OAuth 2.1 surface doesn't need to be written from scratch.
PAT validation — PlaneHeaderAuthProvider._validate_api_key() already does exactly the needed check: GET /api/v1/users/me/ with x-api-key.
Durable storage — storage.py::build_token_store() already provides the Redis-backed store used by the OAuth proxy, so issued tokens have somewhere to live.
Redirect allowlist — DEFAULT_ALLOWED_REDIRECT_URIS in server.py already lists https://claude.ai/* and the ChatGPT connector callbacks.
Missing: a consent/login screen that takes a PAT (plus workspace slug, which today comes from X-Workspace-Slug or installations[0] and has no other source — see client.py::get_plane_client_context), and a provider that persists the PAT↔token binding.
Sketch of the flow:
Client hits /http/self/mcp → 401 with WWW-Authenticate, discovery proceeds against this server's own metadata.
Client registers via DCR (or the operator pastes a client ID/secret).
/authorize renders a form: Plane PAT + workspace slug.
Server validates the PAT via /api/v1/users/me/, stores the binding, issues code → token.
Tool calls resolve the caller's own PAT; get_plane_client_context() already supports auth_method="api_key_header", so downstream code is unchanged.
Why this and not the alternatives
Waiting for [feature]: Add OAuth app registration to CE for MCP server support plane#8782 (OAuth apps in CE) — that's the right long-term fix, but it's Plane-side, still unshipped, and even once it lands it won't help anyone on an older CE. This proposal is independent of the Plane version and works against every CE that has /api/v1/users/me/.
Reverse proxy injecting the headers, with a secret in the URL path — works today (I have it working), but the URL becomes a permanent shared bearer credential: not revocable per user, and every write in Plane is attributed to whichever account owns the injected PAT.
An external IdP via the bundled fastmcp providers (GitHub/Google/WorkOS) — fixes who can reach the endpoint, but the Plane-side identity is still a single shared PAT, so attribution inside Plane is unchanged.
Only "server as AS with the user's own PAT" gives per-user identity and revocation on CE.
Happy to send a PR if the direction is acceptable — the main thing I'd want steering on is whether a PAT-login AS is welcome in this repo at all, or whether CE auth is considered strictly a Plane-side concern.
Feature: let the MCP server act as its own authorization server (PAT as the login step), so CE works with header-less connector clients
Summary
Connector-style MCP clients — Claude's "Add custom connector", ChatGPT connectors — accept only a URL plus optional OAuth client ID/secret. There is no field for custom headers. That makes the PAT transport at
/http/api-key/mcpstructurally unusable for this entire class of client: it needsAuthorization: Bearer <pat>andX-Workspace-Slug, and the client has no way to send either.On Plane CE the OAuth transport at
/http/mcpdoesn't work either, because it proxies to/auth/o/*endpoints CE doesn't have. So for a self-hosted CE instance the remote-transport matrix is empty:/http/mcp/auth/o/*→ 404/sse/http/api-key/mcpThis is the gap I'd like to propose closing inside this repo, without waiting on makeplane/plane#8782.
Evidence
Against a self-hosted CE instance (
plane-aio-community:v1.4.1, host genericised):The 401 on
/api/v1/users/me/and the Django-shaped JSON 404 confirm the requests reach the API — the routes genuinely don't exist, it isn't a proxy misconfiguration. The CE proxy config forwards/auth/*to the API service, so there is nothing to fix on the deployment side.For the record, this is unchanged in 0.3.0. Diffing 0.3.0 against 0.2.10:
__main__.py,client.py,auth/plane_oauth_provider.py,auth/plane_header_auth_provider.pyandstorage.pyare byte-identical;server.pyonly factors the shared middleware into_configured(). The 139 → 28 tool consolidation is unrelated to auth.Proposal
Add a transport where the MCP server is the authorization server, and the login step is the user pasting their own Plane PAT, which the server validates against the instance and exchanges for a per-user, revocable token.
The pieces are already here:
fastmcp3.2.0 shipsfastmcp/server/auth/providers/in_memory.py::InMemoryOAuthProviderwith DCR, PKCE, auth codes, refresh and revocation. It is explicitly a test double (in-memory,authorize()auto-approves with no user interaction), so this isn't "just use it" — but the OAuth 2.1 surface doesn't need to be written from scratch.PlaneHeaderAuthProvider._validate_api_key()already does exactly the needed check:GET /api/v1/users/me/withx-api-key.storage.py::build_token_store()already provides the Redis-backed store used by the OAuth proxy, so issued tokens have somewhere to live.DEFAULT_ALLOWED_REDIRECT_URISinserver.pyalready listshttps://claude.ai/*and the ChatGPT connector callbacks.Missing: a consent/login screen that takes a PAT (plus workspace slug, which today comes from
X-Workspace-Slugorinstallations[0]and has no other source — seeclient.py::get_plane_client_context), and a provider that persists the PAT↔token binding.Sketch of the flow:
/http/self/mcp→ 401 withWWW-Authenticate, discovery proceeds against this server's own metadata./authorizerenders a form: Plane PAT + workspace slug./api/v1/users/me/, stores the binding, issues code → token.get_plane_client_context()already supportsauth_method="api_key_header", so downstream code is unchanged.Why this and not the alternatives
/api/v1/users/me/.fastmcpproviders (GitHub/Google/WorkOS) — fixes who can reach the endpoint, but the Plane-side identity is still a single shared PAT, so attribution inside Plane is unchanged.Only "server as AS with the user's own PAT" gives per-user identity and revocation on CE.
Related
PLANE_OAUTH_PROVIDER_*stubs even for PAT-only deployments (the same mount is involved; a self-contained mode would sidestep it)Environment
plane-mcp-server0.3.0 (and 0.2.10 — identical in the relevant modules)makeplane/plane-aio-community:v1.4.1Happy to send a PR if the direction is acceptable — the main thing I'd want steering on is whether a PAT-login AS is welcome in this repo at all, or whether CE auth is considered strictly a Plane-side concern.