mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
Bug: add resource parameter to MCP OAuth Flow (#4981)
Co-authored-by: Your Name <you@example.com>
This commit is contained in:
@@ -151,6 +151,7 @@ describe('MCPOAuthProvider', () => {
|
|||||||
expect.objectContaining({ accessToken: 'access_token_123' }),
|
expect.objectContaining({ accessToken: 'access_token_123' }),
|
||||||
'test-client-id',
|
'test-client-id',
|
||||||
'https://auth.example.com/token',
|
'https://auth.example.com/token',
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -551,6 +552,7 @@ describe('MCPOAuthProvider', () => {
|
|||||||
expect.objectContaining({ accessToken: 'new_access_token' }),
|
expect.objectContaining({ accessToken: 'new_access_token' }),
|
||||||
'test-client-id',
|
'test-client-id',
|
||||||
'https://auth.example.com/token',
|
'https://auth.example.com/token',
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -272,11 +272,13 @@ export class MCPOAuthProvider {
|
|||||||
*
|
*
|
||||||
* @param config OAuth configuration
|
* @param config OAuth configuration
|
||||||
* @param pkceParams PKCE parameters
|
* @param pkceParams PKCE parameters
|
||||||
|
* @param mcpServerUrl The MCP server URL to use as the resource parameter
|
||||||
* @returns The authorization URL
|
* @returns The authorization URL
|
||||||
*/
|
*/
|
||||||
private static buildAuthorizationUrl(
|
private static buildAuthorizationUrl(
|
||||||
config: MCPOAuthConfig,
|
config: MCPOAuthConfig,
|
||||||
pkceParams: PKCEParams,
|
pkceParams: PKCEParams,
|
||||||
|
mcpServerUrl?: string,
|
||||||
): string {
|
): string {
|
||||||
const redirectUri =
|
const redirectUri =
|
||||||
config.redirectUri ||
|
config.redirectUri ||
|
||||||
@@ -296,10 +298,15 @@ export class MCPOAuthProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add resource parameter for MCP OAuth spec compliance
|
// Add resource parameter for MCP OAuth spec compliance
|
||||||
params.append(
|
// Use the MCP server URL if provided, otherwise fall back to authorization URL
|
||||||
'resource',
|
const resourceUrl = mcpServerUrl || config.authorizationUrl!;
|
||||||
OAuthUtils.buildResourceParameter(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()}`;
|
return `${config.authorizationUrl}?${params.toString()}`;
|
||||||
}
|
}
|
||||||
@@ -310,12 +317,14 @@ export class MCPOAuthProvider {
|
|||||||
* @param config OAuth configuration
|
* @param config OAuth configuration
|
||||||
* @param code Authorization code
|
* @param code Authorization code
|
||||||
* @param codeVerifier PKCE code verifier
|
* @param codeVerifier PKCE code verifier
|
||||||
|
* @param mcpServerUrl The MCP server URL to use as the resource parameter
|
||||||
* @returns The token response
|
* @returns The token response
|
||||||
*/
|
*/
|
||||||
private static async exchangeCodeForToken(
|
private static async exchangeCodeForToken(
|
||||||
config: MCPOAuthConfig,
|
config: MCPOAuthConfig,
|
||||||
code: string,
|
code: string,
|
||||||
codeVerifier: string,
|
codeVerifier: string,
|
||||||
|
mcpServerUrl?: string,
|
||||||
): Promise<OAuthTokenResponse> {
|
): Promise<OAuthTokenResponse> {
|
||||||
const redirectUri =
|
const redirectUri =
|
||||||
config.redirectUri ||
|
config.redirectUri ||
|
||||||
@@ -334,10 +343,15 @@ export class MCPOAuthProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add resource parameter for MCP OAuth spec compliance
|
// Add resource parameter for MCP OAuth spec compliance
|
||||||
params.append(
|
// Use the MCP server URL if provided, otherwise fall back to token URL
|
||||||
'resource',
|
const resourceUrl = mcpServerUrl || config.tokenUrl!;
|
||||||
OAuthUtils.buildResourceParameter(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!, {
|
const response = await fetch(config.tokenUrl!, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -362,12 +376,15 @@ export class MCPOAuthProvider {
|
|||||||
*
|
*
|
||||||
* @param config OAuth configuration
|
* @param config OAuth configuration
|
||||||
* @param refreshToken The refresh token
|
* @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
|
* @returns The new token response
|
||||||
*/
|
*/
|
||||||
static async refreshAccessToken(
|
static async refreshAccessToken(
|
||||||
config: MCPOAuthConfig,
|
config: MCPOAuthConfig,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
tokenUrl: string,
|
tokenUrl: string,
|
||||||
|
mcpServerUrl?: string,
|
||||||
): Promise<OAuthTokenResponse> {
|
): Promise<OAuthTokenResponse> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
@@ -384,7 +401,15 @@ export class MCPOAuthProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add resource parameter for MCP OAuth spec compliance
|
// 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, {
|
const response = await fetch(tokenUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -534,7 +559,11 @@ export class MCPOAuthProvider {
|
|||||||
const pkceParams = this.generatePKCEParams();
|
const pkceParams = this.generatePKCEParams();
|
||||||
|
|
||||||
// Build authorization URL
|
// 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('\nOpening browser for OAuth authentication...');
|
||||||
console.log('If the browser does not open, please visit:');
|
console.log('If the browser does not open, please visit:');
|
||||||
@@ -584,6 +613,7 @@ export class MCPOAuthProvider {
|
|||||||
config,
|
config,
|
||||||
code,
|
code,
|
||||||
pkceParams.codeVerifier,
|
pkceParams.codeVerifier,
|
||||||
|
mcpServerUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Convert to our token format
|
// Convert to our token format
|
||||||
@@ -605,6 +635,7 @@ export class MCPOAuthProvider {
|
|||||||
token,
|
token,
|
||||||
config.clientId,
|
config.clientId,
|
||||||
config.tokenUrl,
|
config.tokenUrl,
|
||||||
|
mcpServerUrl,
|
||||||
);
|
);
|
||||||
console.log('Authentication successful! Token saved.');
|
console.log('Authentication successful! Token saved.');
|
||||||
|
|
||||||
@@ -664,6 +695,7 @@ export class MCPOAuthProvider {
|
|||||||
config,
|
config,
|
||||||
token.refreshToken,
|
token.refreshToken,
|
||||||
credentials.tokenUrl,
|
credentials.tokenUrl,
|
||||||
|
credentials.mcpServerUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update stored token
|
// Update stored token
|
||||||
@@ -683,6 +715,7 @@ export class MCPOAuthProvider {
|
|||||||
newToken,
|
newToken,
|
||||||
config.clientId,
|
config.clientId,
|
||||||
credentials.tokenUrl,
|
credentials.tokenUrl,
|
||||||
|
credentials.mcpServerUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
return newToken.accessToken;
|
return newToken.accessToken;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface MCPOAuthCredentials {
|
|||||||
token: MCPOAuthToken;
|
token: MCPOAuthToken;
|
||||||
clientId?: string;
|
clientId?: string;
|
||||||
tokenUrl?: string;
|
tokenUrl?: string;
|
||||||
|
mcpServerUrl?: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,12 +92,14 @@ export class MCPOAuthTokenStorage {
|
|||||||
* @param token The OAuth token to save
|
* @param token The OAuth token to save
|
||||||
* @param clientId Optional client ID used for this token
|
* @param clientId Optional client ID used for this token
|
||||||
* @param tokenUrl Optional token URL used for this token
|
* @param tokenUrl Optional token URL used for this token
|
||||||
|
* @param mcpServerUrl Optional MCP server URL
|
||||||
*/
|
*/
|
||||||
static async saveToken(
|
static async saveToken(
|
||||||
serverName: string,
|
serverName: string,
|
||||||
token: MCPOAuthToken,
|
token: MCPOAuthToken,
|
||||||
clientId?: string,
|
clientId?: string,
|
||||||
tokenUrl?: string,
|
tokenUrl?: string,
|
||||||
|
mcpServerUrl?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.ensureConfigDir();
|
await this.ensureConfigDir();
|
||||||
|
|
||||||
@@ -107,6 +110,7 @@ export class MCPOAuthTokenStorage {
|
|||||||
token,
|
token,
|
||||||
clientId,
|
clientId,
|
||||||
tokenUrl,
|
tokenUrl,
|
||||||
|
mcpServerUrl,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user