Skip to content

fix: answer session-less GET with open SSE stream so public clients don't attempt OAuth - #30

Merged
Bucknalla merged 1 commit into
mainfrom
alex-mcp-public-get-sse
Aug 6, 2026
Merged

Bucknalla merged 1 commit into
mainfrom
alex-mcp-public-get-sse

Conversation

@Bucknalla

Copy link
Copy Markdown
Collaborator

Problem

Adding https://mcp.blues.io/expert/mcp as a claude.ai custom connector fails with:

Couldn't register with Blues Expert's sign-in service … ofid_…

That's an OAuth dynamic client registration (DCR) failure — but the server is public and requires no authentication (it answers unauthenticated initialize/tools/list fine, and adding it via claude 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:

Probe DeepWiki (works) Ours (fails)
POST initialize 200, no WWW-Authenticate 200, no WWW-Authenticate ✅ same
/.well-known/oauth-* 404 404 ✅ same
session-less GET /mcp 200 SSE (held open) 405 ❌ only difference

The go-sdk StreamableHTTPHandler returns 405 "GET requires an active session" for a session-less GET (streamable.go:258: Stateless || sessionID == ""), and there's no option to change it (Stateless: true makes 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)

Probe Result
session-less GET /expert/mcp 200 text/event-stream (was 405)
POST initialize 200, session issued ✅
session-ed GET 200 stream ✅
tools/list all 5 tools ✅
session-less GET on .well-known subpath 405 (unchanged) ✅
/expert/health 200 ✅

gofmt clean, go vet clean, go build ./... passes.

⚠️ Not yet verified end-to-end

I can't drive the claude.ai connector UI, and nothing reaches mcp.blues.io until 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

…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
@Bucknalla
Bucknalla merged commit 5256128 into main Aug 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant