-
Notifications
You must be signed in to change notification settings - Fork 205
Description
Summary
When StreamableHTTPClientTransport fails for any reason, mcporter falls back to SSEClientTransport using the same /mcp URL. For servers that implement Streamable HTTP with stateful_mode: true, this bare GET request (without a prior POST initialize and Mcp-Session-Id header) returns 400, and mcporter reports a misleading error:
[mcporter] Failed to authorize 'myserver': SSE error: Non-200 status code (400)
This happens even though the OAuth flow completed successfully and the token was saved.
Reproduction
- Target a Streamable HTTP MCP server (e.g., one using rmcp with
stateful_mode: true) - Run
mcporter auth --http-url http://127.0.0.1:35729/mcp --allow-http - Complete OAuth flow successfully
- mcporter reports failure with exit code 1, despite token being saved
Root Cause
In src/runtime/transport.ts lines 166-178:
if (primaryError instanceof Error) {
logger.info(`Falling back to SSE transport for '${activeDefinition.name}': ${primaryError.message}`);
}
const sseTransport = new SSEClientTransport(command.url, {
...baseOptions,
});Two problems:
- Unconditional fallback: Any error from
StreamableHTTPClientTransporttriggers the SSE fallback, including errors that SSE cannot fix (auth errors, server-side rejections after successful auth) - Same URL reuse: Legacy SSE transport typically uses
/sse, not/mcp. Sending a bare GET to a Streamable HTTP endpoint violates the MCP 2025-03-26 spec which requiresMcp-Session-Idon GET requests
Suggested Fix
Only fall back to SSE for errors that suggest the server doesn't support Streamable HTTP at all (404, 405). Don't fall back for auth errors or post-auth failures:
catch (primaryError) {
if (isUnauthorizedError(primaryError)) {
// ... existing OAuth promotion logic
}
if (primaryError instanceof OAuthTimeoutError) {
throw primaryError;
}
// Only fall back to SSE for transport-level incompatibility
const status = extractStatusCode(primaryError);
if (status === 400 || status === 401 || status === 403) {
throw primaryError; // Server understood the request but rejected it — SSE won't help
}
logger.info(`Falling back to SSE transport...`);
// ... SSE fallback
}Additionally, handleAuth could treat a successfully saved token as success (exit 0) without requiring a full listTools verification, since the verification step is what triggers this fallback chain.
Impact
Affects all users connecting to Streamable HTTP servers with stateful_mode: true. The token is saved successfully but mcporter exits with code 1, making CI/automation scripts think auth failed.