Skip to content

feat(mcp): add support for mcp oauth#2911

Open
taciturnaxolotl wants to merge 4 commits into
mainfrom
mcp-oauth
Open

feat(mcp): add support for mcp oauth#2911
taciturnaxolotl wants to merge 4 commits into
mainfrom
mcp-oauth

Conversation

@taciturnaxolotl
Copy link
Copy Markdown
Member

adds support for the 2025-11-25 mcp oauth spec

@taciturnaxolotl
Copy link
Copy Markdown
Member Author

Client ID Metadata Documents (CIMD)

The MCP 2025-11-25 spec recommends Client ID Metadata Documents as the preferred client registration method over Dynamic Client Registration (DCR). We currently only use DCR.

What is it?

Instead of each client dynamically registering with every server (DCR), the client publishes a JSON metadata document at a stable HTTPS URL (e.g. https://crush.charm.land/.well-known/oauth-client.json):

{
  "client_id": "https://crush.charm.land/.well-known/oauth-client.json",
  "client_name": "Crush",
  "redirect_uris": ["http://127.0.0.1/callback"],
  "grant_types": ["authorization_code"],
  "token_endpoint_auth_method": "none"
}

The auth server fetches this URL to learn about the client — no registration endpoint needed. The client uses the URL itself as its client_id in authorization requests.

Why it matters

  • Preferred by spec: MCP 2025-11-25 says clients SHOULD support CIMD; DCR is downgraded to MAY (backwards compat)
  • Better UX: Auth servers can show "Crush by Charm" with logo in consent screens using metadata from the document
  • Simpler for servers: No need to implement a registration endpoint — just fetch the URL
  • More trustworthy: HTTPS URL proves client identity vs DCR where any client can register

What we'd need

  • Host the metadata JSON at a public Charm URL
  • Set ClientIDMetadataDocumentConfig.URL in the OAuth handler config
  • The go-sdk already tries CIMD first (before DCR), so it would be used automatically when the auth server supports it

Current state

DCR works for Linear and most servers today. The SDK handles the fallback order (CIMD → preregistered → DCR), so adding CIMD later is backwards-compatible.

References

@smlx
Copy link
Copy Markdown

smlx commented Jun 5, 2026

Hey, I only just noticed this PR today. I've been maintaining a personal branch implementing MCP OAuth2 in Crush for a while.

One reason I didn't send a PR yet is that the go-sdk has some deficiencies around token handling. I have an API proposal that would allow using the go-sdk's AuthorizationCodeHandler to fully manage the lifecycle of the oauth2 session. I've also implemented the proposal in a branch here.

From what I can tell, this branch wraps the go-sdk AuthorizationCodeHandler and implements a lot of the callback logic in crush? I think with the changes I proposed in the go-sdk that can be avoided and you can use AuthorizationCodeHandler directly. Maybe you want to take a look at that proposal?

Other differences I can see from a brief revew of this code:

  • This branch adds a new UI, while mine re-uses dialog.OAuth.
  • This branch stores tokens in the global config, while mine puts them in XDG_STATE_HOME. (I keep my config in a dotfiles repo and don't want token churn)
  • I don't believe this branch stores new tokens on token refresh? One of the motivations for my go-sdk proposal was to allow hooking the token refresh cycle so that new OAuth2 tokens and refresh tokens can be automatically persisted so that the user isn't constantly prompted to re-auth via a browser flow.

Anyway, if you are interested in taking any ideas (or not!) from my branch feel free. Also if you agree that the go-sdk proposal has merit or could be improved please add a comment on there.

Thanks!

@taciturnaxolotl
Copy link
Copy Markdown
Member Author

oh that looks wonderful! thank you for your work! I had made this as an initial proof of concept but your implementation is far more polished and the change in the sdk I think is a good move

@taciturnaxolotl taciturnaxolotl force-pushed the mcp-oauth branch 2 times, most recently from 4609bc2 to ab412ab Compare June 5, 2026 03:33
@taciturnaxolotl
Copy link
Copy Markdown
Member Author

i incorporated a bunch of things from your branch in! I still think it makes more sense to have a separate oauth dialog for mcps since its a different code flow than the copy code and poll style. I also normally manage my crush config with nix so its immutable but I haven't had any issues here. When we use the config write function it writes into the ~/.local/share/crush/crush.json file which is also used for state when you add a new token for a provider via the ui. It would be awesome to get your upstream sdk change pulled in for this as well that makes for a much more seamless refresh.

@taciturnaxolotl taciturnaxolotl force-pushed the mcp-oauth branch 2 times, most recently from 3629dce to 50f4c82 Compare June 7, 2026 06:55
Co-Authored-By: Scott Leggett <scott@sl.id.au>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants