mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-29 06:07:11 +02:00
fix: add origin-only well-known URL fallback for OAuth discovery
Some MCP servers (e.g., eschat.microsoft.com/mcp) serve their
OAuth Protected Resource Metadata at the origin without the path
suffix. Add a third candidate URL that strips the path, matching
VS Code's discovery behavior.
Tried in order:
1. RFC 9728: {origin}/.well-known/{suffix}{path}
2. Appended: {origin}{path}/.well-known/{suffix}
3. Origin-only: {origin}/.well-known/{suffix}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import electron from 'electron';
|
|||||||
|
|
||||||
import type { McpOauthStaticClientConfig } from '@shared/domain/mcpAuth';
|
import type { McpOauthStaticClientConfig } from '@shared/domain/mcpAuth';
|
||||||
|
|
||||||
import { storeToken, buildWellKnownUrl, buildWellKnownUrlFallback, type McpOAuthToken } from './mcpTokenStore';
|
import { storeToken, buildWellKnownUrl, buildWellKnownUrlFallback, buildWellKnownUrlOriginOnly, type McpOAuthToken } from './mcpTokenStore';
|
||||||
|
|
||||||
const { shell } = electron;
|
const { shell } = electron;
|
||||||
|
|
||||||
@@ -228,7 +228,18 @@ interface AuthServerMetadata {
|
|||||||
async function fetchWellKnownMetadata(baseUrl: string, suffix: string): Promise<Record<string, unknown> | undefined> {
|
async function fetchWellKnownMetadata(baseUrl: string, suffix: string): Promise<Record<string, unknown> | undefined> {
|
||||||
const rfcUrl = buildWellKnownUrl(baseUrl, suffix);
|
const rfcUrl = buildWellKnownUrl(baseUrl, suffix);
|
||||||
const fallbackUrl = buildWellKnownUrlFallback(baseUrl, suffix);
|
const fallbackUrl = buildWellKnownUrlFallback(baseUrl, suffix);
|
||||||
const urls = rfcUrl === fallbackUrl ? [rfcUrl] : [rfcUrl, fallbackUrl];
|
const originOnlyUrl = buildWellKnownUrlOriginOnly(baseUrl, suffix);
|
||||||
|
|
||||||
|
// Deduplicate: RFC path, appended fallback, then origin-only (for servers
|
||||||
|
// that serve metadata at the origin without the resource path suffix).
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const urls: string[] = [];
|
||||||
|
for (const url of [rfcUrl, fallbackUrl, originOnlyUrl]) {
|
||||||
|
if (!seen.has(url)) {
|
||||||
|
seen.add(url);
|
||||||
|
urls.push(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -66,3 +66,8 @@ export function buildWellKnownUrlFallback(baseUrl: string, wellKnownSuffix: stri
|
|||||||
const base = baseUrl.replace(/\/+$/, '');
|
const base = baseUrl.replace(/\/+$/, '');
|
||||||
return `${base}/.well-known/${wellKnownSuffix}`;
|
return `${base}/.well-known/${wellKnownSuffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildWellKnownUrlOriginOnly(baseUrl: string, wellKnownSuffix: string): string {
|
||||||
|
const parsed = new URL(baseUrl);
|
||||||
|
return `${parsed.origin}/.well-known/${wellKnownSuffix}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
import { buildWellKnownUrl, buildWellKnownUrlFallback } from '@main/services/mcpTokenStore';
|
import { buildWellKnownUrl, buildWellKnownUrlFallback, buildWellKnownUrlOriginOnly } from '@main/services/mcpTokenStore';
|
||||||
|
|
||||||
describe('buildWellKnownUrl', () => {
|
describe('buildWellKnownUrl', () => {
|
||||||
test('inserts well-known segment after origin for URL with path', () => {
|
test('inserts well-known segment after origin for URL with path', () => {
|
||||||
@@ -50,3 +50,25 @@ describe('buildWellKnownUrlFallback', () => {
|
|||||||
.toBe('https://auth.example.com/.well-known/oauth-authorization-server');
|
.toBe('https://auth.example.com/.well-known/oauth-authorization-server');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('buildWellKnownUrlOriginOnly', () => {
|
||||||
|
test('strips path and returns origin-only well-known URL', () => {
|
||||||
|
expect(buildWellKnownUrlOriginOnly('https://eschat.microsoft.com/mcp', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://eschat.microsoft.com/.well-known/oauth-protected-resource');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles multi-level path', () => {
|
||||||
|
expect(buildWellKnownUrlOriginOnly('https://example.com/v1/mcp/api', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://example.com/.well-known/oauth-protected-resource');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('matches RFC URL when base has no path', () => {
|
||||||
|
expect(buildWellKnownUrlOriginOnly('https://auth.example.com/', 'oauth-authorization-server'))
|
||||||
|
.toBe('https://auth.example.com/.well-known/oauth-authorization-server');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles URL with port', () => {
|
||||||
|
expect(buildWellKnownUrlOriginOnly('https://mcp.example.com:8443/v1/', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://mcp.example.com:8443/.well-known/oauth-protected-resource');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user