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
- Configure an MCP server with OAuth 2.0 that issues short-lived access tokens (e.g.
expires_in: 10).
- Connect to the server via the Inspector — OAuth flow completes successfully, tools are listed.
- Wait for the access token to expire (10+ seconds in this example).
- Click Run on any tool.
- The Response panel shows:
Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token"}.
- 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
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).
Issue
When an MCP server's OAuth access token expires during an active session...
Toolstab fail permanently withStreamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token"}.App Buildertab 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:
How to reproduce
expires_in: 10).Streamable HTTP error: Error POSTing to endpoint: {"error":"invalid_token"}.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
Platform
Linux, MCPJam Inspector v2.0.5 (latest)
Additional Context
Our good friend Claude had the following suggestion: