Skip to content

[Bug]: MCP servers that allow unauthenticated initialize (HTTP 200) never trigger OAuth — e.g. Google's hosted Drive MCP #13249

Description

@yakovyarmo

What happened?

A streamable-http MCP server configured with requiresOAuth: true never starts the OAuth flow — the Google consent screen is never shown. LibreChat reports the server as "reinitialized successfully," oauthRequired: false, and the token store stays empty. The failure only surfaces later, when a tool is actually executed, as an auth error from the upstream API.

Concrete repro server: Google's official hosted Drive MCP at https://drivemcp.googleapis.com/mcp/v1.

Root cause

LibreChat only initiates OAuth when the MCP server returns HTTP 401/403 on connect:

  • MCPConnection.connectClient() emits oauthRequired only from the catch block, gated by isOAuthError(error) (401/403) — packages/api/src/mcp/connection.ts:1116 (matcher at :1503-1538).
  • MCPConnectionFactory.getOAuthTokens() finds no stored token, logs "...will trigger OAuth flow", then just returns null — it does not start anything (packages/api/src/mcp/MCPConnectionFactory.ts:283).
  • createConnection() then connects unauthenticated and, because the server replies 200, succeeds — so oauthRequired is never emitted (MCPConnectionFactory.ts:226-247).
  • reinitMCPServer therefore returns { success: true, oauthRequired: false, oauthUrl: null } (api/server/services/Tools/mcp.js).
  • The frontend only opens the consent popup when oauthRequired && oauthUrl (client/src/hooks/MCP/useMCPServerManager.ts:339-348), so nothing happens.

Google's hosted Drive MCP does not follow the 401-challenge pattern. Verified directly with curl (no Authorization header):

  • POST initialize → HTTP 200 (returns serverInfo/capabilities)
  • POST tools/list → HTTP 200 (returns the full tool list)
  • POST tools/call → HTTP 200 with an in-body JSON-RPC error: "Request is missing required authentication credential. Expected OAuth 2 access token..."

It does expose RFC 9728 metadata at /.well-known/oauth-protected-resource/mcp/v1 (authorization_servers: ["https://accounts.google.com/"]), so OAuth is clearly required — it's just never enforced at connect time. Net effect: requiresOAuth: true has effectively no effect for such servers.

Steps to reproduce

  1. Configure the server in librechat.yaml:
    mcpServers:
      google-drive:
        type: streamable-http
        url: https://drivemcp.googleapis.com/mcp/v1
        requiresOAuth: true
        oauth:
          client_id: ${GOOGLE_DRIVE_CLIENT_ID}
          client_secret: ${GOOGLE_DRIVE_CLIENT_SECRET}
          scope: "https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.file"
          token_url: https://oauth2.googleapis.com/token
          authorization_url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent
    (Redirect URI https://<domain>/api/mcp/google-drive/oauth/callback registered in Google Cloud.)
  2. Reinitialize/connect the server in the UI — it reports success, no consent screen.
  3. Run a tool → fails: "Request is missing required authentication credential."

Expected behavior

When requiresOAuth: true (or oauth/protected-resource metadata is present) and no valid token exists, LibreChat should proactively start the OAuth flow instead of waiting for a 401 — i.e., treat "OAuth-required server + no token" the same as a 401 challenge.

Suggested fix

When requiresOAuth: true (or oauth/protected-resource metadata is present) and no valid token exists, proactively start the OAuth flow instead of waiting for a 401. LibreChat already discovers RFC 9728 metadata; the gap is only that getOAuthTokens() returns null silently (MCPConnectionFactory.ts:283) and oauthRequired is emitted solely from the 401 catch path (connection.ts:1116). Trigger it in the user-connection path (MCPConnectionFactory.createConnection), gated so tool discovery (discoverToolsInternal) and app-level startup connections still list tools unauthenticated.

This mirrors the fix being made in Dify for the identical server/behavior (see Related).

Related

Workaround

Front the server with a sidecar (nginx/Caddy) that returns 401 for unauthenticated requests, forcing LibreChat into the OAuth flow.

Version / Environment

  • LibreChat v0.8.6-rc1 (image registry.librechat.ai/danny-avila/librechat-dev:latest)
  • librechat.yaml schema version 1.3.11
  • Transport: streamable-http; server: Google hosted Drive MCP (https://drivemcp.googleapis.com/mcp/v1)
  • Deployment: Docker Compose

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions