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_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.
- 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.
Summary
On the hosted server, dynamic client registration and authorization disagree.
POST /http/registerreturns201and echoes back whateverredirect_urisit was given;GET /http/authorizethen rejects the very same URI against the built-in allowlist:For a browser-based MCP client that runs on its own domain — a self-hosted Open WebUI
0.11.3in 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.soon 2026-09-17; hosts genericised. Each row is a fresh DCR client whose single registered redirect URI is the one under test./http/register/http/authorizehttp://localhost:3000/callbackhttp://127.0.0.1:8080/oauth/callbackhttps://claude.ai/api/mcp/auth_callbackhttps://chatgpt.com/connector_platform_oauth_redirecthttps://chat.example.com/oauth/clients/mcp:plane/callbackinvalid_requesthttps://chat.example.com/oauth/callbackinvalid_requesthttps://app.plane.so/callbackinvalid_requesthttps://localhost:8443/cb(HTTPS loopback)invalid_requestReproduction:
The
resourceparameter 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_urifor exactly this case: a registration request whose redirect URI the server will not accept. Returning201instead 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_endpointis 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.
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.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.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 reportsPlane MCP Server (header-http) 3.2.0; MCP protocol2025-06-18.