Skip to content

When using Streamable HTTP transport, the oauth flow is not triggered when the server returns 401 #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ashubham opened this issue Apr 29, 2025 · 1 comment
Labels
authorization Issues related to authentication bug Something isn't working

Comments

@ashubham
Copy link

ashubham commented Apr 29, 2025

Describe the bug
When using Streamable HTTP transport, the oauth flow is not triggered when the server returns 401. The sse transport supports this and the browser correctly initiates the oauth flow as per the spec.

To Reproduce
Steps to reproduce the behavior:

  1. Point against an MCP server with Streamable HTTP + Oauth flow. Like this one (https://thoughtspot-mcp-server.thoughtspot-485.workers.dev/mcp)

Expected behavior
When the server returns 401 the inspector should trigger the oauth flow.

Logs
When using streamable HTTP:

Created streamable web app transport 9b453c43-3a2c-4e67-a18f-29824475847e
Error from MCP server: Error: Error POSTing to endpoint (HTTP 401)
-- the inspector hangs here --

When using sse:

SSE transport: url=<server sse endpoint>, headers=Accept
Received 401 Unauthorized from MCP server: SSE error: Non-200 status code (401)
-- the oauth flow is triggered ---

Additional context
There is discussion on this in #339, and is identified as a follow up task.

@ashubham ashubham added the bug Something isn't working label Apr 29, 2025
@olaservo olaservo added the authorization Issues related to authentication label Apr 30, 2025
@oidebrett
Copy link

I am also experiencing this issue (i.e no authorization flow for streamable http). I did a little digging into the code. Hopefully this will help find the root cause and will help the authors of the StreamableHTTPClientTransport to make a fix.

Looking at the sse case in the SSEClientTransport class the start function calls _startOrAuth function which checks for 401 and throws an exception that gets picked up in index.ts

    async start() {
        if (this._eventSource) {
            throw new Error("SSEClientTransport already started! If using Client class, note that connect() calls start() automatically.");
        }
        return await this._startOrAuth();
    }

exception get caught here in index.ts

index.ts code

app.get("/sse", async (req, res) => {
  try {
    console.log(
      "New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http",
    );

    try {
      await backingServerTransport?.close();
      backingServerTransport = await createTransport(req);
    } catch (error) {
      if (error instanceof SseError && error.code === 401) {
        console.error(
          "Received 401 Unauthorized from MCP server:",
          error.message,
        );
        res.status(401).json(error);
        return;
      }

      throw error;
    }

However, the StreamableHTTPClientTransport also has a start function but that doesnt call the startOrAuth so the exception to send back 401 to the client is never thrown. There is a funtion called _startOrAuthSse that appears to be used elsewhere in the StreamableHTTPClientTransport but possibly the authors have overlooked the initial 401 exception.

    async start() {
        if (this._abortController) {
            throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
        }
        this._abortController = new AbortController();
    }

and therefore the exception never gets thrown so the 401 is never returned to the client

index.ts code

    try {
      console.log("New streamable-http connection");
      try {
        await backingServerTransport?.close();
        backingServerTransport = await createTransport(req);
      } catch (error) {
        if (error instanceof SseError && error.code === 401) {
          console.error(
            "Received 401 Unauthorized from MCP server:",
            error.message,
          );
          res.status(401).json(error);
          return;
        }

        throw error;
      }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
authorization Issues related to authentication bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants