Repository navigation
Conversation
There was a problem hiding this comment.
Looks good. I ran the #54879 reproduction from source in both checkouts: a real V1 data dir with a V1 mcp-auth.json, and a mock OAuth MCP server that only accepts the V1 bearer token. On the base the server shows needs authentication, no MCP credential is imported, and no request carries the token. With this PR an OAuth credential row is created under the same integration id the MCP layer looks up, mcp list shows connected, and every request carries the V1 access token. The migration tests and core typecheck pass. The one failing MCP test (stdio descendant termination) also fails on the base.
Public API unchanged: this adds an internal McpIdentity module and a database migration, and only reads the V1 mcp-auth.json.
No blocking concerns. The import mirrors the existing auth.json migration, and the id derivation is unchanged, so existing V2 credentials keep their ids.
Issue for this PR
Closes #54879
Closes #55964
Type of change
What does this PR do?
V1 stored remote MCP OAuth tokens in
mcp-auth.json, one entry per server name. V2 looks up an MCP credential in thecredentialtable undermcp_<sha1(name + "\0" + url)>, and nothing ever copies the V1 file there. The V1 provider keys inauth.jsonare imported by20260805200742_import_legacy_credentials, but there is no equivalent formcp-auth.json. SoconnectProviderfinds no row, falls back to an empty store, and every V1 OAuth server showsneeds authenticationafter the upgrade.This adds a migration,
20261016120000_import_legacy_mcp_credentials, that readsmcp-auth.jsonand writes one OAuth credential for each usable entry:McpIdentity.integrationID(name, url), which the MCP layer now uses too, so the two can't drift. The derivation itself is unchanged, so existing V2 credentials keep their ids.expiresAtis in seconds (and often fractional); V2 stores whole milliseconds, with 0 meaning no expiry.clientId, secret, issued/expiry times) is kept inmetadata.client. Refresh tokens are bound to the client that obtained them, so refreshing against a new registration would fail.serverUrlare skipped, because V1 only used an entry when itsserverUrlmatched the configured URL. Entries without tokens are skipped because they are unfinished logins.Relation to #55197: that PR takes the same approach, and I checked its diff while working on this. I traced the problem and the fix separately against
dev. The differences here are a namespaced identity module that follows the core self-export pattern, keeping V1's client issued/expiry metadata, and an end-to-end check against a mock server. Either PR fixes the bug, so whichever one lands, the other can be closed.How did you verify your code works?
packages/core/test/database-migration.test.ts:McpOAuth.toTokensandMcpOAuth.clientFromCredential, the same calls the connect-time provider makes.mcp-auth.json, and ranopencode mcp listagainst a mock OAuth MCP server that only accepts the V1 bearer token.origin/dev:⚠ work needs authentication, no credential row, and no request carried the token.✓ work connected, and the mock received the V1 bearer token.bun testfordatabase-migration,mcp,mcp-oauth, andmcp-import-boundaryinpackages/core: 110 pass.bun run script/migration.ts --checkpasses, andbun run checkpasses.Screenshots / recordings
N/A
Checklist