mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 20:52:01 +03:00
fix(core): keep the configured MCP URL as the OAuth resource (#49241)
This commit is contained in:
committed by
GitHub
Unverified
parent
7c3c50bac2
commit
6ee156e0b9
@@ -168,7 +168,12 @@ export const provider = (options: Options): OAuthClientProvider => {
|
||||
if (!resource) return identity
|
||||
if (!checkResourceAllowed({ requestedResource: identity, configuredResource: resource }))
|
||||
throw new Error(`Protected resource ${resource} does not cover ${identity}`)
|
||||
return new URL(resource)
|
||||
const canonical = new URL(resource)
|
||||
// The transport dials the configured URL with extra query parameters and some servers echo
|
||||
// that back as the resource. Query is transport detail, not identity: the token stays bound
|
||||
// to the configured URL so a later refresh names the same resource the login did.
|
||||
if (canonical.origin === identity.origin && canonical.pathname === identity.pathname) return identity
|
||||
return canonical
|
||||
},
|
||||
...(options.clientMetadataUrl ? { clientMetadataUrl: options.clientMetadataUrl } : {}),
|
||||
...(redirect ? { state: () => redirect.state } : {}),
|
||||
|
||||
@@ -324,6 +324,44 @@ describe("MCP OAuth", () => {
|
||||
expect(probes.some((probe) => probe.includes("codemode"))).toBe(false)
|
||||
})
|
||||
|
||||
test("keeps the configured URL as the resource when metadata echoes the dialed query", async () => {
|
||||
const tokenRequests: URLSearchParams[] = []
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url)
|
||||
if (url.pathname === "/.well-known/oauth-authorization-server")
|
||||
return Response.json({
|
||||
issuer: url.origin,
|
||||
authorization_endpoint: `${url.origin}/authorize`,
|
||||
token_endpoint: `${url.origin}/token`,
|
||||
response_types_supported: ["code"],
|
||||
})
|
||||
if (url.pathname === "/.well-known/oauth-protected-resource/mcp")
|
||||
return Response.json({ resource: `${url.origin}/mcp${url.search}`, authorization_servers: [url.origin] })
|
||||
if (request.method === "POST" && url.pathname === "/token") {
|
||||
tokenRequests.push(new URLSearchParams(await request.text()))
|
||||
return Response.json({ access_token: "next", token_type: "Bearer" })
|
||||
}
|
||||
return new Response(null, { status: 404 })
|
||||
},
|
||||
})
|
||||
const url = `${server.url.origin}/mcp`
|
||||
const oauthProvider = await connectProvider(
|
||||
remote(url),
|
||||
memoryCredentials([credential({ access: "expired", refresh: "refresh", url })]),
|
||||
)
|
||||
|
||||
// The transport dials with ?codemode=false and follows the 401 challenge to metadata that echoes it.
|
||||
await auth(oauthProvider, {
|
||||
serverUrl: `${url}?codemode=false`,
|
||||
resourceMetadataUrl: new URL(`${server.url.origin}/.well-known/oauth-protected-resource/mcp?codemode=false`),
|
||||
}).finally(() => server.stop(true))
|
||||
|
||||
expect(tokenRequests[0]?.get("grant_type")).toBe("refresh_token")
|
||||
expect(tokenRequests[0]?.get("resource")).toBe(url)
|
||||
})
|
||||
|
||||
test("finds resource metadata through the 401 header when the well-known path is not served", async () => {
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
|
||||
Reference in New Issue
Block a user