-
Notifications
You must be signed in to change notification settings - Fork 268
change_title tool unavailable — MCP server returns 500 after first request (SDK 1.26.0 stateless transport restriction) #165
Description
Description
After a recent update (around Feb 6-8), the mcp__happy__change_title tool stopped working entirely. All session titles are stuck at the working directory name (e.g., "Raymond-Agent") and never update — despite the system prompt injection being successful.
This was working perfectly ~5 days ago. Every new conversation would automatically get a descriptive title.
Environment
- Happy CLI: 0.13.0
- Claude Code: 2.1.38 (also tested with 2.1.23)
- Node.js: v25.2.1 (Mac mini) / v22.14.0 (MacBook)
- macOS: Darwin 25.1.0 (Apple Silicon)
- MCP SDK (bundled): 1.26.0
Root Cause Analysis
After extensive debugging, I traced this to the same root cause as #162.
What happens:
- Happy CLI starts the MCP HTTP server with
sessionIdGenerator: void 0(stateless mode) - Claude Code starts and connects to the MCP server
- First HTTP request (initialize) → 200 OK ✅
- Second HTTP request (initialized/tools/list) → 500 ❌
- Claude Code marks the
happyMCP server as"status": "failed" mcp__happy__change_titletool is never loaded- System prompt tells AI to call
change_title, but the tool doesn't exist
Why it fails:
MCP SDK 1.26.0 (patching CVE-2026-25536) added this guard in WebStandardStreamableHTTPServerTransport.handleRequest():
if (!this.sessionIdGenerator && this._hasHandledRequest) {
throw new Error('Stateless transport cannot be reused across requests. Create a new transport per request.');
}
this._hasHandledRequest = true;Happy CLI reuses a single StreamableHTTPServerTransport instance for all incoming requests, which now violates the SDK's new constraint.
Proof:
// Standalone test with Happy's bundled MCP SDK:
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await mcp.connect(transport);
// Request 1 → 200 ✅
// Request 2 → 500 ❌ ("Stateless transport cannot be reused across requests")Why the error is invisible:
The error is thrown inside WebStandardStreamableHTTPServerTransport.handleRequest(), which is wrapped by @hono/node-server's getRequestListener. Hono catches the error and returns a bare 500 response before Happy's own catch block can log it. That's why Happy's logs show no MCP-related errors.
Impact
- Session titles never update (stuck at directory name)
save_memorytool (if any) is also unavailable- Core features (remote control, real-time sync) work fine — only MCP-dependent features are broken
Suggested Fix
In startHappyServer.ts, either:
-
Per-request transport (recommended by SDK docs):
const server = createServer(async (req, res) => { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await mcp.connect(transport); await transport.handleRequest(req, res); });
-
Stateful mode:
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
The comment in the current code mentions that stateful mode causes "Invalid Request: Server already initialized" — the per-request approach may be more compatible.
Related
- Happy MCP server incompatible with @modelcontextprotocol/sdk 1.26.0 - returns HTTP 500 on OAuth discovery #162 — Same root cause, different symptom (OAuth discovery 500)
- MCP Server returns HTTP 500 in WSL 2 Mirror networking mode #163 — Same root cause in WSL environment
- CVE-2026-25536 — The security fix that triggered this breaking change