mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-04 15:20:18 +01:00
🛜 feat: Support Legacy OAuth Servers without .well-known Metadata (#10917)
Adds support for MCP servers like StackOverflow that use OAuth but don't provide standard discovery metadata at .well-known endpoints. Changes: - Add fallback OAuth endpoints (/authorize, /token, /register) when discoverAuthorizationServerMetadata returns undefined - Add POST fallback in OAuth detection when HEAD returns non-401 (StackOverflow returns 405 for HEAD, 401 for POST) - Detect OAuth requirement from WWW-Authenticate: Bearer header even without resource_metadata URL - Add fallback /token endpoint for token refresh when metadata discovery fails - Add registration_endpoint to OAuthMetadata type This mirrors the MCP SDK's behavior where it gracefully falls back to default OAuth endpoint paths when .well-known metadata isn't available. Tests: - Add unit tests for detectOAuth.ts (POST fallback, Bearer detection) - Add unit tests for handler.ts (fallback metadata, fallback refresh) - Add StackOverflow to integration test servers Fixes OAuth flow for servers that: - Return 405 for HEAD requests (only support POST) - Return 401 with simple "Bearer" in WWW-Authenticate - Don't have .well-known/oauth-authorization-server endpoint - Use standard /authorize, /token, /register paths
This commit is contained in:
parent
4a2de417b6
commit
24c76c6cb9
8 changed files with 556 additions and 35 deletions
|
|
@ -93,10 +93,37 @@ export class MCPOAuthHandler {
|
|||
});
|
||||
|
||||
if (!rawMetadata) {
|
||||
logger.error(
|
||||
`[MCPOAuth] Failed to discover OAuth metadata from ${sanitizeUrlForLogging(authServerUrl)}`,
|
||||
/**
|
||||
* No metadata discovered - create fallback metadata using default OAuth endpoint paths.
|
||||
* This mirrors the MCP SDK's behavior where it falls back to /authorize, /token, /register
|
||||
* when metadata discovery fails (e.g., servers without .well-known endpoints).
|
||||
* See: https://github.com/modelcontextprotocol/sdk/blob/main/src/client/auth.ts
|
||||
*/
|
||||
logger.warn(
|
||||
`[MCPOAuth] No OAuth metadata discovered from ${sanitizeUrlForLogging(authServerUrl)}, using legacy fallback endpoints`,
|
||||
);
|
||||
throw new Error('Failed to discover OAuth metadata');
|
||||
|
||||
const fallbackMetadata: OAuthMetadata = {
|
||||
issuer: authServerUrl.toString(),
|
||||
authorization_endpoint: new URL('/authorize', authServerUrl).toString(),
|
||||
token_endpoint: new URL('/token', authServerUrl).toString(),
|
||||
registration_endpoint: new URL('/register', authServerUrl).toString(),
|
||||
response_types_supported: ['code'],
|
||||
grant_types_supported: ['authorization_code', 'refresh_token'],
|
||||
code_challenge_methods_supported: ['S256', 'plain'],
|
||||
token_endpoint_auth_methods_supported: [
|
||||
'client_secret_basic',
|
||||
'client_secret_post',
|
||||
'none',
|
||||
],
|
||||
};
|
||||
|
||||
logger.debug(`[MCPOAuth] Using fallback metadata:`, fallbackMetadata);
|
||||
return {
|
||||
metadata: fallbackMetadata,
|
||||
resourceMetadata,
|
||||
authServerUrl,
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug(`[MCPOAuth] OAuth metadata discovered successfully`);
|
||||
|
|
@ -562,13 +589,21 @@ export class MCPOAuthHandler {
|
|||
fetchFn: this.createOAuthFetch(oauthHeaders),
|
||||
});
|
||||
if (!oauthMetadata) {
|
||||
throw new Error('Failed to discover OAuth metadata for token refresh');
|
||||
}
|
||||
if (!oauthMetadata.token_endpoint) {
|
||||
/**
|
||||
* No metadata discovered - use fallback /token endpoint.
|
||||
* This mirrors the MCP SDK's behavior for legacy servers without .well-known endpoints.
|
||||
*/
|
||||
logger.warn(
|
||||
`[MCPOAuth] No OAuth metadata discovered for token refresh, using fallback /token endpoint`,
|
||||
);
|
||||
tokenUrl = new URL('/token', metadata.serverUrl).toString();
|
||||
authMethods = ['client_secret_basic', 'client_secret_post', 'none'];
|
||||
} else if (!oauthMetadata.token_endpoint) {
|
||||
throw new Error('No token endpoint found in OAuth metadata');
|
||||
} else {
|
||||
tokenUrl = oauthMetadata.token_endpoint;
|
||||
authMethods = oauthMetadata.token_endpoint_auth_methods_supported;
|
||||
}
|
||||
tokenUrl = oauthMetadata.token_endpoint;
|
||||
authMethods = oauthMetadata.token_endpoint_auth_methods_supported;
|
||||
}
|
||||
|
||||
const body = new URLSearchParams({
|
||||
|
|
@ -741,12 +776,20 @@ export class MCPOAuthHandler {
|
|||
fetchFn: this.createOAuthFetch(oauthHeaders),
|
||||
});
|
||||
|
||||
let tokenUrl: URL;
|
||||
if (!oauthMetadata?.token_endpoint) {
|
||||
throw new Error('No token endpoint found in OAuth metadata');
|
||||
/**
|
||||
* No metadata or token_endpoint discovered - use fallback /token endpoint.
|
||||
* This mirrors the MCP SDK's behavior for legacy servers without .well-known endpoints.
|
||||
*/
|
||||
logger.warn(
|
||||
`[MCPOAuth] No OAuth metadata or token endpoint found, using fallback /token endpoint`,
|
||||
);
|
||||
tokenUrl = new URL('/token', metadata.serverUrl);
|
||||
} else {
|
||||
tokenUrl = new URL(oauthMetadata.token_endpoint);
|
||||
}
|
||||
|
||||
const tokenUrl = new URL(oauthMetadata.token_endpoint);
|
||||
|
||||
const body = new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue