A simple, robust, and secure Google OAuth 2.0 authentication package for MCP (Model Context Protocol) servers. Provides automated token management, secure local storage, and automatic token refresh with minimal configuration.
- Simple Setup: 1-2 lines for basic authentication
- Automatic Token Management: Handles token refresh with 5-minute buffer
- Secure Storage: Tokens stored with 0o600 permissions (owner read/write only)
- Robust Error Handling: Typed errors with retry logic
- TypeScript Support: Full TypeScript support with strict typing
- Minimal Dependencies: Only essential Google auth libraries
- Extensible: Pluggable storage interface for future remote storage
npm install google-auth-mcpbun install google-auth-mcp- Node.js >= 22 (ESM support required)
- Google Cloud Console Setup:
- Create a project in Google Cloud Console
- Enable the APIs you want to use (e.g., YouTube API, Drive API)
- Create OAuth 2.0 Client ID credentials for "Desktop application"
- Download the
credentials.jsonfile
Place your credentials.json file in your project root, then:
import { createAuth } from 'google-auth-mcp';
// Create auth instance
const auth = createAuth({
scopes: [
'https://www.googleapis.com/auth/youtube.readonly',
'https://www.googleapis.com/auth/drive.metadata.readonly'
]
});
// Get authorization header for API requests
const headers = await auth.getAuthHeader();
// Returns: { Authorization: 'Bearer ya29.a0...' }
// Use with fetch
const response = await fetch('https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true', {
headers: await auth.getAuthHeader()
});import { createAuth, GoogleAuthMCP } from 'google-auth-mcp';
class MyMCPServer {
private auth: GoogleAuthMCP;
constructor() {
this.auth = createAuth({
scopes: ['https://www.googleapis.com/auth/youtube.readonly']
});
}
async handleYouTubeRequest() {
try {
// The first call will trigger browser-based OAuth flow
const headers = await this.auth.getAuthHeader();
const response = await fetch('https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true', {
headers
});
return await response.json();
} catch (error) {
console.error('YouTube API request failed:', error);
throw error;
}
}
}Creates a new authentication instance.
interface AuthOptions {
scopes: string[]; // Required: OAuth scopes
credentialsPath?: string; // Default: './credentials.json'
tokenPath?: string; // Default: './tokens/default.token.json'
storage?: Storage; // Custom storage implementation
accountId?: string; // For future multi-account support
logger?: Pick<Console, 'log' | 'error'>; // Default: console
}getAuthHeader(): Promise<{ Authorization: string }>- Get authorization header for HTTP requestsgetAccessToken(): Promise<string>- Get raw access tokengetClient(): Promise<OAuth2Client>- Get the underlying OAuth2ClientisAuthenticated(): Promise<boolean>- Check if currently authenticatedsignIn(): Promise<void>- Force re-authenticationsignOut(): Promise<void>- Sign out and revoke tokens
const auth = createAuth({
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
credentialsPath: './config/google-credentials.json',
tokenPath: './config/tokens/drive.token.json'
});import { Storage, TokenData, CredentialsJson } from 'google-auth-mcp';
class DatabaseStorage implements Storage {
async readCredentials(): Promise<CredentialsJson> {
// Read from database
}
async readToken(accountId?: string): Promise<TokenData | null> {
// Read from database
}
async writeToken(token: TokenData, accountId?: string): Promise<void> {
// Write to database
}
async deleteToken(accountId?: string): Promise<void> {
// Delete from database
}
}
const auth = createAuth({
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
storage: new DatabaseStorage()
});- First Time: Opens browser for Google OAuth consent, saves tokens locally
- Subsequent Calls: Uses saved tokens, automatically refreshes when needed
- Token Refresh: Handles automatically with exponential backoff retry logic
- Error Recovery: Clear error messages for common issues
import {
AuthenticationError,
TokenExpiredError,
StorageError
} from 'google-auth-mcp';
try {
const headers = await auth.getAuthHeader();
} catch (error) {
if (error instanceof TokenExpiredError) {
console.log('Token expired, trying to sign in again...');
await auth.signIn();
} else if (error instanceof StorageError) {
console.log('Storage issue:', error.message);
} else if (error instanceof AuthenticationError) {
console.log('Authentication failed:', error.message);
}
}- Secure File Permissions: Token files created with 0o600 (owner read/write only)
- No Secret Logging: Tokens and secrets never logged
- Path Safety: Uses
path.resolve()to prevent traversal attacks - Automatic Refresh: Tokens refreshed 5 minutes before expiry
// YouTube
'https://www.googleapis.com/auth/youtube.readonly'
'https://www.googleapis.com/auth/youtube'
// Google Drive
'https://www.googleapis.com/auth/drive.readonly'
'https://www.googleapis.com/auth/drive.file'
'https://www.googleapis.com/auth/drive'
// Gmail
'https://www.googleapis.com/auth/gmail.readonly'
'https://www.googleapis.com/auth/gmail.send'
// Calendar
'https://www.googleapis.com/auth/calendar.readonly'
'https://www.googleapis.com/auth/calendar'This typically happens when you've previously authorized the application. Solutions:
- Delete existing token files:
rm -rf ./tokens/ - Revoke access in Google Account Settings
- Re-run your application
Ensure credentials.json is in the correct location:
ls -la credentials.jsonVerify your credentials file has the correct structure:
{
"installed": {
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"redirect_uris": ["http://localhost"]
}
}your-project/
├── credentials.json # Google OAuth credentials
├── tokens/
│ └── default.token.json # Saved auth tokens (auto-created)
└── your-code.js
- Node.js >= 18 (ESM support)
- Google Cloud Console project with OAuth 2.0 credentials
- Internet access for initial authentication and token refresh
MIT
Issues and pull requests welcome at GitHub repository