diff options
Diffstat (limited to 'packages/core/src/mcp')
| -rw-r--r-- | packages/core/src/mcp/google-auth-provider.test.ts | 67 | ||||
| -rw-r--r-- | packages/core/src/mcp/google-auth-provider.ts | 83 |
2 files changed, 150 insertions, 0 deletions
diff --git a/packages/core/src/mcp/google-auth-provider.test.ts b/packages/core/src/mcp/google-auth-provider.test.ts new file mode 100644 index 00000000..f481b9e2 --- /dev/null +++ b/packages/core/src/mcp/google-auth-provider.test.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { GoogleAuth } from 'google-auth-library'; +import { GoogleCredentialProvider } from './google-auth-provider.js'; +import { vi, describe, beforeEach, it, expect, Mock } from 'vitest'; +import { MCPServerConfig } from '../config/config.js'; + +vi.mock('google-auth-library'); + +describe('GoogleCredentialProvider', () => { + it('should throw an error if no scopes are provided', () => { + expect(() => new GoogleCredentialProvider()).toThrow( + 'Scopes must be provided in the oauth config for Google Credentials provider', + ); + }); + + it('should use scopes from the config if provided', () => { + const config = { + oauth: { + scopes: ['scope1', 'scope2'], + }, + } as MCPServerConfig; + new GoogleCredentialProvider(config); + expect(GoogleAuth).toHaveBeenCalledWith({ + scopes: ['scope1', 'scope2'], + }); + }); + + describe('with provider instance', () => { + let provider: GoogleCredentialProvider; + + beforeEach(() => { + const config = { + oauth: { + scopes: ['scope1', 'scope2'], + }, + } as MCPServerConfig; + provider = new GoogleCredentialProvider(config); + vi.clearAllMocks(); + }); + + it('should return credentials', async () => { + const mockClient = { + getAccessToken: vi.fn().mockResolvedValue({ token: 'test-token' }), + }; + (GoogleAuth.prototype.getClient as Mock).mockResolvedValue(mockClient); + + const credentials = await provider.tokens(); + + expect(credentials?.access_token).toBe('test-token'); + }); + + it('should return undefined if access token is not available', async () => { + const mockClient = { + getAccessToken: vi.fn().mockResolvedValue({ token: null }), + }; + (GoogleAuth.prototype.getClient as Mock).mockResolvedValue(mockClient); + + const credentials = await provider.tokens(); + expect(credentials).toBeUndefined(); + }); + }); +}); diff --git a/packages/core/src/mcp/google-auth-provider.ts b/packages/core/src/mcp/google-auth-provider.ts new file mode 100644 index 00000000..88cd086b --- /dev/null +++ b/packages/core/src/mcp/google-auth-provider.ts @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'; +import { + OAuthClientInformation, + OAuthClientInformationFull, + OAuthClientMetadata, + OAuthTokens, +} from '@modelcontextprotocol/sdk/shared/auth.js'; +import { GoogleAuth } from 'google-auth-library'; +import { MCPServerConfig } from '../config/config.js'; + +export class GoogleCredentialProvider implements OAuthClientProvider { + private readonly auth: GoogleAuth; + + // Properties required by OAuthClientProvider, with no-op values + readonly redirectUrl = ''; + readonly clientMetadata: OAuthClientMetadata = { + client_name: 'Gemini CLI (Google ADC)', + redirect_uris: [], + grant_types: [], + response_types: [], + token_endpoint_auth_method: 'none', + }; + private _clientInformation?: OAuthClientInformationFull; + + constructor(private readonly config?: MCPServerConfig) { + const scopes = this.config?.oauth?.scopes; + if (!scopes || scopes.length === 0) { + throw new Error( + 'Scopes must be provided in the oauth config for Google Credentials provider', + ); + } + this.auth = new GoogleAuth({ + scopes, + }); + } + + clientInformation(): OAuthClientInformation | undefined { + return this._clientInformation; + } + + saveClientInformation(clientInformation: OAuthClientInformationFull): void { + this._clientInformation = clientInformation; + } + + async tokens(): Promise<OAuthTokens | undefined> { + const client = await this.auth.getClient(); + const accessTokenResponse = await client.getAccessToken(); + + if (!accessTokenResponse.token) { + console.error('Failed to get access token from Google ADC'); + return undefined; + } + + const tokens: OAuthTokens = { + access_token: accessTokenResponse.token, + token_type: 'Bearer', + }; + return tokens; + } + + saveTokens(_tokens: OAuthTokens): void { + // No-op, ADC manages tokens. + } + + redirectToAuthorization(_authorizationUrl: URL): void { + // No-op + } + + saveCodeVerifier(_codeVerifier: string): void { + // No-op + } + + codeVerifier(): string { + // No-op + return ''; + } +} |
