fix(mcp): include scope in clientMetadata and add callbackPort option (#28810)

Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
This commit is contained in:
Sebin
2026-05-22 13:40:38 +02:00
committed by GitHub
parent 2663ecd39d
commit 7a769dab3a
4 changed files with 80 additions and 4 deletions

View File

@@ -26,6 +26,10 @@ export const OAuth = Schema.Struct({
description: "OAuth client secret (if required by the authorization server)",
}),
scope: Schema.optional(Schema.String).annotate({ description: "OAuth scopes to request during authorization" }),
callbackPort: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))).annotate({
description:
"Port for the local OAuth callback server (default: 19876). Shorthand for redirectUri when only the port needs changing. Ignored if redirectUri is set.",
}),
redirectUri: Schema.optional(Schema.String).annotate({
description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).",
}),

View File

@@ -19,7 +19,7 @@ import { NamedError } from "@opencode-ai/core/util/error"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { withTimeout } from "@/util/timeout"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { McpOAuthProvider } from "./oauth-provider"
import { McpOAuthProvider, OAUTH_CALLBACK_PATH } from "./oauth-provider"
import { McpOAuthCallback } from "./oauth-callback"
import { McpAuth } from "./auth"
import { BusEvent } from "../bus/bus-event"
@@ -318,6 +318,7 @@ export const layer = Layer.effect(
clientId: oauthConfig?.clientId,
clientSecret: oauthConfig?.clientSecret,
scope: oauthConfig?.scope,
callbackPort: oauthConfig?.callbackPort,
redirectUri: oauthConfig?.redirectUri,
},
{
@@ -769,8 +770,15 @@ export const layer = Layer.effect(
// OAuth config is optional - if not provided, we'll use auto-discovery
const oauthConfig = typeof mcpConfig.oauth === "object" ? mcpConfig.oauth : undefined
// Resolve effective redirect URI: explicit redirectUri > callbackPort shorthand > default
const effectiveRedirectUri =
oauthConfig?.redirectUri ??
(oauthConfig?.callbackPort
? `http://127.0.0.1:${oauthConfig.callbackPort}${OAUTH_CALLBACK_PATH}`
: undefined)
// Start the callback server with custom redirectUri if configured
yield* Effect.promise(() => McpOAuthCallback.ensureRunning(oauthConfig?.redirectUri))
yield* Effect.promise(() => McpOAuthCallback.ensureRunning(effectiveRedirectUri))
const oauthState = Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map((b) => b.toString(16).padStart(2, "0"))
@@ -784,7 +792,7 @@ export const layer = Layer.effect(
clientId: oauthConfig?.clientId,
clientSecret: oauthConfig?.clientSecret,
scope: oauthConfig?.scope,
redirectUri: oauthConfig?.redirectUri,
redirectUri: effectiveRedirectUri,
},
{
onRedirect: async (url) => {

View File

@@ -18,6 +18,7 @@ export interface McpOAuthConfig {
clientId?: string
clientSecret?: string
scope?: string
callbackPort?: number
redirectUri?: string
}
@@ -38,7 +39,8 @@ export class McpOAuthProvider implements OAuthClientProvider {
if (this.config.redirectUri) {
return this.config.redirectUri
}
return `http://127.0.0.1:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`
const port = this.config.callbackPort ?? OAUTH_CALLBACK_PORT
return `http://127.0.0.1:${port}${OAUTH_CALLBACK_PATH}`
}
get clientMetadata(): OAuthClientMetadata {
@@ -49,6 +51,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
grant_types: ["authorization_code", "refresh_token"],
response_types: ["code"],
token_endpoint_auth_method: this.config.clientSecret ? "client_secret_post" : "none",
...(this.config.scope ? { scope: this.config.scope } : {}),
}
}