Skip to content

Remote MCP OAuth never triggers when the server challenges on tool calls (not on initialize) #36946

Closed

Description

@kevineger

Description

Summary

opencode decides whether a remote MCP server needs OAuth solely from the HTTP status of the initialize request it sends at connect. Servers that allow unauthenticated discovery (initialize / tools/list return 200) and only challenge with 401 + WWW-Authenticate on tools/call never trigger the OAuth flow. mcp auth reports success, stores no token, and tool calls fail with Unauthorized.

Root cause

In packages/opencode/src/cli/cmd/mcp.ts, auth detection branches only on the initialize status.

The www-authenticate header is read but not acted on unless the status is 401. A mid-session 401 on tools/call surfaces as UnauthorizedError without starting the flow.

Expected

Per RFC 9728 / the MCP authorization spec, a client should discover auth from Protected Resource Metadata even when the base endpoint returns 200, e.g. proactively fetch /.well-known/oauth-protected-resource/<path> at init, and/or initiate + complete OAuth from a 401 on any request (including tools/call). Either approach preserves unauthenticated discovery.

Suggested fix

Trigger OAuth discovery from Protected Resource Metadata rather than gating solely on a 401 from initialize: fetch the PRM at init when advertised, or run the OAuth flow on a 401 from tools/call.

Plugins

No response

OpenCode version

No response

Steps to reproduce

A remote MCP server where:

  • POST /mcp initialize → 200 (no WWW-Authenticate)
  • POST /mcp tools/call (unauthenticated) → 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp"
  • /.well-known/oauth-protected-resource/mcp → valid PRM

Then:
$ opencode mcp auth <server> # "Authentication successful!" but stores nothing
$ opencode mcp auth list # still "not authenticated"
…and any tool call returns Unauthorized.

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. opencode-agent commented on Jul 14, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. We confirmed this behavior in the current V1 implementation.

    The reported failure is resolved in OpenCode V2, which we're planning to release soon. The V2 work is available on the v2 branch.

    V2's explicit MCP authorization path no longer decides whether OAuth is required from the initialize response. It invokes the MCP SDK's authorization flow directly:

    const result = yield* Effect.tryPromise({
    try: () => auth(oauthProvider, { serverUrl: input.config.url, scope: oauth?.scope }),
    catch: (error) => (error instanceof Error ? error : new Error(String(error))),
    })

    The SDK then performs RFC 9728 protected-resource discovery independently of initialize, including the standard path-aware metadata location:

    https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.29.0/src/client/auth.ts#L488-L499

    V2 also supplies that OAuth provider to the streamable HTTP transport:

    if (!URL.canParse(config.url))
    return yield* new ConnectError({ server, message: `Invalid MCP URL for "${server}"` })
    return new StreamableHTTPClientTransport(new URL(config.url), {
    requestInit: config.headers ? { headers: config.headers } : undefined,
    authProvider,
    })

    The transport recognizes a later 401, extracts resource_metadata and scope from WWW-Authenticate, and enters the SDK's authorization/refresh logic:

    https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.29.0/src/client/streamableHttp.ts#L489-L518

    We verified the reported server shape against V2: initialize and tools/list returned 200, while tools/call returned 401. V2 discovered the protected-resource metadata and produced the OAuth authorization URL. Unlike V1, invoking MCP authorization does not falsely report success without storing credentials.

    Try the V2 beta

    V2 is currently a work in progress. Beta data may be wiped, features may break, and APIs and configuration may continue to change. You can install it alongside V1 from npm's next tag:

    npm install -g @opencode-ai/cli@next

    The beta installs as a separate executable:

    opencode2

    See the V2 beta documentation for current setup and migration details.

    Since the reported authentication failure is resolved in the forthcoming V2 implementation, we're closing this as completed. If it remains reproducible after V2 is released, please open a new issue with the updated reproduction details.

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

Metadata

Metadata

Assignees

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