Skip to main content
Glama

Google Drive MCP Server

by ducla5
oauth-manager.ts6.59 kB
import { google } from 'googleapis'; import { OAuth2Client } from 'google-auth-library'; import fs from 'fs/promises'; import path from 'path'; import { AuthManager, AuthCredentials, TokenInfo } from '../types/auth.js'; /** * OAuth 2.0 Authentication Manager for Google Drive API * Handles authentication flow, token storage, and refresh logic */ export class OAuthManager implements AuthManager { private oauth2Client: OAuth2Client; private tokenInfo: TokenInfo | null = null; private tokenFilePath: string; constructor(credentials: AuthCredentials, tokenStoragePath: string = './tokens') { this.tokenFilePath = path.join(tokenStoragePath, 'google-drive-tokens.json'); this.oauth2Client = new google.auth.OAuth2( credentials.clientId, credentials.clientSecret, credentials.redirectUri ); // Set up token refresh callback this.oauth2Client.on('tokens', (tokens: any) => { if (tokens.refresh_token) { this.tokenInfo = { accessToken: tokens.access_token!, refreshToken: tokens.refresh_token, expiryDate: tokens.expiry_date || Date.now() + 3600000, // 1 hour default scope: tokens.scope ? tokens.scope.split(' ') : [] }; this.saveTokens(); } }); } /** * Authenticate with Google Drive API using OAuth 2.0 * Returns true if authentication successful, false otherwise */ async authenticate(): Promise<boolean> { try { // Try to load existing tokens first await this.loadTokens(); if (this.tokenInfo && this.isTokenValid()) { this.oauth2Client.setCredentials({ access_token: this.tokenInfo.accessToken, refresh_token: this.tokenInfo.refreshToken, expiry_date: this.tokenInfo.expiryDate }); return true; } // If no valid tokens, need to go through OAuth flow if (this.tokenInfo?.refreshToken) { // Try to refresh existing token return await this.refreshToken(); } // No tokens available, need initial authentication throw new Error('No valid tokens found. Initial OAuth flow required.'); } catch (error) { console.error('Authentication failed:', error); return false; } } /** * Refresh the access token using refresh token */ async refreshToken(): Promise<boolean> { try { if (!this.tokenInfo?.refreshToken) { throw new Error('No refresh token available'); } this.oauth2Client.setCredentials({ refresh_token: this.tokenInfo.refreshToken }); const { credentials } = await this.oauth2Client.refreshAccessToken(); if (credentials.access_token) { this.tokenInfo = { accessToken: credentials.access_token, refreshToken: credentials.refresh_token || this.tokenInfo.refreshToken, expiryDate: credentials.expiry_date || Date.now() + 3600000, scope: this.tokenInfo.scope }; this.oauth2Client.setCredentials({ access_token: this.tokenInfo.accessToken, refresh_token: this.tokenInfo.refreshToken, expiry_date: this.tokenInfo.expiryDate }); await this.saveTokens(); return true; } return false; } catch (error) { console.error('Token refresh failed:', error); return false; } } /** * Get current access token */ getAccessToken(): string { if (!this.tokenInfo?.accessToken) { throw new Error('No access token available. Authentication required.'); } return this.tokenInfo.accessToken; } /** * Check if currently authenticated with valid token */ isAuthenticated(): boolean { return this.tokenInfo !== null && this.isTokenValid(); } /** * Get OAuth2 client instance for Google APIs */ getOAuth2Client(): OAuth2Client { return this.oauth2Client; } /** * Generate authorization URL for initial OAuth flow */ getAuthUrl(): string { const scopes = [ 'https://www.googleapis.com/auth/drive.readonly', 'https://www.googleapis.com/auth/documents.readonly' ]; return this.oauth2Client.generateAuthUrl({ access_type: 'offline', scope: scopes, prompt: 'consent' // Force consent to get refresh token }); } /** * Exchange authorization code for tokens */ async exchangeCodeForTokens(code: string): Promise<boolean> { try { const { tokens } = await this.oauth2Client.getToken(code); if (tokens.access_token && tokens.refresh_token) { this.tokenInfo = { accessToken: tokens.access_token, refreshToken: tokens.refresh_token, expiryDate: tokens.expiry_date || Date.now() + 3600000, scope: tokens.scope ? tokens.scope.split(' ') : [] }; this.oauth2Client.setCredentials(tokens); await this.saveTokens(); return true; } return false; } catch (error) { console.error('Code exchange failed:', error); return false; } } /** * Check if current token is valid (not expired) */ private isTokenValid(): boolean { if (!this.tokenInfo) return false; // Add 5 minute buffer before expiry const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds return Date.now() < (this.tokenInfo.expiryDate - bufferTime); } /** * Load tokens from storage */ private async loadTokens(): Promise<void> { try { const tokenData = await fs.readFile(this.tokenFilePath, 'utf-8'); this.tokenInfo = JSON.parse(tokenData); } catch (error) { // File doesn't exist or is invalid, that's okay this.tokenInfo = null; } } /** * Save tokens to storage */ private async saveTokens(): Promise<void> { try { if (!this.tokenInfo) return; // Ensure directory exists const tokenDir = path.dirname(this.tokenFilePath); await fs.mkdir(tokenDir, { recursive: true }); await fs.writeFile( this.tokenFilePath, JSON.stringify(this.tokenInfo, null, 2), { mode: 0o600 } // Restrict file permissions for security ); } catch (error) { console.error('Failed to save tokens:', error); } } /** * Clear stored tokens (for logout) */ async clearTokens(): Promise<void> { try { await fs.unlink(this.tokenFilePath); this.tokenInfo = null; this.oauth2Client.setCredentials({}); } catch (error) { // File might not exist, that's okay } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ducla5/gdriver-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server