Bug: add resource parameter to MCP OAuth Flow (#4981)

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
Brian Ray 2025-07-27 14:09:45 -04:00 committed by GitHub
parent 576cebc928
commit c45c14ee0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 49 additions and 10 deletions

View File

@ -151,6 +151,7 @@ describe('MCPOAuthProvider', () => {
expect.objectContaining({ accessToken: 'access_token_123' }),
'test-client-id',
'https://auth.example.com/token',
undefined,
);
});
@ -551,6 +552,7 @@ describe('MCPOAuthProvider', () => {
expect.objectContaining({ accessToken: 'new_access_token' }),
'test-client-id',
'https://auth.example.com/token',
undefined,
);
});

View File

@ -272,11 +272,13 @@ export class MCPOAuthProvider {
*
* @param config OAuth configuration
* @param pkceParams PKCE parameters
* @param mcpServerUrl The MCP server URL to use as the resource parameter
* @returns The authorization URL
*/
private static buildAuthorizationUrl(
config: MCPOAuthConfig,
pkceParams: PKCEParams,
mcpServerUrl?: string,
): string {
const redirectUri =
config.redirectUri ||
@ -296,10 +298,15 @@ export class MCPOAuthProvider {
}
// Add resource parameter for MCP OAuth spec compliance
params.append(
'resource',
OAuthUtils.buildResourceParameter(config.authorizationUrl!),
);
// Use the MCP server URL if provided, otherwise fall back to authorization URL
const resourceUrl = mcpServerUrl || config.authorizationUrl!;
try {
params.append('resource', OAuthUtils.buildResourceParameter(resourceUrl));
} catch (error) {
throw new Error(
`Invalid resource URL: "${resourceUrl}". ${getErrorMessage(error)}`,
);
}
return `${config.authorizationUrl}?${params.toString()}`;
}
@ -310,12 +317,14 @@ export class MCPOAuthProvider {
* @param config OAuth configuration
* @param code Authorization code
* @param codeVerifier PKCE code verifier
* @param mcpServerUrl The MCP server URL to use as the resource parameter
* @returns The token response
*/
private static async exchangeCodeForToken(
config: MCPOAuthConfig,
code: string,
codeVerifier: string,
mcpServerUrl?: string,
): Promise<OAuthTokenResponse> {
const redirectUri =
config.redirectUri ||
@ -334,10 +343,15 @@ export class MCPOAuthProvider {
}
// Add resource parameter for MCP OAuth spec compliance
params.append(
'resource',
OAuthUtils.buildResourceParameter(config.tokenUrl!),
);
// Use the MCP server URL if provided, otherwise fall back to token URL
const resourceUrl = mcpServerUrl || config.tokenUrl!;
try {
params.append('resource', OAuthUtils.buildResourceParameter(resourceUrl));
} catch (error) {
throw new Error(
`Invalid resource URL: "${resourceUrl}". ${getErrorMessage(error)}`,
);
}
const response = await fetch(config.tokenUrl!, {
method: 'POST',
@ -362,12 +376,15 @@ export class MCPOAuthProvider {
*
* @param config OAuth configuration
* @param refreshToken The refresh token
* @param tokenUrl The token endpoint URL
* @param mcpServerUrl The MCP server URL to use as the resource parameter
* @returns The new token response
*/
static async refreshAccessToken(
config: MCPOAuthConfig,
refreshToken: string,
tokenUrl: string,
mcpServerUrl?: string,
): Promise<OAuthTokenResponse> {
const params = new URLSearchParams({
grant_type: 'refresh_token',
@ -384,7 +401,15 @@ export class MCPOAuthProvider {
}
// Add resource parameter for MCP OAuth spec compliance
params.append('resource', OAuthUtils.buildResourceParameter(tokenUrl));
// Use the MCP server URL if provided, otherwise fall back to token URL
const resourceUrl = mcpServerUrl || tokenUrl;
try {
params.append('resource', OAuthUtils.buildResourceParameter(resourceUrl));
} catch (error) {
throw new Error(
`Invalid resource URL: "${resourceUrl}". ${getErrorMessage(error)}`,
);
}
const response = await fetch(tokenUrl, {
method: 'POST',
@ -534,7 +559,11 @@ export class MCPOAuthProvider {
const pkceParams = this.generatePKCEParams();
// Build authorization URL
const authUrl = this.buildAuthorizationUrl(config, pkceParams);
const authUrl = this.buildAuthorizationUrl(
config,
pkceParams,
mcpServerUrl,
);
console.log('\nOpening browser for OAuth authentication...');
console.log('If the browser does not open, please visit:');
@ -584,6 +613,7 @@ export class MCPOAuthProvider {
config,
code,
pkceParams.codeVerifier,
mcpServerUrl,
);
// Convert to our token format
@ -605,6 +635,7 @@ export class MCPOAuthProvider {
token,
config.clientId,
config.tokenUrl,
mcpServerUrl,
);
console.log('Authentication successful! Token saved.');
@ -664,6 +695,7 @@ export class MCPOAuthProvider {
config,
token.refreshToken,
credentials.tokenUrl,
credentials.mcpServerUrl,
);
// Update stored token
@ -683,6 +715,7 @@ export class MCPOAuthProvider {
newToken,
config.clientId,
credentials.tokenUrl,
credentials.mcpServerUrl,
);
return newToken.accessToken;

View File

@ -28,6 +28,7 @@ export interface MCPOAuthCredentials {
token: MCPOAuthToken;
clientId?: string;
tokenUrl?: string;
mcpServerUrl?: string;
updatedAt: number;
}
@ -91,12 +92,14 @@ export class MCPOAuthTokenStorage {
* @param token The OAuth token to save
* @param clientId Optional client ID used for this token
* @param tokenUrl Optional token URL used for this token
* @param mcpServerUrl Optional MCP server URL
*/
static async saveToken(
serverName: string,
token: MCPOAuthToken,
clientId?: string,
tokenUrl?: string,
mcpServerUrl?: string,
): Promise<void> {
await this.ensureConfigDir();
@ -107,6 +110,7 @@ export class MCPOAuthTokenStorage {
token,
clientId,
tokenUrl,
mcpServerUrl,
updatedAt: Date.now(),
};