Skip to content

mcp: V2 does not import V1 MCP OAuth credentials from mcp-auth.json, servers drop to needs_auth after upgrade #53607

Closed as not planned

Description

@laurisvr

Summary

V2 does not import remote MCP OAuth credentials that V1 stored in <data>/mcp-auth.json. After upgrading, every OAuth-protected remote MCP server goes to needs_auth, even when the V1 file still holds a valid refresh token. V2 gives no warning or hint that a V1 login exists.

Environment

  • opencode version: 0.0.0-dev-202610031828 (local build of dev, comparable to v2.0.18+)
  • OS: Linux 6.12.107+deb13-amd64 x86_64
  • Terminal: Unavailable: terminal environment variables are not set (server/web client)
  • Shell: /bin/bash
  • Install/channel: dev
  • Active plugins: none found in config

Reproduction

  1. On V1, configure a remote MCP server with OAuth (type: "remote", oauth: {...}) and authorize it. V1 writes ~/.local/share/opencode/mcp-auth.json with tokens.accessToken, tokens.refreshToken, tokens.expiresAt and serverUrl for the server.
  2. Upgrade to V2 and start it.
  3. Look at the server's MCP status, or the credential table in opencode.db.

Expected Behavior

V2 imports the V1 MCP credential into the credential table, as it already does for provider credentials from auth.json (migration 20260805200742_import_legacy_credentials). The server should then connect, refreshing with the imported refresh token if the access token has expired.

If an entry can't be imported, V2 should at least say that a V1 credential exists and that you need to sign in again.

Actual Behavior

  • The credential table has no row for the MCP server (0 rows in total here).
  • The server sends a 401 and OpenCode reports needs_auth:
    level=WARN message="mcp http authentication rejected" status=401 server=<name>
    level=WARN message="mcp connect failed" server=<name> status.status=needs_auth status.error="MCP server \"https://…/mcp\" requires user authorization"
    
  • No token-endpoint call is ever made. mcp-auth.json is never read again. Here its last write is the final V1 login on 2026-09-28.
  • On the authorization server (Authentik), the V1 refresh token is still active and unrevoked, valid until 2026-10-28. It could have been used.

Additional Context

Cause: packages/core/src/database/migration/20260805200742_import_legacy_credentials.ts imports <data>/auth.json only. No non-test code in V2 reads mcp-auth.json. V2 MCP OAuth (packages/core/src/mcp/oauth.ts, connectProvider) reads only Credential.Service, keyed by an integration id derived in packages/core/src/mcp/index.ts as "mcp_" + sha1(name + "\0" + url).slice(0, 16).

Suggested fix: add a migration next to import_legacy_credentials that, for each mcp-auth.json entry with tokens and serverUrl:

  • derives the same integration id from (name, serverUrl), using a helper shared with mcp/index.ts rather than a copy;
  • writes a Credential.OAuth shaped like McpOAuth.toCredential:
    • access;
    • refresh ?? "";
    • expires = expiresAt * 1000 (V1 stores seconds). If expiresAt is absent, use a time in the past such as 1, not 0: in V2 0 means non-expiring, so toTokens would omit expires_in and the SDK would never refresh;
    • metadata with serverUrl, tokenType: "Bearer", scope, and client, where client is V1 clientInfo mapped to the SDK client-information shape. clientInfo is optional. When the config sets oauth.client_id, provider() takes the client from config and refreshes normally. When neither is present (a server that used dynamic client registration), clientInformation() throws before any token request, so the server reports needs_auth as it does today. The imported row is not invalidated; it simply stays until the user signs in again and replaces it;
  • skips entries whose integration id already has a credential, entries without tokens, and malformed input, without failing the migration;
  • leaves mcp-auth.json in place.

An expired imported access token is fine: toTokens yields expires_in: 0, so the SDK refreshes on first use.

Related, but a separate bug: #49773 / #51979 cover V2 not refreshing MCP OAuth tokens once a credential exists. This issue is about the credential never reaching V2 in the first place. Fixing either one alone doesn't restore upgraded users who have long-lived refresh tokens.

Workaround: sign in to each OAuth MCP server again (/mcps → server, or opencode mcp auth <name>).

Testing note: a fresh install records every migration as applied without running its up step, which is correct. So only an upgrade, meaning an existing database with this migration still pending and an old mcp-auth.json present, exercises the import. Test an end-to-end fix on that path, not on a fresh data directory.

Note on the base branch: the relevant code is on the v2 branch, not dev.

Activity

  1. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    This issue doesn't fully meet our contributing guidelines.

    What needs to be fixed:

    • This report does not appear to use one of the required issue templates. The Bug Report template requires a Description field; this issue uses Summary and custom sections instead. Please edit it to use the Bug Report template format and put the bug description in the required field. The reproduction steps and system details already provided are useful.

    Please edit this issue to address the above within 72 hours, or it will be automatically closed.


    This issue might be a duplicate of existing issues. Please check:

    • #49773: Similar MCP OAuth needs_auth and restart symptoms; that report concerns token refresh after a credential exists.
    • #42875: Also describes MCP servers requiring re-auth with mcp-auth.json; that report concerns tokens being lost from the V1 file rather than V2 failing to import them.

    If you believe this was flagged incorrectly, please let a maintainer know.

  2. opencode-agent commented on Oct 6, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on v2.0.24.

    Steps:

    1. In a fresh data dir, I wrote a v1-style mcp-auth.json entry for a remote OAuth MCP server (tokens.accessToken, refreshToken, a future expiresAt, clientInfo and serverUrl).
    2. I configured the same server in opencode.json with type: "remote" and oauth: {}.
    3. I started v2 and ran opencode2 mcp list, then opened /mcps in the TUI.

    The startup migrations ran, but the credential table stayed empty. The server reported needs authentication (Sign in required in /mcps). The test MCP server only received requests without an Authorization header, and its token endpoint was never called, so v2 never read the v1 credentials. As you said, signing in to the server again in v2 is the workaround for now.

  3. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    Thanks for the update. This still needs one required template field: the report uses a Summary section, but the Bug Report template requires a Description field with real content. Please update the issue to use the Bug Report template and put the existing bug description in its required Description field. The reproduction steps and environment details are already useful.

  4. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    This is still missing the required Description field from the Bug Report template. The current report has a Summary section instead. Please update the issue to use the Bug Report template and place the bug description under its required Description field; the reproduction steps and system details are already helpful.

  5. github-actions commented on Oct 9, 2026

    @github-actions
    Contributor

    This issue has been automatically closed because it was not updated to meet our contributing guidelines within the 72-hour window.

    Feel free to open a new issue that follows our issue templates.

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