Synapse v2.1.6 — "custom connector"¶
Feature release adding OAuth 2.1 + PKCE support for the remote MCP server. No breaking changes.
Added¶
- MCP OAuth 2.1 + PKCE authorization server, for claude.ai's web "Custom connector"
(ADR-0090): live-observed gap — adding Synapse's remote MCP server as a claude.ai
"Custom connector" silently failed, because that UI only speaks OAuth 2.1
authorization_code + PKCE (unlike Claude Desktop's JSON config, which already supports
a static bearer header). Clicking "Connect" redirected the browser to
<origin>/authorize?response_type=code&client_id=...and landed on the ordinary Synapse frontend, because neither/authorizenor any discovery/registration endpoint existed and the reverse proxy had no route for them.
Added a minimal, single-operator-oriented authorization server:
- GET/POST /authorize — renders a consent form; approved with the SAME static MCP
token that already gates /mcp/server directly (DB-set via PUT /mcp/auth, or the
MCP_AUTH_TOKEN env bootstrap).
- POST /token — authorization_code (with mandatory PKCE, S256 only) and
refresh_token grants; refresh is rotate-on-use.
- POST /register — RFC 7591 Dynamic Client Registration. Public clients only (PKCE is
the confidentiality mechanism) — no client_secret is ever issued or stored.
- GET /.well-known/oauth-authorization-server and
/.well-known/oauth-protected-resource — RFC 8414 / 9728 discovery.
JIT (just-in-time) client registration handles MCP clients (observed live with
claude.ai) that self-assign a client_id and skip /register entirely — a client_id
is bound to a redirect_uri on first approval and can never be silently rebound
(open-redirect guard).
An OAuth-issued access token is accepted by the existing MCP bearer gate
(BearerAuthMiddleware) exactly like the static token — there is no separate scope
model, and no delegation chain (an OAuth-issued token can never be used to approve
another OAuth grant; only the operator's real static token can). The whole surface
shares the same remote_mcp_enabled floor as /mcp/server itself.
New tables mcp_oauth_clients / mcp_oauth_tokens (migration 0038), PBKDF2-hashed
token storage (same helpers as api_tokens.secret_hash). See
ADR-0090 for the full design.
docs/DEPLOY.md§5.6/§5.6b anddocs/USER.mdupdated with setup steps for both connector paths (Claude Desktop bearer token vs. claude.ai web OAuth), including the Cloudflare Access bypass scope needed for the new root-level endpoints.
Upgrade notes¶
One additive migration (0038 — mcp_oauth_clients / mcp_oauth_tokens). No existing
behavior changes: the static MCP_AUTH_TOKEN / DB-token bearer gate on /mcp/server is
completely unaffected — OAuth is an additional way to obtain a valid bearer credential,
not a replacement.
If you run Synapse behind your own reverse proxy (not the bundled nginx image), add
routes for /authorize, /token, /register, /.well-known/oauth-authorization-server,
and /.well-known/oauth-protected-resource pointing at the backend — otherwise these
fall through to your frontend's SPA fallback, exactly like the bug this release fixes.