Просмотр исходного кода

fix(mcp): scope auth status to server URL (#33924)

Aiden Cline 1 месяц назад
Родитель
Сommit
3fbc005f26

+ 9 - 9
packages/opencode/src/cli/cmd/mcp.ts

@@ -669,14 +669,20 @@ export const McpDebugCommand = effectCmd({
     const config = yield* Config.Service.use((cfg) => cfg.get())
     const mcp = yield* MCP.Service
     const auth = yield* McpAuth.Service
+    const serverConfig = config.mcp?.[args.name]
+    const authInfo =
+      serverConfig && isMcpRemote(serverConfig) && serverConfig.oauth !== false
+        ? yield* Effect.all({
+            authStatus: mcp.getAuthStatus(args.name),
+            entry: auth.get(args.name),
+          })
+        : undefined
     yield* Effect.promise(async () => {
       UI.empty()
       prompts.intro("MCP OAuth Debug")
 
-      const mcpServers = config.mcp ?? {}
       const serverName = args.name
 
-      const serverConfig = mcpServers[serverName]
       if (!serverConfig) {
         prompts.log.error(`MCP server not found: ${serverName}`)
         prompts.outro("Done")
@@ -698,13 +704,7 @@ export const McpDebugCommand = effectCmd({
       prompts.log.info(`Server: ${serverName}`)
       prompts.log.info(`URL: ${serverConfig.url}`)
 
-      // Check stored auth status — services already in hand, run inline.
-      const { authStatus, entry } = await Effect.runPromise(
-        Effect.all({
-          authStatus: mcp.getAuthStatus(serverName),
-          entry: auth.get(serverName),
-        }),
-      )
+      const { authStatus, entry } = authInfo!
       prompts.log.info(`Auth status: ${getAuthStatusIcon(authStatus)} ${getAuthStatusText(authStatus)}`)
 
       if (entry?.tokens) {

+ 0 - 9
packages/opencode/src/mcp/auth.ts

@@ -50,7 +50,6 @@ export interface Interface {
   readonly updateOAuthState: (mcpName: string, oauthState: string) => Effect.Effect<void>
   readonly getOAuthState: (mcpName: string) => Effect.Effect<string | undefined>
   readonly clearOAuthState: (mcpName: string) => Effect.Effect<void>
-  readonly isTokenExpired: (mcpName: string) => Effect.Effect<boolean | null>
 }
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/McpAuth") {}
@@ -142,13 +141,6 @@ export const layer = Layer.effect(
       return entry?.oauthState
     })
 
-    const isTokenExpired = Effect.fn("McpAuth.isTokenExpired")(function* (mcpName: string) {
-      const entry = yield* get(mcpName)
-      if (!entry?.tokens) return null
-      if (!entry.tokens.expiresAt) return false
-      return entry.tokens.expiresAt < Date.now() / 1000
-    })
-
     return Service.of({
       all,
       get,
@@ -162,7 +154,6 @@ export const layer = Layer.effect(
       updateOAuthState,
       getOAuthState,
       clearOAuthState,
-      isTokenExpired,
     })
   }),
 )

+ 8 - 3
packages/opencode/src/mcp/index.ts

@@ -963,10 +963,15 @@ export const layer = Layer.effect(
     })
 
     const getAuthStatus = Effect.fn("MCP.getAuthStatus")(function* (mcpName: string) {
-      const entry = yield* auth.get(mcpName)
+      const runtimeConfig = (yield* InstanceState.has(state))
+        ? (yield* InstanceState.get(state)).config[mcpName]
+        : undefined
+      const mcpConfig = runtimeConfig ?? (yield* cfgSvc.get()).mcp?.[mcpName]
+      if (!mcpConfig || !isMcpConfigured(mcpConfig) || mcpConfig.type !== "remote") return "not_authenticated"
+      const entry = yield* auth.getForUrl(mcpName, mcpConfig.url)
       if (!entry?.tokens) return "not_authenticated"
-      const expired = yield* auth.isTokenExpired(mcpName)
-      return expired ? "expired" : "authenticated"
+      if (entry.tokens.expiresAt && entry.tokens.expiresAt < Date.now() / 1000) return "expired"
+      return "authenticated"
     })
 
     return Service.of({

+ 32 - 0
packages/opencode/test/mcp/oauth-auto-connect.test.ts

@@ -227,6 +227,38 @@ mcpTest.instance("state() returns existing state when one is saved", () =>
   }),
 )
 
+mcpTest.instance(
+  "auth status only reports credentials stored for the configured server URL",
+  () =>
+    Effect.gen(function* () {
+      const mcp = yield* MCP.Service
+      expect(transportCalls).toHaveLength(0)
+      yield* McpAuth.use.updateTokens(
+        "test-status-url",
+        { accessToken: "old-token" },
+        "https://old.example.com/mcp",
+      )
+
+      expect(yield* mcp.getAuthStatus("test-status-url")).toBe("not_authenticated")
+
+      yield* McpAuth.use.updateTokens(
+        "test-status-url",
+        { accessToken: "current-token" },
+        "https://example.com/mcp",
+      )
+      expect(yield* mcp.getAuthStatus("test-status-url")).toBe("authenticated")
+
+      yield* McpAuth.use.updateTokens(
+        "test-status-url",
+        { accessToken: "expired-token", expiresAt: 1 },
+        "https://example.com/mcp",
+      )
+      expect(yield* mcp.getAuthStatus("test-status-url")).toBe("expired")
+      expect(transportCalls).toHaveLength(0)
+    }),
+  { config: config("test-status-url") },
+)
+
 mcpTest.instance(
   "authenticate() stores a connected client when auth completes without redirect",
   () =>