makeplane / makeplane/plane-mcp-server
Hosted mcp.plane.so: /http/register accepts any redirect URI, /http/authorize then rejects it — no way to onboard a self-hosted web client
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 325
- Forks
- 178
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 2
Description
Summary
On the hosted server, dynamic client registration and authorization disagree. POST /http/register returns 201 and echoes back whatever redirect_uris it was given; GET /http/authorize then rejects the very same URI against the built-in allowlist:
{"error":"invalid_request",
"error_description":"Redirect URI 'https://chat.example.com/oauth/clients/mcp:plane/callback' does not match allowed patterns."}
For a browser-based MCP client that runs on its own domain — a self-hosted Open WebUI 0.11.3 in this case, but the shape is general — the OAuth transports are therefore unusable, and the failure surfaces only at the last step, after the admin has configured everything and a user clicks connect.
Self-hosters got an escape hatch in #166 (PLANE_OAUTH_ALLOWED_REDIRECT_URIS). Users of the hosted server have no equivalent: the allowlist is baked into the deployment they do not run.
Evidence
Tested against https://mcp.plane.so on 2026-09-17; hosts genericised. Each row is a fresh DCR client whose single registered redirect URI is the one under test.
| Redirect URI | /http/register |
/http/authorize |
|---|---|---|
http://localhost:3000/callback |
201 | 302 |
http://127.0.0.1:8080/oauth/callback |
201 | 302 |
https://claude.ai/api/mcp/auth_callback |
201 | 302 |
https://chatgpt.com/connector_platform_oauth_redirect |
201 | 302 |
https://chat.example.com/oauth/clients/mcp:plane/callback |
201 | 400 invalid_request |
https://chat.example.com/oauth/callback |
201 | 400 invalid_request |
https://app.plane.so/callback |
201 | 400 invalid_request |
https://localhost:8443/cb (HTTPS loopback) |
201 | 400 invalid_request |
Reproduction:
REDIRECT="https://chat.example.com/oauth/clients/mcp:plane/callback"
CLIENT_ID=$(curl -s -X POST https://mcp.plane.so/http/register \
-H 'Content-Type: application/json' \
-d "{\"client_name\":\"probe\",\"redirect_uris\":[\"$REDIRECT\"],
\"grant_types\":[\"authorization_code\",\"refresh_token\"],
\"response_types\":[\"code\"],\"scope\":\"read write\"}" \
| python -c 'import sys,json;print(json.load(sys.stdin)["client_id"])') # 201
curl -s -i "https://mcp.plane.so/http/authorize?response_type=code&client_id=$CLIENT_ID\
&redirect_uri=$REDIRECT&scope=read+write&state=probe\
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256"
# 400 invalid_request — "does not match allowed patterns"
The resource parameter makes no difference, and neither does dropping the colon from the client id segment of the path.
Why the registration step matters
RFC 7591 §3.2.2 defines invalid_redirect_uri for exactly this case: a registration request whose redirect URI the server will not accept. Returning 201 instead moves a deterministic, statically-known rejection to the end of an interactive flow, and it does so after the metadata has told the client that DCR is the supported path (registration_endpoint is advertised at /.well-known/oauth-authorization-server/http).
Client implementations read that combination as a working setup. Open WebUI, for instance, validates the connection by fetching the discovery document — which succeeds — stores the registered client, and only discovers the problem when a user tries to authorize. Its own recovery path (re-register, retry) cannot help, because re-registration succeeds again.
Asks
Either one would resolve this; the first is the conformance fix, the second is the capability gap.
- Reject at registration. Validate
redirect_urisagainst the allowlist in/http/registerand return400 invalid_redirect_uriwhen they do not match. The admin then sees the real constraint at configuration time, in the field that caused it. - Let hosted workspaces extend the allowlist. Per-workspace redirect patterns in workspace settings, mirroring what
PLANE_OAUTH_ALLOWED_REDIRECT_URISgives self-hosters. Today the hosted allowlist effectively enumerates a handful of vendor clients plus loopback, which closes the OAuth transports to every self-hosted web client — the category most likely to want per-user Plane identities rather than one shared PAT.
Workarounds, for the record
/http/api-key/mcpworks and is unaffected, but a shared PAT collapses every user of the client onto one Plane identity.- Self-hosting this server against Plane Cloud with
PLANE_OAUTH_ALLOWED_REDIRECT_URISand a workspace OAuth app restores per-user OAuth, at the cost of running the component that the hosted service already runs.
Environment: hosted mcp.plane.so; PAT mount reports Plane MCP Server (header-http) 3.2.0; MCP protocol 2025-06-18.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the /http/register and /http/authorize handlers and the allowlist used by the hosted server. Reproduce the mismatch with the supplied curl example, then make registration reject disallowed redirect_uris with 400 invalid_redirect_uri, and verify that accepted URIs still authorize successfully and rejected ones fail at registration.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100