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

Open
#220 0 comments 0 reactions 0 assignees View on GitHub

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.

  1. Reject at registration. Validate redirect_uris against the allowlist in /http/register and return 400 invalid_redirect_uri when they do not match. The admin then sees the real constraint at configuration time, in the field that caused it.
  2. Let hosted workspaces extend the allowlist. Per-workspace redirect patterns in workspace settings, mirroring what PLANE_OAUTH_ALLOWED_REDIRECT_URIS gives 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/mcp works 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_URIS and 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.