fix(mcp): retain DCR metadata fallback
This commit is contained in:
@@ -563,7 +563,7 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
||||
* accept the later authorize request for the same scope set.
|
||||
*/
|
||||
async #tryRegisterClient(redirectUri: string): Promise<void> {
|
||||
const registrationEndpoint = this.config.registrationUrl;
|
||||
const registrationEndpoint = this.config.registrationUrl ?? (await this.#resolveRegistrationEndpoint());
|
||||
if (!registrationEndpoint) return;
|
||||
|
||||
try {
|
||||
@@ -620,6 +620,56 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
|
||||
}
|
||||
}
|
||||
|
||||
async #resolveRegistrationEndpoint(): Promise<string | null> {
|
||||
const authorizationUrl = new URL(this.config.authorizationUrl);
|
||||
|
||||
// origin-root well-known; most servers serve metadata here.
|
||||
const rootUrl = new URL("/.well-known/oauth-authorization-server", authorizationUrl.origin).toString();
|
||||
const endpoint = await this.#tryWellKnownForRegistration(rootUrl);
|
||||
if (endpoint) return endpoint;
|
||||
|
||||
// path-prefixed well-known for gateways (e.g. https://gateway.example.com/my-service/).
|
||||
const normalizedPath = authorizationUrl.pathname.replace(/\/$/, "");
|
||||
const lastSlash = normalizedPath.lastIndexOf("/");
|
||||
// Bare-origin authorization URL — nothing further to try.
|
||||
if (lastSlash < 0) return null;
|
||||
|
||||
// Single-segment paths are the gateway prefix itself; multi-segment paths
|
||||
// drop the trailing segment (typically a service endpoint).
|
||||
const prefixPath = lastSlash === 0 ? normalizedPath : normalizedPath.slice(0, lastSlash);
|
||||
const prefixedUrl = new URL(
|
||||
".well-known/oauth-authorization-server",
|
||||
`${authorizationUrl.origin}${prefixPath}/`,
|
||||
).toString();
|
||||
const prefixedEndpoint = await this.#tryWellKnownForRegistration(prefixedUrl);
|
||||
if (prefixedEndpoint) return prefixedEndpoint;
|
||||
|
||||
// RFC 8414 §3.1 path-ful issuer form: /.well-known/oauth-authorization-server/<path>.
|
||||
const pathfulUrl = new URL(
|
||||
`/.well-known/oauth-authorization-server${normalizedPath}`,
|
||||
authorizationUrl.origin,
|
||||
).toString();
|
||||
return await this.#tryWellKnownForRegistration(pathfulUrl);
|
||||
}
|
||||
|
||||
async #tryWellKnownForRegistration(wellKnownUrl: string): Promise<string | null> {
|
||||
try {
|
||||
const response = await this.#fetch(wellKnownUrl, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: this.ctrl.signal,
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const metadata = (await response.json()) as { registration_endpoint?: string };
|
||||
if (metadata.registration_endpoint && metadata.registration_endpoint.trim() !== "") {
|
||||
return metadata.registration_endpoint;
|
||||
}
|
||||
} catch {
|
||||
// Ignore fetch/parse failures.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async #assertClientIdNotRequired(authorizationUrl: string): Promise<void> {
|
||||
try {
|
||||
const response = await this.#fetch(authorizationUrl, {
|
||||
|
||||
@@ -28,6 +28,12 @@ function mockProviderTokenEndpoint(onBody: (body: string) => void): FetchImpl {
|
||||
function mockFigmaRegistration(onRegistration: (payload: Record<string, unknown>) => void): FetchImpl {
|
||||
return async (input, init) => {
|
||||
const url = String(input);
|
||||
if (url === "https://www.figma.com/.well-known/oauth-authorization-server") {
|
||||
return new Response(JSON.stringify({ registration_endpoint: "https://www.figma.com/oauth/register" }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
if (url === "https://www.figma.com/oauth/register") {
|
||||
onRegistration(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
||||
return new Response(
|
||||
@@ -62,7 +68,6 @@ describe("mcp oauth flow", () => {
|
||||
{
|
||||
authorizationUrl: "https://www.figma.com/oauth/mcp",
|
||||
tokenUrl: "https://api.figma.com/v1/oauth/token",
|
||||
registrationUrl: "https://www.figma.com/oauth/register",
|
||||
fetch: mockFigmaRegistration(payload => {
|
||||
registrationPayload = payload;
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user