Skip to content

[BUG] OAuth access token refresh not triggered on 401 — expired tokens cause permanent tool failure #1593

@rjhuijsman

Description

@rjhuijsman

Issue

When an MCP server's OAuth access token expires during an active session...

  • tool calls from the Tools tab fail permanently with Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token"}.
  • tool calls from the App Builder tab fail permanently silently; no response is shown anywhere.

The Inspector does not use the refresh token to obtain a new access token, even though the SDK (MCPClientManager) seems to supports automatic 401 → refresh → retry.

I've asked our good friend Claude why, it says:

The root cause is in createServerConfig() in mcpjam-inspector/client/src/lib/oauth/mcp-oauth.ts. It bakes the access token into a static Authorization header and never passes refreshToken or clientId to the backend:

function createServerConfig(serverUrl: string, tokens: any): HttpServerConfig {
  // Note: We don't include authProvider in the config because it can't be serialized
  // when sent to the backend via JSON. The backend will use the Authorization header instead.
  // Token refresh should be handled separately if the token expires.
  return {
    url: serverUrl,
    requestInit: {
      headers: tokens.access_token
        ? { Authorization: `Bearer ${tokens.access_token}` }
        : {},
    },
  };
}

Because refreshToken and clientId are absent from the config, MCPClientManager.connectViaHttp() never instantiates a RefreshTokenOAuthProvider and the StreamableHTTPClientTransport has no authProvider — so when it receives a 401 there is no refresh path.

How to reproduce

  1. Configure an MCP server with OAuth 2.0 that issues short-lived access tokens (e.g. expires_in: 10).
  2. Connect to the server via the Inspector — OAuth flow completes successfully, tools are listed.
  3. Wait for the access token to expire (10+ seconds in this example).
  4. Click Run on any tool.
  5. The Response panel shows: Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token"}.
  6. No refresh token exchange is attempted. All subsequent tool calls also fail.

Expected Behavior

When a tool call receives HTTP 401 with WWW-Authenticate: Bearer error="invalid_token", the Inspector should use the refresh token to obtain a new access token from the server's token endpoint, then retry the request.

Screenshots

Image Image

Platform

Linux, MCPJam Inspector v2.0.5 (latest)

Additional Context

Our good friend Claude had the following suggestion:

The fix should be straightforward: createServerConfig() should pass refreshToken and clientId (both available in localStorage) as top-level fields in the HttpServerConfig instead of a static Authorization header:

function createServerConfig(
  serverUrl: string,
  tokens: any,
  clientId?: string,
): HttpServerConfig {
  return {
    url: serverUrl,
    refreshToken: tokens.refresh_token,
    clientId: clientId,
  };
}

This would cause MCPClientManager.connectViaHttp() (line ~1105) to create a RefreshTokenOAuthProvider and pass it as authProvider to the transport, enabling the automatic 401 → refresh → retry flow that the SDK already supports and tests (sdk/tests/refresh-token-auth.test.ts).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions