google-cloud-mcp
Version:
Model Context Protocol server for Google Cloud services
201 lines • 8.67 kB
JavaScript
/**
* Authentication utilities for Google Cloud services
*/
import { GoogleAuth } from 'google-auth-library';
import fs from 'fs';
import path from 'path';
import { configManager } from './config.js';
// Global auth client that can be reused
// Exported to allow checking auth status from other modules
export let authClient = null;
/**
* Initialises Google Cloud authentication using either:
* 1. GOOGLE_APPLICATION_CREDENTIALS environment variable pointing to a service account file
* 2. GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY environment variables
*
* This function supports lazy loading - it won't fail if credentials aren't available yet,
* allowing the server to start without authentication and defer it until needed.
*
* Authentication is required for operation but can be lazy-loaded when first needed rather than
* at startup, which helps prevent timeouts with Smithery.
*
* @param requireAuth If true, will throw an error if authentication fails. If false, will return null.
* @returns Promise resolving to the authenticated GoogleAuth client or null if authentication isn't available
*/
export async function initGoogleAuth(requireAuth = false) {
// Check if we're in lazy loading mode
const lazyAuth = process.env.LAZY_AUTH !== 'false'; // Default to true if not set
// If we're in lazy loading mode and not explicitly requiring auth, defer authentication
if (lazyAuth && !requireAuth) {
console.log('Lazy authentication enabled - deferring authentication until needed');
}
try {
// If we already have an auth client, return it
if (authClient) {
return authClient;
}
// Check if we need to create a temporary credentials file from environment variables
if (process.env.GOOGLE_CLIENT_EMAIL && process.env.GOOGLE_PRIVATE_KEY) {
try {
// Create a temporary credentials file without blocking server startup
const tempDir = path.join(process.cwd(), '.temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const credentials = {
type: 'service_account',
project_id: process.env.GOOGLE_CLOUD_PROJECT || '',
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n'),
client_email: process.env.GOOGLE_CLIENT_EMAIL,
client_id: '',
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://oauth2.googleapis.com/token',
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
client_x509_cert_url: `https://www.googleapis.com/robot/v1/metadata/x509/${encodeURIComponent(process.env.GOOGLE_CLIENT_EMAIL)}`
};
const tempCredentialsPath = path.join(tempDir, 'temp-credentials.json');
fs.writeFileSync(tempCredentialsPath, JSON.stringify(credentials));
process.env.GOOGLE_APPLICATION_CREDENTIALS = tempCredentialsPath;
}
catch (error) {
console.error(`Warning: Failed to create temporary credentials file: ${error instanceof Error ? error.message : String(error)}`);
if (requireAuth) {
throw new Error(`Failed to create temporary credentials file: ${error instanceof Error ? error.message : String(error)}`);
}
return null;
}
}
// Check if GOOGLE_APPLICATION_CREDENTIALS is set
if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
if (requireAuth) {
throw new Error('Google Cloud authentication not configured. Please set GOOGLE_APPLICATION_CREDENTIALS ' +
'or both GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY environment variables.');
}
return null;
}
// Create the auth client without verifying it immediately
authClient = new GoogleAuth({
scopes: [
'https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/spanner.data',
'https://www.googleapis.com/auth/logging.read',
'https://www.googleapis.com/auth/monitoring.read'
]
});
// Only verify the token if explicitly required
// This prevents blocking operations during server startup
if (requireAuth) {
try {
const client = await authClient.getClient();
await client.getAccessToken();
}
catch (verifyError) {
authClient = null;
throw verifyError;
}
}
return authClient;
}
catch (error) {
// Log error but don't crash the server unless authentication is required
console.error(`Auth error: ${error instanceof Error ? error.message : String(error)}`);
if (requireAuth) {
throw error;
}
return null;
}
}
/**
* Gets the project ID from configuration, environment variables, or from the authenticated client
*
* @param requireAuth If true, will throw an error if project ID can't be determined. If false, will return a default value.
* @returns Promise resolving to the Google Cloud project ID or a default value if not available
*/
export async function getProjectId(requireAuth = true) {
try {
// First check environment variable (fastest method)
if (process.env.GOOGLE_CLOUD_PROJECT) {
try {
// Try to store in config but don't block on it
configManager.initialize().then(() => {
configManager.setDefaultProjectId(process.env.GOOGLE_CLOUD_PROJECT);
}).catch(() => {
// Ignore config errors
});
}
catch (configError) {
// Ignore config errors
}
return process.env.GOOGLE_CLOUD_PROJECT;
}
// Next check if we have a configured default project ID
try {
await configManager.initialize();
const configuredProjectId = configManager.getDefaultProjectId();
if (configuredProjectId) {
return configuredProjectId;
}
}
catch (configError) {
console.error(`Config error: ${configError instanceof Error ? configError.message : String(configError)}`);
// Continue to next method
}
// Fall back to getting it from auth client
try {
const auth = await initGoogleAuth(requireAuth);
if (!auth) {
if (requireAuth) {
throw new Error('Google Cloud authentication not available. Please configure authentication to access project ID.');
}
return 'unknown-project';
}
const projectId = await auth.getProjectId();
if (!projectId) {
if (requireAuth) {
throw new Error('Could not determine Google Cloud project ID. Please set a default project ID using the set-project-id tool.');
}
return 'unknown-project';
}
// Store this in config for future use (don't block on it)
configManager.initialize().then(() => {
configManager.setDefaultProjectId(projectId);
}).catch(() => {
// Ignore config errors
});
return projectId;
}
catch (authError) {
if (requireAuth) {
throw authError;
}
return 'unknown-project';
}
}
catch (error) {
console.error(`Project ID error: ${error instanceof Error ? error.message : String(error)}`);
if (requireAuth) {
throw error;
}
return 'unknown-project';
}
}
/**
* Sets the default project ID to use for all Google Cloud operations
*
* @param projectId The project ID to set as default
*/
export async function setProjectId(projectId) {
await configManager.initialize();
await configManager.setDefaultProjectId(projectId);
// Default project ID set
}
/**
* Gets the list of recently used project IDs
*
* @returns Array of recent project IDs
*/
export async function getRecentProjectIds() {
await configManager.initialize();
return configManager.getRecentProjectIds();
}
//# sourceMappingURL=auth.js.map