Skip to content

Commit de022b3

Browse files
authored
fix(mcp): scope auth status to server URL (anomalyco#33924)
1 parent bc98549 commit de022b3

4 files changed

Lines changed: 49 additions & 21 deletions

File tree

packages/opencode/src/cli/cmd/mcp.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -669,14 +669,20 @@ export const McpDebugCommand = effectCmd({
669669
const config = yield* Config.Service.use((cfg) => cfg.get())
670670
const mcp = yield* MCP.Service
671671
const auth = yield* McpAuth.Service
672+
const serverConfig = config.mcp?.[args.name]
673+
const authInfo =
674+
serverConfig && isMcpRemote(serverConfig) && serverConfig.oauth !== false
675+
? yield* Effect.all({
676+
authStatus: mcp.getAuthStatus(args.name),
677+
entry: auth.get(args.name),
678+
})
679+
: undefined
672680
yield* Effect.promise(async () => {
673681
UI.empty()
674682
prompts.intro("MCP OAuth Debug")
675683

676-
const mcpServers = config.mcp ?? {}
677684
const serverName = args.name
678685

679-
const serverConfig = mcpServers[serverName]
680686
if (!serverConfig) {
681687
prompts.log.error(`MCP server not found: ${serverName}`)
682688
prompts.outro("Done")
@@ -698,13 +704,7 @@ export const McpDebugCommand = effectCmd({
698704
prompts.log.info(`Server: ${serverName}`)
699705
prompts.log.info(`URL: ${serverConfig.url}`)
700706

701-
// Check stored auth status — services already in hand, run inline.
702-
const { authStatus, entry } = await Effect.runPromise(
703-
Effect.all({
704-
authStatus: mcp.getAuthStatus(serverName),
705-
entry: auth.get(serverName),
706-
}),
707-
)
707+
const { authStatus, entry } = authInfo!
708708
prompts.log.info(`Auth status: ${getAuthStatusIcon(authStatus)} ${getAuthStatusText(authStatus)}`)
709709

710710
if (entry?.tokens) {

packages/opencode/src/mcp/auth.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export interface Interface {
5050
readonly updateOAuthState: (mcpName: string, oauthState: string) => Effect.Effect<void>
5151
readonly getOAuthState: (mcpName: string) => Effect.Effect<string | undefined>
5252
readonly clearOAuthState: (mcpName: string) => Effect.Effect<void>
53-
readonly isTokenExpired: (mcpName: string) => Effect.Effect<boolean | null>
5453
}
5554

5655
export class Service extends Context.Service<Service, Interface>()("@opencode/McpAuth") {}
@@ -142,13 +141,6 @@ export const layer = Layer.effect(
142141
return entry?.oauthState
143142
})
144143

145-
const isTokenExpired = Effect.fn("McpAuth.isTokenExpired")(function* (mcpName: string) {
146-
const entry = yield* get(mcpName)
147-
if (!entry?.tokens) return null
148-
if (!entry.tokens.expiresAt) return false
149-
return entry.tokens.expiresAt < Date.now() / 1000
150-
})
151-
152144
return Service.of({
153145
all,
154146
get,
@@ -162,7 +154,6 @@ export const layer = Layer.effect(
162154
updateOAuthState,
163155
getOAuthState,
164156
clearOAuthState,
165-
isTokenExpired,
166157
})
167158
}),
168159
)

packages/opencode/src/mcp/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -963,10 +963,15 @@ export const layer = Layer.effect(
963963
})
964964

965965
const getAuthStatus = Effect.fn("MCP.getAuthStatus")(function* (mcpName: string) {
966-
const entry = yield* auth.get(mcpName)
966+
const runtimeConfig = (yield* InstanceState.has(state))
967+
? (yield* InstanceState.get(state)).config[mcpName]
968+
: undefined
969+
const mcpConfig = runtimeConfig ?? (yield* cfgSvc.get()).mcp?.[mcpName]
970+
if (!mcpConfig || !isMcpConfigured(mcpConfig) || mcpConfig.type !== "remote") return "not_authenticated"
971+
const entry = yield* auth.getForUrl(mcpName, mcpConfig.url)
967972
if (!entry?.tokens) return "not_authenticated"
968-
const expired = yield* auth.isTokenExpired(mcpName)
969-
return expired ? "expired" : "authenticated"
973+
if (entry.tokens.expiresAt && entry.tokens.expiresAt < Date.now() / 1000) return "expired"
974+
return "authenticated"
970975
})
971976

972977
return Service.of({

packages/opencode/test/mcp/oauth-auto-connect.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,38 @@ mcpTest.instance("state() returns existing state when one is saved", () =>
227227
}),
228228
)
229229

230+
mcpTest.instance(
231+
"auth status only reports credentials stored for the configured server URL",
232+
() =>
233+
Effect.gen(function* () {
234+
const mcp = yield* MCP.Service
235+
expect(transportCalls).toHaveLength(0)
236+
yield* McpAuth.use.updateTokens(
237+
"test-status-url",
238+
{ accessToken: "old-token" },
239+
"https://old.example.com/mcp",
240+
)
241+
242+
expect(yield* mcp.getAuthStatus("test-status-url")).toBe("not_authenticated")
243+
244+
yield* McpAuth.use.updateTokens(
245+
"test-status-url",
246+
{ accessToken: "current-token" },
247+
"https://example.com/mcp",
248+
)
249+
expect(yield* mcp.getAuthStatus("test-status-url")).toBe("authenticated")
250+
251+
yield* McpAuth.use.updateTokens(
252+
"test-status-url",
253+
{ accessToken: "expired-token", expiresAt: 1 },
254+
"https://example.com/mcp",
255+
)
256+
expect(yield* mcp.getAuthStatus("test-status-url")).toBe("expired")
257+
expect(transportCalls).toHaveLength(0)
258+
}),
259+
{ config: config("test-status-url") },
260+
)
261+
230262
mcpTest.instance(
231263
"authenticate() stores a connected client when auth completes without redirect",
232264
() =>

0 commit comments

Comments
 (0)