improve mcp CLI + ability to debug MCP oauth (#5980)

This commit is contained in:
Matt Silverlock
2025-12-22 23:27:38 -05:00
committed by GitHub
parent 161e9287a8
commit 1a2b656c4d
4 changed files with 333 additions and 23 deletions

View File

@@ -121,4 +121,15 @@ export namespace McpAuth {
await set(mcpName, entry)
}
}
/**
* Check if stored tokens are expired.
* Returns null if no tokens exist, false if no expiry or not expired, true if expired.
*/
export async function isTokenExpired(mcpName: string): Promise<boolean | null> {
const entry = await get(mcpName)
if (!entry?.tokens) return null
if (!entry.tokens.expiresAt) return false
return entry.tokens.expiresAt < Date.now() / 1000
}
}

View File

@@ -15,6 +15,8 @@ import { withTimeout } from "@/util/timeout"
import { McpOAuthProvider } from "./oauth-provider"
import { McpOAuthCallback } from "./oauth-callback"
import { McpAuth } from "./auth"
import { Bus } from "@/bus"
import { TuiEvent } from "@/cli/cmd/tui/event"
import open from "open"
export namespace MCP {
@@ -251,10 +253,24 @@ export namespace MCP {
status: "needs_client_registration" as const,
error: "Server does not support dynamic client registration. Please provide clientId in config.",
}
// Show toast for needs_client_registration
Bus.publish(TuiEvent.ToastShow, {
title: "MCP Authentication Required",
message: `Server "${key}" requires a pre-registered client ID. Add clientId to your config.`,
variant: "warning",
duration: 8000,
}).catch((e) => log.debug("failed to show toast", { error: e }))
} else {
// Store transport for later finishAuth call
pendingOAuthTransports.set(key, transport)
status = { status: "needs_auth" as const }
// Show toast for needs_auth
Bus.publish(TuiEvent.ToastShow, {
title: "MCP Authentication Required",
message: `Server "${key}" requires authentication. Run: opencode mcp auth ${key}`,
variant: "warning",
duration: 8000,
}).catch((e) => log.debug("failed to show toast", { error: e }))
}
break
}
@@ -623,4 +639,16 @@ export namespace MCP {
const entry = await McpAuth.get(mcpName)
return !!entry?.tokens
}
export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
/**
* Get the authentication status for an MCP server.
*/
export async function getAuthStatus(mcpName: string): Promise<AuthStatus> {
const hasTokens = await hasStoredTokens(mcpName)
if (!hasTokens) return "not_authenticated"
const expired = await McpAuth.isTokenExpired(mcpName)
return expired ? "expired" : "authenticated"
}
}