Skip to content

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 /authorize nor 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 and docs/USER.md updated 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.