[BUG] Remote MCP OAuth requests every scope from oauth-authorization-server.scopes_supported, causing invalid_scope on GitLab self-hosted

Resolved 💬 4 comments Opened May 4, 2026 by sihyun-park2 Closed Jun 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When Claude Code connects to a remote MCP server via the native HTTP transport, the OAuth 2.1 + PKCE authorize request includes every scope advertised in the authorization server's scopes_supported rather than only the scopes required by the protected MCP resource. On GitLab self-hosted instances this list contains admin-only and dynamic-template scopes that a normal user cannot be granted, so the entire OAuth flow aborts with invalid_scope before the MCP handshake even begins.

The community mcp-remote stdio↔HTTP bridge authenticates against the same endpoint, same user, same OAuth server without issue. This isolates the defect to Claude Code's native HTTP transport OAuth scope-selection logic.

What Should Happen?

Per RFC 9728 (OAuth 2.0 Protected Resource Metadata) and the MCP authorization specification, the client should request only the scopes required by the protected resource, discovered via either:

  1. The WWW-Authenticate: Bearer ... scope="..." header on the MCP endpoint's 401 response, or
  2. scopes_supported in https://<gitlab-host>/.well-known/oauth-protected-resource[/api/v4/mcp]

For GitLab MCP this resolves to mcp (optionally augmented with openid read_user for identity).

scopes_supported from the authorization server metadata represents what the AS can issue across all clients — not what the resource requires. Treating the two as equivalent is the root cause here.

Confirming the bug is client-side, not server-side

Replacing the native HTTP registration with the community mcp-remote stdio bridge — pointing at the same URL, same OAuth server, same user account — succeeds:

claude mcp remove GitLab
claude mcp add GitLab -- npx -y mcp-remote https://<gitlab-host>/api/v4/mcp

mcp-remote requests scope=mcp only, and GitLab issues the token. Claude Desktop also relies on mcp-remote for this same endpoint and works for the same reason. This rules out:

  • GitLab not supporting HTTP transport for MCP (it does — mcp-remote speaks HTTP to it)
  • GitLab not supporting OAuth (it does — full RFC-compliant metadata)
  • GitLab not issuing the mcp scope (it does — to mcp-remote)

The OAuth failure happens before the MCP transport handshake, so transport-level differences cannot explain it. The defect is in Claude Code's OAuth scope-selection step.

Error Messages/Logs

Steps to Reproduce

  1. Register the GitLab MCP endpoint with the native HTTP transport: claude mcp add --transport http GitLab https://<gitlab-host>/api/v4/mcp
  2. Run /mcp in Claude Code and trigger GitLab authentication.
  3. The browser opens an authorize URL (captured below).
  4. GitLab responds with: The requested scope is invalid, unknown, or malformed.

Actual authorize URL produced by Claude Code

  https://<gitlab-host>/oauth/authorize
    ?response_type=code
    &client_id=<dcr-client-id>
    &code_challenge=<...>
    &code_challenge_method=S256
    &redirect_uri=http%3A%2F%2Flocalhost%3A59251%2Fcallback
    &state=<...>
    &scope=api+read_api+read_user+create_runner+manage_runner+k8s_proxy
          +self_rotate+mcp+read_repository+write_repository
          +read_virtual_registry+write_virtual_registry
          +read_observability+write_observability+ai_features
          +sudo+admin_mode+read_service_ping
          +openid+profile+email+ai_workflows+user%3A*

The 23-element scope= parameter is byte-identical to scopes_supported from https://<gitlab-host>/.well-known/oauth-authorization-server:

  [
    "api","read_api","read_user","create_runner","manage_runner","k8s_proxy",
    "self_rotate","mcp","read_repository","write_repository",
    "read_virtual_registry","write_virtual_registry",
    "read_observability","write_observability","ai_features",
    "sudo","admin_mode","read_service_ping",
    "openid","profile","email","ai_workflows","user:*"
  ]

Scopes that cause the failure:

  • sudo, admin_mode — admin-only, not grantable to regular users
  • user:* — dynamic scope template (user:<id>), not a literal value
  • Any one of these triggers invalid_scope and aborts the entire flow

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.126 (Claude Code)

Platform

Other

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Environment

  • Claude Code version: 2.1.126 (Claude Code)
  • OS: macOS 15 (Darwin 25.4.0)
  • MCP server: GitLab Community Edition 18.7.0, self-hosted
  • MCP endpoint: https://<gitlab-host>/api/v4/mcp
  • Transport tested: --transport http (native, fails) vs. mcp-remote stdio bridge (works)

Workaround

Two options that work today:

  1. Manual scope override — keep the original client_id, code_challenge, state, redirect_uri, replace scope=... with scope=mcp (or scope=mcp+openid+read_user), open in a browser tab while Claude Code is still listening on the callback port. Token exchange completes normally.
  2. Use mcp-remote instead of native HTTP (recommended):

claude mcp add GitLab -- npx -y mcp-remote https://<gitlab-host>/api/v4/mcp

Suggested fix

When initiating OAuth for a remote MCP server, derive the requested scope set in this order:

  1. The scope parameter from WWW-Authenticate: Bearer on the MCP endpoint's 401 response.
  2. scopes_supported from oauth-protected-resource metadata (RFC 9728).
  3. A minimal default of mcp — never the full scopes_supported from the authorization server metadata.

Impact / scope of the bug

This will affect any OAuth server whose oauth-authorization-server.scopes_supported advertises scopes the user cannot be granted — extremely common in enterprise IdPs (GitLab, Keycloak, Okta with custom scopes, etc.). The reproduction here is GitLab self-hosted, but the fix benefits all native-HTTP MCP integrations.

References

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗