Repository navigation
fix: answer session-less GET with open SSE stream so public clients don't attempt OAuth - #30
Merged
Merged
Conversation
…s don't attempt OAuth The claude.ai custom-connector flow fails to add https://mcp.blues.io/expert/mcp with "Couldn't register with Blues Expert's sign-in service" — an OAuth dynamic client registration error — even though the server is public and requires no auth. Root cause (established by diffing our response surface against DeepWiki, a public MCP server that the connector accepts): - initialize (POST) -> 200, no WWW-Authenticate [same as DeepWiki] - /.well-known/oauth-* -> 404 [same as DeepWiki] - session-less GET /mcp -> 405 (ours) vs 200 SSE (DeepWiki) <-- only difference The go-sdk StreamableHTTPHandler returns 405 for a session-less GET and has no option to change this (Stateless makes it 405 unconditionally). Some clients read that 405 as "auth required" and start an OAuth flow that can't complete against a public server. This adds a narrow shim: a session-less GET to the MCP endpoint returns an open (keep-alive) SSE stream (200), mirroring public reference servers. POST, DELETE, and session-scoped GET — the paths real clients and Claude Code use — are delegated to the SDK unchanged, as are the /expert/*/.well-known/* discovery paths (verified locally). Verified locally: session-less GET now 200 text/event-stream; initialize + tools/list unchanged; session-ed GET unchanged; .well-known subpath still 405; health 200. gofmt/go vet clean. Note: the claude.ai connector itself can only be re-tested after this ships via a release deploy and the connector is re-added, so connector success is not yet confirmed end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mdMQc7sPbtAAGhiLnR8t6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Adding
https://mcp.blues.io/expert/mcpas a claude.ai custom connector fails with:That's an OAuth dynamic client registration (DCR) failure — but the server is public and requires no authentication (it answers unauthenticated
initialize/tools/listfine, and adding it viaclaude mcp add --transport http …in Claude Code connects with no OAuth at all).Root cause
Diffing our response surface against DeepWiki (
mcp.deepwiki.com/mcp), a public MCP server the connector does accept:POST initializeWWW-AuthenticateWWW-Authenticate✅ same/.well-known/oauth-*GET /mcpThe go-sdk
StreamableHTTPHandlerreturns405 "GET requires an active session"for a session-less GET (streamable.go:258:Stateless || sessionID == ""), and there's no option to change it (Stateless: truemakes it 405 unconditionally). Some clients — including the claude.ai connector flow — treat that 405 as "authentication required" and kick off an OAuth/DCR attempt that can't complete against a public server.Change
A narrowly-scoped shim in
main.go: a session-less GET to the MCP endpoint returns an open (keep-alive-only) SSE stream (200 text/event-stream), mirroring how public reference servers behave. Everything the real transport uses is delegated to the SDK unchanged:POST(all MCP requests),DELETE, and session-scoped GET → SDK (this is the Claude Code path)/expert/*/.well-known/*discovery paths → SDK (still 404/405; not touched)Verification (local)
GET /expert/mcptext/event-stream(was 405)POST initializeGETtools/list.well-knownsubpath/expert/healthgofmtclean,go vetclean,go build ./...passes.I can't drive the claude.ai connector UI, and nothing reaches
mcp.blues.iountil a release is cut (deploys are gated to published releases). So this is a best-effort fix targeting the one confirmed response-surface difference from a working public server. Final confirmation requires: merge → publish a release → re-add the connector in claude.ai. If the connector still attempts OAuth after that, the trigger is connector-side (not the GET) and no server change will help — in which case Claude Code (claude mcp add --transport http) remains the working path.🤖 Generated with Claude Code