mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-26 08:12:13 +00:00
fix(mcp): include scope in clientMetadata and add callbackPort option
Two related OAuth issues with remote MCP servers: 1. `scope` config field was accepted but never propagated to `clientMetadata`. The MCP SDK uses `clientMetadata.scope` as its last-resort fallback when neither the WWW-Authenticate header nor the Protected Resource Metadata (scopes_supported) advertise scopes. For servers whose metadata returns no scopes (e.g. AWS Bedrock AgentCore), the authorization request was sent with no scope parameter, causing IdPs such as Okta to reject with "No scopes were requested." 2. The callback port was hardcoded to 19876 with no way to override it short of providing a full `redirectUri`. Added `callbackPort` as a shorthand in the OAuth config — when set it constructs the redirect URI as `http://127.0.0.1:<callbackPort>/mcp/oauth/callback`. `redirectUri` still takes precedence if both are provided. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
76d9c2cd76
commit
e9ca7d292b
3 changed files with 19 additions and 4 deletions
|
|
@ -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(PositiveInt).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).",
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue