#
tokens: 31089/50000 13/72 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/mohalmah/google-appscript-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .gitattributes
├── .gitignore
├── .gitkeep
├── commands
│   └── tools.js
├── demo
│   └── google app script mcp demo.gif
├── Dockerfile
├── ERROR_HANDLING_ENHANCEMENT.md
├── helpers
│   ├── check-deployments.js
│   ├── check-head-deployment.js
│   ├── convert-to-oauth.js
│   ├── create-proper-webapp.js
│   ├── create-web-app-fixed.js
│   ├── create-web-app.js
│   └── create-webapp-deployment.js
├── index.js
├── lib
│   ├── authHelper.js
│   ├── logger.js
│   ├── oauth-helper.js
│   ├── tokenManager.js
│   └── tools.js
├── LOGGING.md
├── mcpServer.js
├── OAUTH_IMPLEMENTATION.md
├── OAUTH_SETUP.md
├── oauth-setup.js
├── package-lock.json
├── package.json
├── README.md
├── test
│   ├── debug-content-fetch.js
│   ├── debug-deployment.js
│   ├── debug-mcp-deployment.js
│   ├── debug-test.js
│   ├── deploy-complete-webapp.js
│   ├── oauth-setup-broken.js
│   ├── oauth-setup-fixed.js
│   ├── simple-oauth-test.js
│   ├── simple-test.js
│   ├── test-complete-mcp-webapp.js
│   ├── test-fields-issue.js
│   ├── test-logging.js
│   ├── test-mcp-content.js
│   ├── test-mcp-deployment-direct.js
│   ├── test-mcp-deployment-fix.js
│   ├── test-mcp-deployment-get.js
│   ├── test-mcp-errors.js
│   ├── test-mcp-fetch-processes.js
│   ├── test-mcp-processes.js
│   ├── test-mcp-tools.js
│   ├── test-mcp-version-fix.js
│   ├── test-oauth.js
│   ├── test-token-management.js
│   ├── test-versions-list.js
│   ├── update-and-deploy-dark-theme.js
│   ├── update-error-handling.js
│   ├── update-tools.js
│   └── update-webapp-deployment.js
└── tools
    ├── google-app-script-api
    │   └── apps-script-api
    │       ├── script-processes-list-script-processes.js
    │       ├── script-processes-list.js
    │       ├── script-projects-create.js
    │       ├── script-projects-deployments-create.js
    │       ├── script-projects-deployments-delete.js
    │       ├── script-projects-deployments-get.js
    │       ├── script-projects-deployments-list.js
    │       ├── script-projects-deployments-update.js
    │       ├── script-projects-get-content.js
    │       ├── script-projects-get-metrics.js
    │       ├── script-projects-get.js
    │       ├── script-projects-update-content.js
    │       ├── script-projects-versions-create.js
    │       ├── script-projects-versions-get.js
    │       ├── script-projects-versions-list.js
    │       └── script-scripts-run.js
    └── paths.js
```

# Files

--------------------------------------------------------------------------------
/test/oauth-setup-fixed.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * OAuth Setup Script for Google Apps Script API
 * This script helps you obtain and securely store OAuth tokens
 */

import 'dotenv/config';
import { manualOAuthFlow } from '../lib/oauth-helper.js';
import { TokenManager } from '../lib/tokenManager.js';
import { readFileSync } from 'fs';

console.log('🔐 Google Apps Script API OAuth Setup');
console.log('=====================================\n');

async function setupOAuth() {
  console.log('📋 This script will help you set up OAuth authentication for Google Apps Script API.');
  console.log('📝 You need to have your CLIENT_ID and CLIENT_SECRET configured in .env file.\n');
  
  const tokenManager = new TokenManager();
  
  // Handle info command
  if (process.argv.includes('--info')) {
    const tokenInfo = tokenManager.getTokenInfo();
    
    console.log('🔍 Token Information:');
    console.log('=====================\n');
    
    if (tokenInfo.hasTokens) {
      console.log('✅ Tokens found');
      console.log(`📁 Location: ${tokenInfo.location}`);
      console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
      console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
      console.log(`📊 Status: ${tokenInfo.status}`);
      console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
    } else {
      console.log('❌ No tokens found');
      console.log(`📁 Expected location: ${tokenInfo.location}`);
      console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
    }
    
    process.exit(0);
  }
  
  // Handle clear command
  if (process.argv.includes('--clear')) {
    tokenManager.clearTokens();
    console.log('✅ Tokens cleared successfully.');
    process.exit(0);
  }
  
  // Check if tokens already exist
  const tokenInfo = tokenManager.getTokenInfo();
  if (tokenInfo.hasTokens) {
    console.log('🔍 Found existing tokens:');
    console.log(`   📁 Location: ${tokenInfo.location}`);
    console.log(`   💾 Saved at: ${tokenInfo.savedAt}`);
    console.log(`   ⏰ Expires at: ${tokenInfo.expiresAt}`);
    console.log(`   📊 Status: ${tokenInfo.status}`);
    console.log(`   🔐 Scope: ${tokenInfo.scope || 'Not specified'}\n`);
    
    if (!tokenInfo.isExpired) {
      console.log('✅ You already have valid tokens stored.');
      console.log('💡 To get new tokens, run: node oauth-setup.js --force');
      console.log('🗑️ To clear existing tokens, run: node oauth-setup.js --clear\n');
      
      if (!process.argv.includes('--force')) {
        process.exit(0);
      }
    }
  }
  
  try {
    // Check if .env file exists and has required credentials
    const envPath = '.env';
    let envContent = '';
    
    try {
      envContent = readFileSync(envPath, 'utf8');
      console.log('✅ Found .env file');
    } catch (error) {
      console.error('❌ No .env file found. Please create one first with your CLIENT_ID and CLIENT_SECRET.');
      console.log('\n📝 Example .env file content:');
      console.log('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
      console.log('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
      console.log('\n📖 Note: Refresh token is now stored securely and not needed in .env file');
      process.exit(1);
    }
    
    // Check for required credentials
    const hasClientId = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=') && 
                       !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
    const hasClientSecret = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=') && 
                           !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
    
    if (!hasClientId || !hasClientSecret) {
      console.error('❌ Missing CLIENT_ID or CLIENT_SECRET in .env file.');
      console.log('\n🔧 Please update your .env file with valid credentials:');
      console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_actual_client_id');
      console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_actual_client_secret');
      console.log('\n📖 See OAUTH_SETUP.md for instructions on obtaining these credentials.');
      process.exit(1);
    }
    
    console.log('✅ Found required credentials in .env file');
    console.log('\n🚀 Starting OAuth flow...');
    console.log('📱 Your browser will open automatically');
    console.log('🔐 Please authorize the application when prompted');
    console.log('⏳ Waiting for authorization...\n');
    
    // Start OAuth flow
    const tokens = await manualOAuthFlow();
    
    if (tokens.refresh_token) {
      console.log('\n🎉 OAuth setup successful!');
      console.log('🔑 Access token obtained:', tokens.access_token ? '✅' : '❌');
      console.log('🔄 Refresh token obtained:', tokens.refresh_token ? '✅' : '❌');
      
      // Save tokens securely using TokenManager
      try {
        tokenManager.saveTokens(tokens);
        console.log('💾 Tokens saved securely');
        
        const tokenInfo = tokenManager.getTokenInfo();
        console.log(`📁 Token location: ${tokenInfo.location}`);
        console.log(`🔒 File permissions: Owner read/write only`);
        
        console.log('\n✅ Setup complete! Your OAuth tokens are now stored securely.');
        console.log('🔐 Refresh tokens are stored in a secure OS-specific location');
        console.log('🚀 You can now use the MCP server and API tools');
        
        console.log('\n🧪 Test your setup with:');
        console.log('   node test-token-management.js');
        
      } catch (saveError) {
        console.error('\n❌ Failed to save tokens:', saveError.message);
        console.log('🔧 Please check file permissions and try again');
        process.exit(1);
      }
      
    } else {
      console.log('\n⚠️ OAuth completed but no refresh token received.');
      console.log('🔄 You may need to revoke and re-authorize the application.');
      console.log('📖 Check the Google Cloud Console for your OAuth settings.');
    }
    
  } catch (error) {
    console.error('\n❌ OAuth setup failed:', error.message);
    console.log('\n🔧 Troubleshooting:');
    console.log('   1. Check your internet connection');
    console.log('   2. Verify your CLIENT_ID and CLIENT_SECRET are correct');
    console.log('   3. Ensure the redirect URI is registered in Google Cloud Console');
    console.log('   4. Make sure Google Apps Script API is enabled');
    console.log('   5. Try revoking and re-creating your OAuth credentials');
    console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
    process.exit(1);
  }
}

// Run setup
setupOAuth().catch((error) => {
  console.error('💥 Unexpected error:', error);
  process.exit(1);
});

```

--------------------------------------------------------------------------------
/tools/google-app-script-api/apps-script-api/script-processes-list-script-processes.js:
--------------------------------------------------------------------------------

```javascript
import { logger } from '../../../lib/logger.js';

/**
 * Function to list script processes for a given script ID.
 *
 * @param {Object} args - Arguments for listing script processes.
 * @param {string} args.scriptId - The ID of the script to list processes for.
 * @param {number} [args.pageSize=100] - The number of processes to return per page.
 * @param {string} [args.functionName] - Filter by function name.
 * @param {string} [args.pageToken] - Token for pagination.
 * @param {string} [args.startTime] - Filter by start time.
 * @param {string} [args.endTime] - Filter by end time.
 * @param {string} [args.deploymentId] - Filter by deployment ID.
 * @param {string} [args.types] - Filter by process types.
 * @param {string} [args.statuses] - Filter by process statuses.
 * @param {string} [args.userAccessLevels] - Filter by user access levels.
 * @returns {Promise<Object>} - The result of the script processes listing.
 */
const executeFunction = async ({ scriptId, pageSize = 100, functionName, pageToken, startTime, endTime, deploymentId, types, statuses, userAccessLevels }) => {
  const baseUrl = 'https://script.googleapis.com';
  const accessToken = ''; // will be provided by the user
  const startTimeMs = Date.now();
  
  try {
    logger.info('SCRIPT_PROCESSES_LIST', 'Starting script processes list request', { scriptId, pageSize, functionName });

    // Construct the URL with query parameters
    const url = new URL(`${baseUrl}/v1/processes:listScriptProcesses`);
    url.searchParams.append('scriptId', scriptId);
    url.searchParams.append('pageSize', pageSize.toString());
    if (functionName) url.searchParams.append('scriptProcessFilter.functionName', functionName);
    if (pageToken) url.searchParams.append('pageToken', pageToken);
    if (startTime) url.searchParams.append('scriptProcessFilter.startTime', startTime);
    if (endTime) url.searchParams.append('scriptProcessFilter.endTime', endTime);
    if (deploymentId) url.searchParams.append('scriptProcessFilter.deploymentId', deploymentId);
    if (types) url.searchParams.append('scriptProcessFilter.types', types);
    if (statuses) url.searchParams.append('scriptProcessFilter.statuses', statuses);
    if (userAccessLevels) url.searchParams.append('scriptProcessFilter.userAccessLevels', userAccessLevels);
    url.searchParams.append('alt', 'json');
    url.searchParams.append('prettyPrint', 'true');

    logger.debug('SCRIPT_PROCESSES_LIST', 'Constructed API URL', {
      url: url.toString(),
      pathSegments: url.pathname.split('/'),
      queryParams: Object.fromEntries(url.searchParams)
    });

    // Set up headers for the request
    const headers = {
      'Authorization': `Bearer ${accessToken}`,
      'Accept': 'application/json'
    };

    logger.logAPICall('GET', url.toString(), headers);

    // Perform the fetch request
    const fetchStartTime = Date.now();
    const response = await fetch(url.toString(), {
      method: 'GET',
      headers
    });
    
    const fetchDuration = Date.now() - fetchStartTime;
    const responseSize = response.headers.get('content-length') || 'unknown';
    
    logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);

    // Check if the response was successful
    if (!response.ok) {
      const errorText = await response.text();
      let errorData;
      
      try {
        errorData = JSON.parse(errorText);
      } catch (parseError) {
        errorData = { message: errorText };
      }

      const detailedError = {
        status: response.status,
        statusText: response.statusText,
        url: url.toString(),
        errorResponse: errorData,
        duration: Date.now() - startTimeMs,
        scriptId,
        timestamp: new Date().toISOString()
      };

      logger.error('SCRIPT_PROCESSES_LIST', 'API request failed', detailedError);
      
      console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
      
      throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
    }

    // Parse and return the response data
    const data = await response.json();
    
    logger.info('SCRIPT_PROCESSES_LIST', 'Successfully retrieved script processes', {
      scriptId,
      processesCount: data.processes?.length || 0,
      duration: Date.now() - startTimeMs
    });
    
    console.log('✅ Successfully retrieved script processes');
    return data;
  } catch (error) {
    const errorDetails = {
      message: error.message,
      stack: error.stack,
      scriptId,
      duration: Date.now() - startTimeMs,
      timestamp: new Date().toISOString(),
      errorType: error.name || 'Unknown'
    };

    logger.error('SCRIPT_PROCESSES_LIST', 'Error listing script processes', errorDetails);
    
    console.error('❌ Error listing script processes:', errorDetails);
    
    // Return detailed error information for debugging
    return { 
      error: true,
      message: error.message,
      details: errorDetails,
      rawError: {
        name: error.name,
        stack: error.stack
      }
    };
  }
};

/**
 * Tool configuration for listing script processes on Google Apps Script.
 * @type {Object}
 */
const apiTool = {
  function: executeFunction,
  definition: {
    type: 'function',
    function: {
      name: 'list_script_processes',
      description: 'List information about a script\'s executed processes.',
      parameters: {
        type: 'object',
        properties: {
          scriptId: {
            type: 'string',
            description: 'The ID of the script to list processes for.'
          },
          pageSize: {
            type: 'integer',
            description: 'The number of processes to return per page.'
          },
          functionName: {
            type: 'string',
            description: 'Filter by function name.'
          },
          pageToken: {
            type: 'string',
            description: 'Token for pagination.'
          },
          startTime: {
            type: 'string',
            description: 'Filter by start time.'
          },
          endTime: {
            type: 'string',
            description: 'Filter by end time.'
          },
          deploymentId: {
            type: 'string',
            description: 'Filter by deployment ID.'
          },
          types: {
            type: 'string',
            description: 'Filter by process types.'
          },
          statuses: {
            type: 'string',
            description: 'Filter by process statuses.'
          },
          userAccessLevels: {
            type: 'string',
            description: 'Filter by user access levels.'
          }
        },
        required: ['scriptId']
      }
    }
  }
};

export { apiTool };
```

--------------------------------------------------------------------------------
/lib/tokenManager.js:
--------------------------------------------------------------------------------

```javascript
/**
 * OAuth Token Manager for secure token storage and management
 * Handles access token refresh and secure storage of refresh tokens
 */

import fs from 'fs';
import path from 'path';
import os from 'os';

export class TokenManager {
  constructor() {
    this.tokenDir = this.getTokenDirectory();
    this.tokenFile = path.join(this.tokenDir, 'tokens.json');
    this.ensureTokenDirectory();
  }

  /**
   * Get platform-specific directory for token storage
   * @returns {string} Token directory path
   */
  getTokenDirectory() {
    const platform = os.platform();
    const homedir = os.homedir();
    
    switch (platform) {
      case 'win32':
        return path.join(homedir, 'AppData', 'Roaming', 'google-apps-script-mcp');
      case 'darwin':
        return path.join(homedir, 'Library', 'Application Support', 'google-apps-script-mcp');
      default:
        return path.join(homedir, '.config', 'google-apps-script-mcp');
    }
  }

  /**
   * Ensure token directory exists
   */
  ensureTokenDirectory() {
    if (!fs.existsSync(this.tokenDir)) {
      fs.mkdirSync(this.tokenDir, { recursive: true });
      console.log(`📁 Created token directory: ${this.tokenDir}`);
    }
  }

  /**
   * Save OAuth tokens securely
   * @param {Object} tokens - OAuth tokens object
   */
  saveTokens(tokens) {
    const tokenData = {
      access_token: tokens.access_token,
      refresh_token: tokens.refresh_token,
      expires_at: Date.now() + (tokens.expires_in * 1000),
      token_type: tokens.token_type || 'Bearer',
      scope: tokens.scope,
      saved_at: new Date().toISOString()
    };
    
    try {
      fs.writeFileSync(this.tokenFile, JSON.stringify(tokenData, null, 2), { mode: 0o600 });
      console.log(`💾 Tokens saved securely to: ${this.tokenFile}`);
      console.log(`🔒 File permissions: 600 (owner read/write only)`);
    } catch (error) {
      console.error('❌ Failed to save tokens:', error.message);
      throw new Error(`Failed to save tokens: ${error.message}`);
    }
  }

  /**
   * Load stored OAuth tokens
   * @returns {Object|null} Stored tokens or null if not found
   */
  loadTokens() {
    if (!fs.existsSync(this.tokenFile)) {
      console.log('📝 No stored tokens found');
      return null;
    }
    
    try {
      const tokenData = JSON.parse(fs.readFileSync(this.tokenFile, 'utf8'));
      console.log(`📖 Loaded tokens from: ${this.tokenFile}`);
      console.log(`💾 Tokens saved at: ${tokenData.saved_at || 'Unknown'}`);
      return tokenData;
    } catch (error) {
      console.error('❌ Failed to load tokens:', error.message);
      return null;
    }
  }

  /**
   * Check if current access token is expired or will expire soon
   * @param {Object} tokens - Token object
   * @returns {boolean} True if token is expired or will expire within 1 minute
   */
  isTokenExpired(tokens) {
    if (!tokens || !tokens.expires_at) {
      return true;
    }
    
    // Consider token expired if it expires within the next minute
    const bufferTime = 60000; // 1 minute buffer
    const isExpired = Date.now() >= (tokens.expires_at - bufferTime);
    
    if (isExpired) {
      console.log('⏰ Access token is expired or will expire soon');
    } else {
      const expiresIn = Math.round((tokens.expires_at - Date.now()) / 1000 / 60);
      console.log(`⏰ Access token expires in ${expiresIn} minutes`);
    }
    
    return isExpired;
  }

  /**
   * Refresh access token using stored refresh token
   * @param {string} clientId - OAuth client ID
   * @param {string} clientSecret - OAuth client secret
   * @returns {Promise<Object>} New tokens
   */
  async refreshAccessToken(clientId, clientSecret) {
    const tokens = this.loadTokens();
    if (!tokens || !tokens.refresh_token) {
      throw new Error('No refresh token available. Please run OAuth setup again: node oauth-setup.js');
    }

    console.log('🔄 Refreshing access token...');
    
    try {
      const response = await fetch('https://oauth2.googleapis.com/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: new URLSearchParams({
          client_id: clientId,
          client_secret: clientSecret,
          refresh_token: tokens.refresh_token,
          grant_type: 'refresh_token'
        })
      });

      if (!response.ok) {
        const errorText = await response.text();
        console.error('❌ Token refresh failed:', response.status, errorText);
        throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
      }

      const newTokens = await response.json();
      
      // Keep the existing refresh token if not provided in response
      if (!newTokens.refresh_token) {
        newTokens.refresh_token = tokens.refresh_token;
      }

      // Save the refreshed tokens
      this.saveTokens(newTokens);
      console.log('✅ Access token refreshed successfully');
      
      return newTokens;
    } catch (error) {
      console.error('❌ Failed to refresh access token:', error.message);
      throw error;
    }
  }

  /**
   * Get a valid access token, refreshing if necessary
   * @param {string} clientId - OAuth client ID
   * @param {string} clientSecret - OAuth client secret
   * @returns {Promise<string>} Valid access token
   */
  async getValidAccessToken(clientId, clientSecret) {
    let tokens = this.loadTokens();
    
    if (!tokens) {
      throw new Error('No tokens found. Please run OAuth setup first: node oauth-setup.js');
    }
    
    if (this.isTokenExpired(tokens)) {
      console.log('🔄 Token expired, refreshing...');
      tokens = await this.refreshAccessToken(clientId, clientSecret);
    } else {
      console.log('✅ Using existing valid access token');
    }
    
    return tokens.access_token;
  }

  /**
   * Check if tokens are stored and available
   * @returns {boolean} True if refresh token is available
   */
  hasStoredTokens() {
    const tokens = this.loadTokens();
    return tokens && tokens.refresh_token;
  }

  /**
   * Clear stored tokens (for logout/reset)
   */
  clearTokens() {
    if (fs.existsSync(this.tokenFile)) {
      fs.unlinkSync(this.tokenFile);
      console.log('🗑️ Stored tokens cleared');
    }
  }

  /**
   * Get token storage information
   * @returns {Object} Token storage info
   */
  getTokenInfo() {
    const tokens = this.loadTokens();
    if (!tokens) {
      return {
        hasTokens: false,
        location: this.tokenFile,
        status: 'No tokens stored'
      };
    }

    const isExpired = this.isTokenExpired(tokens);
    const expiresAt = new Date(tokens.expires_at);
    
    return {
      hasTokens: true,
      location: this.tokenFile,
      savedAt: tokens.saved_at,
      expiresAt: expiresAt.toISOString(),
      isExpired,
      scope: tokens.scope,
      status: isExpired ? 'Token expired' : 'Token valid'
    };
  }
}

// Export using named export (already done at class declaration)

```

--------------------------------------------------------------------------------
/tools/google-app-script-api/apps-script-api/script-processes-list.js:
--------------------------------------------------------------------------------

```javascript
import { getAuthHeaders } from '../../../lib/oauth-helper.js';
import { logger } from '../../../lib/logger.js';

/**
 * Function to list processes for a Google Apps Script project.
 *
 * @param {Object} args - Arguments for the process listing.
 * @param {string} args.scriptId - The ID of the script to filter processes.
 * @param {string} [args.startTime] - The start time for filtering processes.
 * @param {string} [args.functionName] - The name of the function to filter processes.
 * @param {string} [args.deploymentId] - The deployment ID to filter processes.
 * @param {string} [args.projectName] - The project name to filter processes.
 * @param {Array<string>} [args.statuses] - The statuses to filter processes.
 * @param {string} [args.pageToken] - Token for pagination.
 * @param {Array<string>} [args.types] - The types of processes to filter.
 * @param {Array<string>} [args.userAccessLevels] - User access levels to filter.
 * @param {number} [args.pageSize=100] - The number of processes to return per page.
 * @param {string} [args.endTime] - The end time for filtering processes.
 * @param {string} [args.fields] - Selector specifying which fields to include in a partial response.
 * @param {boolean} [args.prettyPrint=true] - Returns response with indentations and line breaks.
 * @returns {Promise<Object>} - The result of the process listing.
 */
const executeFunction = async ({
  scriptId,
  startTime,
  functionName,
  deploymentId,
  projectName,
  statuses,
  pageToken,
  types,
  userAccessLevels,
  pageSize = 100,
  endTime,
  fields,
  prettyPrint = true
}) => {
  const baseUrl = 'https://script.googleapis.com';
  const startTime_exec = Date.now();

  logger.info('API_CALL', 'Starting script processes list request', {
    scriptId,
    pageSize,
    startTime,
    endTime,
    functionName,
    deploymentId,
    baseUrl
  });

  try {
    // Validate required parameters
    if (!scriptId) {
      logger.error('API_CALL', 'Missing required parameter: scriptId');
      throw new Error('scriptId is required');
    }

    // Construct the URL with query parameters
    const url = new URL(`${baseUrl}/v1/processes`);
    const params = new URLSearchParams();
    params.append('userProcessFilter.scriptId', scriptId);
    if (startTime) params.append('userProcessFilter.startTime', startTime);
    if (functionName) params.append('userProcessFilter.functionName', functionName);
    if (deploymentId) params.append('userProcessFilter.deploymentId', deploymentId);
    if (projectName) params.append('userProcessFilter.projectName', projectName);
    if (statuses) params.append('userProcessFilter.statuses', statuses.join(','));
    if (pageToken) params.append('pageToken', pageToken);
    if (types) params.append('userProcessFilter.types', types.join(','));
    if (userAccessLevels) params.append('userProcessFilter.userAccessLevels', userAccessLevels.join(','));
    if (endTime) params.append('userProcessFilter.endTime', endTime);
    if (fields) params.append('fields', fields);
    params.append('pageSize', pageSize);
    params.append('prettyPrint', prettyPrint);
    
    url.search = params.toString();

    logger.debug('API_CALL', 'Constructed API URL', {
      url: url.toString(),
      queryParams: Object.fromEntries(params)
    });

    // Get OAuth headers
    logger.debug('API_CALL', 'Getting OAuth headers');
    const headers = await getAuthHeaders();

    logger.logAPICall('GET', url.toString(), headers);

    // Perform the fetch request
    const fetchStartTime = Date.now();
    const response = await fetch(url.toString(), {
      method: 'GET',
      headers
    });
    
    const fetchDuration = Date.now() - fetchStartTime;
    const responseSize = response.headers.get('content-length') || 'unknown';
    
    logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);

    // Check if the response was successful
    if (!response.ok) {
      const errorText = await response.text();
      let errorData;
      
      try {
        errorData = JSON.parse(errorText);
      } catch (parseError) {
        errorData = { message: errorText };
      }

      logger.error('API_CALL', 'API request failed', {
        status: response.status,
        statusText: response.statusText,
        url: url.toString(),
        errorResponse: errorData,
        scriptId
      });
      
      throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
    }

    // Parse and return the response data
    const data = await response.json();
    const totalDuration = Date.now() - startTime_exec;
    
    logger.info('API_CALL', 'Script processes list request completed successfully', {
      scriptId,
      processCount: data.processes ? data.processes.length : 0,
      hasNextPageToken: !!data.nextPageToken,
      totalDuration: `${totalDuration}ms`,
      responseSize: JSON.stringify(data).length
    });
    
    return data;
  } catch (error) {
    logger.error('API_CALL', 'Script processes list request failed', {
      scriptId,
      error: {
        message: error.message,
        stack: error.stack
      }
    });
    
    console.error('Error listing processes:', error);
    return { 
      error: true,
      message: error.message,
      details: {
        scriptId,
        timestamp: new Date().toISOString(),
        errorType: error.name || 'Unknown'
      }
    };
  }
};

/**
 * Tool configuration for listing processes in Google Apps Script.
 * @type {Object}
 */
const apiTool = {
  function: executeFunction,
  definition: {
    type: 'function',
    function: {
      name: 'script_processes_list',
      description: 'List processes for a Google Apps Script project.',
      parameters: {
        type: 'object',
        properties: {
          scriptId: {
            type: 'string',
            description: 'The ID of the script to filter processes.'
          },
          startTime: {
            type: 'string',
            description: 'The start time for filtering processes.'
          },
          functionName: {
            type: 'string',
            description: 'The name of the function to filter processes.'
          },
          deploymentId: {
            type: 'string',
            description: 'The deployment ID to filter processes.'
          },
          projectName: {
            type: 'string',
            description: 'The project name to filter processes.'
          },
          statuses: {
            type: 'array',
            items: {
              type: 'string'
            },
            description: 'The statuses to filter processes.'
          },
          pageToken: {
            type: 'string',
            description: 'Token for pagination.'
          },
          types: {
            type: 'array',
            items: {
              type: 'string'
            },
            description: 'The types of processes to filter.'
          },
          userAccessLevels: {
            type: 'array',
            items: {
              type: 'string'
            },
            description: 'User access levels to filter.'
          },
          pageSize: {
            type: 'integer',
            description: 'The number of processes to return per page.'
          },
          endTime: {
            type: 'string',
            description: 'The end time for filtering processes.'
          },
          fields: {
            type: 'string',
            description: 'Selector specifying which fields to include in a partial response.'
          },
          prettyPrint: {
            type: 'boolean',
            description: 'Returns response with indentations and line breaks.'
          }
        },
        required: ['scriptId']
      }
    }
  }
};

export { apiTool };
```

--------------------------------------------------------------------------------
/LOGGING.md:
--------------------------------------------------------------------------------

```markdown
# Enhanced MCP Server Logging

This document describes the enhanced logging capabilities added to the MCP server for detailed monitoring and debugging of tool responses.

## Overview

The MCP server now includes comprehensive logging that tracks:
- Tool discovery and initialization
- Tool execution requests and responses
- API calls to Google Apps Script services
- Authentication flows
- Error conditions with detailed context
- Performance metrics

## Configuration

### Log Levels

Set the `LOG_LEVEL` environment variable in your `.env` file:

```env
# LOG_LEVEL can be: error, warn, info, debug, trace
LOG_LEVEL=info
```

**Available Log Levels:**
- `error`: Only error messages
- `warn`: Errors and warnings
- `info`: Errors, warnings, and informational messages (default)
- `debug`: All above plus detailed debugging information
- `trace`: Maximum verbosity (includes all internal operations)

### Environment Variables

Add to your `.env` file:
```env
# Logging Configuration
LOG_LEVEL=info
```

## Features

### 1. Tool Execution Logging

Every tool execution is logged with:
- **Request ID**: Unique identifier for tracking
- **Tool Name**: Which tool was called
- **Arguments**: Input parameters (sanitized)
- **Execution Time**: How long the tool took to execute
- **Response Size**: Size of the response data
- **Success/Failure Status**: Whether the tool completed successfully

**Example Output:**
```
[2025-06-01T10:30:15.123Z] [INFO] [TOOL_REQUEST] Executing tool: script_projects_get
{
  "tool": "script_projects_get",
  "arguments": {
    "scriptId": "1BxKdN9XvlHF8rF9mF8..."
  },
  "requestId": "req_1717234215123_abc123def"
}

[2025-06-01T10:30:15.456Z] [INFO] [TOOL_RESPONSE] Tool completed: script_projects_get
{
  "tool": "script_projects_get",
  "duration": "333ms",
  "requestId": "req_1717234215123_abc123def",
  "responseSize": 2048,
  "success": true
}
```

### 2. API Call Logging

All HTTP requests to Google APIs are logged with:
- **Method and URL**: Complete request details
- **Headers**: Sanitized headers (Authorization tokens are redacted)
- **Response Status**: HTTP status codes
- **Response Time**: Network latency
- **Response Size**: Payload size

**Example Output:**
```
[2025-06-01T10:30:15.200Z] [DEBUG] [API_CALL] Making API request: GET https://script.googleapis.com/v1/projects/123
{
  "method": "GET",
  "url": "https://script.googleapis.com/v1/projects/123",
  "headers": {
    "Authorization": "Bearer ***REDACTED***",
    "Accept": "application/json",
    "Content-Type": "application/json"
  }
}

[2025-06-01T10:30:15.400Z] [DEBUG] [API_RESPONSE] API response: GET https://script.googleapis.com/v1/projects/123
{
  "method": "GET",
  "url": "https://script.googleapis.com/v1/projects/123",
  "status": 200,
  "responseTime": "200ms",
  "responseSize": "1024 bytes"
}
```

### 3. Authentication Logging

OAuth authentication flows are tracked:
- **Token Requests**: When access tokens are requested
- **Token Refresh**: When tokens are refreshed
- **Authentication Failures**: Failed auth attempts with reasons
- **Token Information**: Token types and scopes (sanitized)

**Example Output:**
```
[2025-06-01T10:30:14.500Z] [INFO] [AUTH] Requesting OAuth access token
[2025-06-01T10:30:14.800Z] [INFO] [AUTH] Access token obtained successfully
{
  "duration": "300ms",
  "tokenLength": 195,
  "tokenPrefix": "ya29.a0AWY7C..."
}
```

### 4. Error Logging

Comprehensive error tracking includes:
- **Stack traces**: Full error call stacks
- **Context information**: What was being attempted
- **Request parameters**: Input data that caused the error
- **Timing information**: How long the operation took before failing

**Example Output:**
```
[2025-06-01T10:30:16.123Z] [ERROR] [TOOL_ERROR] Tool failed: script_projects_get
{
  "tool": "script_projects_get",
  "duration": "667ms",
  "requestId": "req_1717234215456_def456ghi",
  "error": {
    "message": "API Error (404): Script project not found",
    "stack": "Error: API Error (404): Script project not found\n    at executeFunction...",
    "name": "Error"
  }
}
```

### 5. Performance Metrics

Track performance across all operations:
- **Tool execution times**: How long each tool takes
- **API response times**: Network latency to Google services
- **Authentication times**: OAuth token acquisition time
- **Overall request duration**: End-to-end request processing

## Usage

### 1. Running with Enhanced Logging

```bash
# Start the MCP server with info level logging
npm start

# Start with debug logging for detailed information
LOG_LEVEL=debug npm start

# Start with minimal error-only logging
LOG_LEVEL=error npm start
```

### 2. Testing the Logging

Run the logging test script:
```bash
node test-logging.js
```

This will demonstrate all logging features and show you what to expect.

### 3. Log Output Examples

**Startup Logs:**
```
[2025-06-01T10:30:10.000Z] [INFO] [STARTUP] Starting MCP server in STDIO mode
[2025-06-01T10:30:10.100Z] [INFO] [DISCOVERY] Starting tool discovery for 17 tool paths
[2025-06-01T10:30:10.500Z] [INFO] [DISCOVERY] Tool discovery completed
{
  "totalPaths": 17,
  "successfullyLoaded": 17,
  "failed": 0,
  "toolNames": ["script_projects_get", "script_projects_deployments_list", ...]
}
```

**Request Handling:**
```
[2025-06-01T10:30:15.000Z] [INFO] [REQUEST] CallTool request received
{
  "requestId": "req_1717234215000_xyz789",
  "toolName": "script_projects_get",
  "arguments": {
    "scriptId": "1BxKdN9XvlHF8rF9mF8..."
  },
  "timestamp": "2025-06-01T10:30:15.000Z"
}
```

## Log Categories

The logging system uses categories to organize different types of events:

- **STARTUP**: Server initialization and configuration
- **DISCOVERY**: Tool loading and discovery process  
- **REQUEST**: MCP protocol request handling
- **EXECUTION**: Tool execution and results
- **API_CALL**: HTTP requests to external APIs
- **API_RESPONSE**: HTTP response processing
- **AUTH**: Authentication and authorization
- **ERROR**: Error conditions and failures
- **TEST**: Testing and debugging operations

## Benefits

1. **Debugging**: Quickly identify issues with specific tools or API calls
2. **Performance Monitoring**: Track response times and identify bottlenecks
3. **Audit Trail**: Complete record of all operations for compliance
4. **Error Analysis**: Detailed error context for troubleshooting
5. **Usage Analytics**: Understand which tools are used most frequently
6. **Security Monitoring**: Track authentication events and failures

## Log File Integration

While the current implementation outputs to console, you can easily redirect logs to files:

```bash
# Log everything to a file
node mcpServer.js 2>&1 | tee mcp-server.log

# Log only errors to a separate file
node mcpServer.js 2>error.log

# Use with log rotation tools like logrotate
node mcpServer.js 2>&1 | logger -t mcp-server
```

## Customization

The logging system is built with the `MCPLogger` class in `lib/logger.js`. You can:

1. **Add new log categories**: Extend the logger for specific use cases
2. **Customize log format**: Modify the `formatMessage` method
3. **Add log filtering**: Implement custom filtering logic
4. **Integrate with external systems**: Send logs to monitoring services

## Security Considerations

- **Token Redaction**: OAuth tokens are automatically redacted in logs
- **Sensitive Data**: Personal data in request parameters should be sanitized
- **Log Storage**: Ensure log files are properly secured if stored
- **Retention**: Implement appropriate log retention policies

## Troubleshooting

**Common Issues:**

1. **Too much logging**: Set `LOG_LEVEL=warn` or `LOG_LEVEL=error`
2. **Missing logs**: Check that `LOG_LEVEL` is set appropriately
3. **Performance impact**: Debug level logging may impact performance in production

**Log Level Guidelines:**
- **Production**: Use `info` or `warn` level
- **Development**: Use `debug` level for detailed information
- **Troubleshooting**: Use `trace` level for maximum detail
- **Error monitoring**: Use `error` level for minimal output

```

--------------------------------------------------------------------------------
/mcpServer.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import dotenv from "dotenv";
import express from "express";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import { discoverTools } from "./lib/tools.js";
import { logger } from "./lib/logger.js";

import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

dotenv.config({ path: path.resolve(__dirname, ".env") });

const SERVER_NAME = "generated-mcp-server";

async function transformTools(tools) {
  logger.info('SETUP', `Transforming ${tools.length} discovered tools`);
  const transformedTools = tools
    .map((tool) => {
      const definitionFunction = tool.definition?.function;
      if (!definitionFunction) {
        logger.warn('SETUP', `Tool missing definition function`, { toolPath: tool.path });
        return;
      }
      logger.debug('SETUP', `Transformed tool: ${definitionFunction.name}`, {
        name: definitionFunction.name,
        description: definitionFunction.description,
        requiredParams: definitionFunction.parameters?.required || []
      });
      return {
        name: definitionFunction.name,
        description: definitionFunction.description,
        inputSchema: definitionFunction.parameters,
      };
    })
    .filter(Boolean);
  
  logger.info('SETUP', `Successfully transformed ${transformedTools.length} tools`);
  return transformedTools;
}

async function setupServerHandlers(server, tools) {
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    logger.info('REQUEST', 'Handling ListTools request');
    const transformedTools = await transformTools(tools);
    logger.info('REQUEST', `Returning ${transformedTools.length} available tools`, {
      toolNames: transformedTools.map(t => t.name)
    });
    return { tools: transformedTools };
  });

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const startTime = Date.now();
    const requestId = logger.generateRequestId();
    const toolName = request.params.name;
    const args = request.params.arguments;
    
    logger.info('REQUEST', `CallTool request received`, {
      requestId,
      toolName,
      arguments: args,
      timestamp: new Date().toISOString()
    });

    const tool = tools.find((t) => t.definition.function.name === toolName);
    if (!tool) {
      logger.error('REQUEST', `Tool not found: ${toolName}`, {
        requestId,
        availableTools: tools.map(t => t.definition.function.name)
      });
      throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
    }

    const requiredParameters = tool.definition?.function?.parameters?.required || [];
    
    // Validate required parameters
    for (const requiredParameter of requiredParameters) {
      if (!(requiredParameter in args)) {
        logger.error('REQUEST', `Missing required parameter`, {
          requestId,
          toolName,
          missingParameter: requiredParameter,
          providedArgs: Object.keys(args)
        });
        throw new McpError(
          ErrorCode.InvalidParams,
          `Missing required parameter: ${requiredParameter}`
        );
      }
    }

    try {
      logger.info('EXECUTION', `Executing tool: ${toolName}`, {
        requestId,
        toolPath: tool.path,
        validatedArgs: args
      });

      const result = await tool.function(args);
      const duration = Date.now() - startTime;
      
      logger.info('EXECUTION', `Tool execution completed successfully`, {
        requestId,
        toolName,
        duration: `${duration}ms`,
        resultType: typeof result,
        resultSize: JSON.stringify(result).length,
        hasError: !!result.error
      });

      // Log detailed result for debugging
      logger.debug('EXECUTION', `Tool result details`, {
        requestId,
        toolName,
        result: result
      });

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      const duration = Date.now() - startTime;
      
      logger.error('EXECUTION', `Tool execution failed`, {
        requestId,
        toolName,
        duration: `${duration}ms`,
        error: {
          message: error.message,
          stack: error.stack,
          name: error.name
        },
        args: args
      });
      
      console.error("[Error] Failed to fetch data:", error);
      throw new McpError(
        ErrorCode.InternalError,
        `API error: ${error.message}`
      );
    }
  });
}

async function run() {
  const args = process.argv.slice(2);
  const isSSE = args.includes("--sse");
  
  logger.info('STARTUP', `Starting MCP server in ${isSSE ? 'SSE' : 'STDIO'} mode`);
  
  const tools = await discoverTools();
  logger.info('STARTUP', `Discovered ${tools.length} tools`, {
    toolPaths: tools.map(t => t.path),
    toolNames: tools.map(t => t.definition?.function?.name).filter(Boolean)
  });

  if (isSSE) {
    const app = express();
    const transports = {};
    const servers = {};

    app.get("/sse", async (_req, res) => {
      const sessionId = Date.now().toString();
      logger.info('SSE', `New SSE connection established`, { sessionId });
      
      // Create a new Server instance for each session
      const server = new Server(
        {
          name: SERVER_NAME,
          version: "0.1.0",
        },
        {
          capabilities: {
            tools: {},
          },
        }
      );
      
      server.onerror = (error) => {
        logger.error('SSE', `Server error for session ${sessionId}`, error);
        console.error("[Error]", error);
      };
      
      await setupServerHandlers(server, tools);

      const transport = new SSEServerTransport("/messages", res);
      transports[transport.sessionId] = transport;
      servers[transport.sessionId] = server;

      res.on("close", async () => {
        logger.info('SSE', `SSE connection closed`, { sessionId: transport.sessionId });
        delete transports[transport.sessionId];
        await server.close();
        delete servers[transport.sessionId];
      });

      await server.connect(transport);
    });

    app.post("/messages", async (req, res) => {
      const sessionId = req.query.sessionId;
      const transport = transports[sessionId];
      const server = servers[sessionId];

      if (transport && server) {
        logger.debug('SSE', `Processing message for session`, { sessionId });
        await transport.handlePostMessage(req, res);
      } else {
        logger.warn('SSE', `No transport/server found for session`, { sessionId });
        res.status(400).send("No transport/server found for sessionId");
      }
    });

    const port = process.env.PORT || 3001;
    app.listen(port, () => {
      logger.info('SSE', `SSE server running on port ${port}`);
      console.log(`[SSE Server] running on port ${port}`);
    });
  } else {
    // stdio mode: single server instance
    logger.info('STDIO', 'Initializing STDIO server');
    
    const server = new Server(
      {
        name: SERVER_NAME,
        version: "0.1.0",
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );
    
    server.onerror = (error) => {
      logger.error('STDIO', 'Server error', error);
      console.error("[Error]", error);
    };
    
    await setupServerHandlers(server, tools);

    process.on("SIGINT", async () => {
      logger.info('STDIO', 'Received SIGINT, shutting down gracefully');
      await server.close();
      process.exit(0);
    });

    const transport = new StdioServerTransport();
    logger.info('STDIO', 'STDIO server ready for connections');
    await server.connect(transport);
  }
}

run().catch(console.error);

```

--------------------------------------------------------------------------------
/test/test-complete-mcp-webapp.js:
--------------------------------------------------------------------------------

```javascript
// Complete test script to create a web app deployment using MCP tools
import { apiTool as updateContentTool } from './tools/google-app-script-api/apps-script-api/script-projects-update-content.js';
import { apiTool as createVersionTool } from './tools/google-app-script-api/apps-script-api/script-projects-versions-create.js';
import { apiTool as createDeploymentTool } from './tools/google-app-script-api/apps-script-api/script-projects-deployments-create.js';
import { apiTool as getDeploymentTool } from './tools/google-app-script-api/apps-script-api/script-projects-deployments-get.js';

const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';

async function createCompleteWebAppViaMCP() {
  console.log('🚀 Creating complete web app deployment via MCP tools...');
  
  try {
    // Step 1: Update script content with webapp configuration
    console.log('\n📝 Step 1: Updating script content with webapp configuration...');
    
    const updatedManifest = {
      "timeZone": "America/New_York",
      "dependencies": {},
      "exceptionLogging": "STACKDRIVER", 
      "runtimeVersion": "V8",
      "webapp": {
        "access": "ANYONE",
        "executeAs": "USER_ACCESSING"
      }
    };

    const scriptContent = {
      files: [
        {
          name: "appsscript",
          type: "JSON",
          source: JSON.stringify(updatedManifest, null, 2)
        },
        {
          name: "code",
          type: "SERVER_JS",
          source: `/**
 * Serves the HTML page when the web app is accessed
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('Hello World App via MCP')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Server-side function that can be called from the client
 */
function getGreeting(name) {
  if (!name) {
    name = 'World';
  }
  return \`Hello, \${name}! This web app was created via MCP tools.\`;
}

/**
 * Get current time
 */
function getCurrentTime() {
  return new Date().toLocaleString();
}`
        },
        {
          name: "index",
          type: "HTML",
          source: `<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <title>Hello World App via MCP</title>
  <style>
    body {
      font-family: 'Google Sans', Roboto, Arial, sans-serif;
      max-width: 600px;
      margin: 50px auto;
      padding: 20px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      text-align: center;
    }
    .container {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 15px;
      padding: 30px;
      backdrop-filter: blur(10px);
      box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
    }
    h1 {
      color: #fff;
      margin-bottom: 30px;
      font-size: 2.5em;
    }
    input {
      padding: 12px;
      margin: 10px;
      border: none;
      border-radius: 8px;
      font-size: 16px;
      width: 250px;
    }
    button {
      background: #4285f4;
      color: white;
      border: none;
      padding: 12px 24px;
      margin: 10px;
      border-radius: 8px;
      cursor: pointer;
      font-size: 16px;
      transition: background 0.3s;
    }
    button:hover {
      background: #3367d6;
    }
    .result {
      margin-top: 20px;
      padding: 20px;
      background: rgba(255, 255, 255, 0.2);
      border-radius: 8px;
      min-height: 50px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .loading {
      display: none;
      color: #ffd700;
    }
    .timestamp {
      margin-top: 10px;
      font-size: 0.9em;
      opacity: 0.8;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>🌟 Hello World App via MCP 🌟</h1>
    <p>This web app was created using MCP (Model Context Protocol) tools!</p>
    
    <div class="input-group">
      <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
    </div>
    
    <div>
      <button onclick="sayHello()">Say Hello</button>
      <button onclick="getTime()">Get Current Time</button>
    </div>
    
    <div id="result" class="result">
      Click a button to see the MCP magic! ✨
    </div>
    
    <div id="loading" class="loading">Loading...</div>
    
    <div id="timestamp" class="timestamp"></div>
  </div>

  <script>
    function sayHello() {
      showLoading();
      const name = document.getElementById('nameInput').value;
      
      google.script.run
        .withSuccessHandler(onSuccess)
        .withFailureHandler(onFailure)
        .getGreeting(name);
    }
    
    function getTime() {
      showLoading();
      
      google.script.run
        .withSuccessHandler(onTimeSuccess)
        .withFailureHandler(onFailure)
        .getCurrentTime();
    }
    
    function onSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 20px; font-weight: bold;">
          \${result}
        </div>
      \`;
      updateTimestamp();
    }
    
    function onTimeSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 18px;">
          🕒 Current Time: <strong>\${result}</strong>
        </div>
      \`;
      updateTimestamp();
    }
    
    function onFailure(error) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="color: #ff6b6b;">
          ❌ Error: \${error.message || 'Something went wrong!'}
        </div>
      \`;
      updateTimestamp();
    }
    
    function showLoading() {
      document.getElementById('loading').style.display = 'block';
      document.getElementById('result').style.display = 'none';
    }
    
    function hideLoading() {
      document.getElementById('loading').style.display = 'none';
      document.getElementById('result').style.display = 'flex';
    }
    
    function updateTimestamp() {
      document.getElementById('timestamp').textContent = 
        'Last updated: ' + new Date().toLocaleTimeString();
    }
    
    // Initial timestamp
    updateTimestamp();
  </script>
</body>
</html>`
        }
      ]
    };

    const updateResult = await updateContentTool.function({
      scriptId: scriptId,
      files: scriptContent.files
    });
    
    console.log('✅ Script content updated:', JSON.stringify(updateResult, null, 2));
    
    // Step 2: Create a new version
    console.log('\n📦 Step 2: Creating new version...');
    
    const versionResult = await createVersionTool.function({
      scriptId: scriptId,
      description: 'Web app version created via MCP tools'
    });
    
    console.log('✅ Version created:', JSON.stringify(versionResult, null, 2));
    
    if (!versionResult.versionNumber) {
      throw new Error('Failed to create version');
    }
    
    // Step 3: Create deployment
    console.log('\n🚀 Step 3: Creating deployment...');
    
    const deploymentResult = await createDeploymentTool.function({
      scriptId: scriptId,
      manifestFileName: 'appsscript',
      versionNumber: versionResult.versionNumber,
      description: 'Web app deployment via MCP tools'
    });
    
    console.log('✅ Deployment created:', JSON.stringify(deploymentResult, null, 2));
    
    if (!deploymentResult.deploymentId) {
      throw new Error('Failed to create deployment');
    }
    
    // Step 4: Get deployment details with entry points
    console.log('\n🔍 Step 4: Getting deployment details...');
    
    const deploymentDetails = await getDeploymentTool.function({
      scriptId: scriptId,
      deploymentId: deploymentResult.deploymentId
    });
    
    console.log('✅ Deployment details:', JSON.stringify(deploymentDetails, null, 2));
    
    // Check for web app URL
    if (deploymentDetails.entryPoints && deploymentDetails.entryPoints.length > 0) {
      console.log('\n🎉 SUCCESS! Web app deployment completed!');
      console.log('📱 Deployment ID:', deploymentResult.deploymentId);
      
      const webAppEntry = deploymentDetails.entryPoints.find(entry => entry.entryPointType === 'WEB_APP');
      if (webAppEntry) {
        console.log('🌐 Web App URL:', webAppEntry.webApp.url);
        console.log('🔒 Access Level:', webAppEntry.webApp.access);
        console.log('👤 Execute As:', webAppEntry.webApp.executeAs);
        console.log('\n🚀 Your web app is now live and accessible to anyone!');
      }
    } else {
      console.log('⚠️ Deployment created but no entry points found. May need to wait for propagation.');
    }
    
  } catch (error) {
    console.error('💥 Error during web app creation:', error);
  }
}

createCompleteWebAppViaMCP();

```

--------------------------------------------------------------------------------
/test/oauth-setup-broken.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * OAuth Setup Script for Google Apps Script API
 * This script helps you obtain and securely store OAuth tokens
 */

import 'dotenv/config';
import { manualOAuthFlow } from '../lib/oauth-helper.js';
import { TokenManager } from '../lib/tokenManager.js';
import { readFileSync } from 'fs';

console.log('🔐 Google Apps Script API OAuth Setup');
console.log('=====================================\n');

async function setupOAuth() {
  console.log('📋 This script will help you set up OAuth authentication for Google Apps Script API.');
  console.log('📝 You need to have your CLIENT_ID and CLIENT_SECRET configured in .env file.\n');
  
  const tokenManager = new TokenManager();
  
  // Handle info command
  if (process.argv.includes('--info')) {
    const tokenInfo = tokenManager.getTokenInfo();
    
    console.log('🔍 Token Information:');
    console.log('=====================\n');
    
    if (tokenInfo.hasTokens) {
      console.log('✅ Tokens found');
      console.log(`📁 Location: ${tokenInfo.location}`);
      console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
      console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
      console.log(`📊 Status: ${tokenInfo.status}`);
      console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
    } else {
      console.log('❌ No tokens found');
      console.log(`📁 Expected location: ${tokenInfo.location}`);
      console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
    }
    
    process.exit(0);
  }
  
  // Check if tokens already exist
  const tokenInfo = tokenManager.getTokenInfo();
  if (tokenInfo.hasTokens) {
    console.log('🔍 Found existing tokens:');
    console.log(`   📁 Location: ${tokenInfo.location}`);
    console.log(`   💾 Saved at: ${tokenInfo.savedAt}`);
    console.log(`   ⏰ Expires at: ${tokenInfo.expiresAt}`);
    console.log(`   📊 Status: ${tokenInfo.status}`);
    console.log(`   🔐 Scope: ${tokenInfo.scope || 'Not specified'}\n`);
    
    if (!tokenInfo.isExpired) {
      console.log('✅ You already have valid tokens stored.');
      console.log('💡 To get new tokens, run: node oauth-setup.js --force');
      console.log('🗑️ To clear existing tokens, run: node oauth-setup.js --clear\n');
      
      if (!process.argv.includes('--force')) {
        process.exit(0);
      }
    }
  }
  
  // Handle clear command
  if (process.argv.includes('--clear')) {
    tokenManager.clearTokens();
    console.log('✅ Tokens cleared successfully.');
    process.exit(0);
  }
  
  try {
    // Check if .env file exists and has required credentials
    const envPath = '.env';
    let envContent = '';
    
    try {
      envContent = readFileSync(envPath, 'utf8');
      console.log('✅ Found .env file');
    } catch (error) {
      console.error('❌ No .env file found. Please create one first with your CLIENT_ID and CLIENT_SECRET.');
      console.log('\n📝 Example .env file content:');
      console.log('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
      console.log('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
      console.log('\n📖 Note: Refresh token is now stored securely and not needed in .env file');
      process.exit(1);
    }
      // Check for required credentials
    const hasClientId = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=') && 
                       !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
    const hasClientSecret = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=') && 
                           !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
    
    if (!hasClientId || !hasClientSecret) {
      console.error('❌ Missing CLIENT_ID or CLIENT_SECRET in .env file.');
      console.log('\n🔧 Please update your .env file with valid credentials:');
      console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_actual_client_id');
      console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_actual_client_secret');
      console.log('\n📖 See OAUTH_SETUP.md for instructions on obtaining these credentials.');
      process.exit(1);
    }
    
    console.log('✅ Found required credentials in .env file');    console.log('\n🚀 Starting OAuth flow...');
    console.log('📱 Your browser will open automatically');
    console.log('🔐 Please authorize the application when prompted');
    console.log('⏳ Waiting for authorization...\n');
    
    // Start OAuth flow
    const tokens = await manualOAuthFlow();
    
    if (tokens.refresh_token) {
      console.log('\n🎉 OAuth setup successful!');
      console.log('🔑 Access token obtained:', tokens.access_token ? '✅' : '❌');
      console.log('🔄 Refresh token obtained:', tokens.refresh_token ? '✅' : '❌');
      
      // Save tokens securely using TokenManager
      try {
        tokenManager.saveTokens(tokens);
        console.log('💾 Tokens saved securely');
        
        const tokenInfo = tokenManager.getTokenInfo();
        console.log(`📁 Token location: ${tokenInfo.location}`);
        
      } catch (error) {
        console.error('❌ Failed to save tokens:', error.message);
        console.log('\n📝 Please run the setup again or contact support.');
        process.exit(1);
      }
      
      console.log('\n📋 Setup Summary:');
      console.log('   ✅ OAuth flow completed');
      console.log('   ✅ Access token obtained');
      console.log('   ✅ Refresh token obtained');
      console.log('   ✅ Tokens stored securely');
      console.log('\n🔐 Security Notes:');
      console.log('   🔒 Refresh token is stored with restricted file permissions');
      console.log('   ⏰ Access token will be refreshed automatically');
      console.log('   🚫 No sensitive tokens are stored in .env file');
      console.log('\n🎯 Next Steps:');
      console.log('   1. Test the OAuth setup: npm run test-oauth');
      console.log('   2. Configure your MCP client (Claude Desktop, VS Code, etc.)');
      console.log('   3. Use your MCP tools with confidence!');
      
    } else {
      console.error('\n❌ OAuth setup failed: No refresh token received');
      console.log('🔧 This might happen if:');
      console.log('   - Your OAuth app is not configured correctly');
      console.log('   - You denied the authorization request');
      console.log('   - There was a network error during the process');
      console.log('\n📖 Please check the OAUTH_SETUP.md guide and try again.');
      process.exit(1);
    }
    
  } catch (error) {
    console.error('\n❌ OAuth setup failed:', error.message);
    
    if (error.message.includes('EADDRINUSE')) {
      console.log('\n🔧 Port already in use. Please:');
      console.log('   1. Close any other applications using port 3001');
      console.log('   2. Wait a moment and try again');
    } else if (error.message.includes('CLIENT_ID') || error.message.includes('CLIENT_SECRET')) {
      console.log('\n🔧 OAuth credential issue. Please:');
      console.log('   1. Check your .env file has correct credentials');
      console.log('   2. Verify credentials in Google Cloud Console');
      console.log('   3. Make sure OAuth consent screen is configured');
    } else {
      console.log('\n🔧 Please check:');
      console.log('   1. Your internet connection');
      console.log('   2. Google Cloud Console OAuth configuration');
      console.log('   3. That you authorized the application in the browser');
    }
    
    console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
    process.exit(1);
  }
}

// Handle command line arguments
if (process.argv.includes('--help') || process.argv.includes('-h')) {
  console.log('📖 Google Apps Script OAuth Setup');
  console.log('\nUsage:');
  console.log('  node oauth-setup.js           # Run OAuth setup');
  console.log('  node oauth-setup.js --force   # Force new OAuth setup (overwrite existing tokens)');
  console.log('  node oauth-setup.js --clear   # Clear stored tokens');
  console.log('  node oauth-setup.js --info    # Show token information');
  console.log('  node oauth-setup.js --help    # Show this help');
  process.exit(0);
}

if (process.argv.includes('--info')) {
  const tokenManager = new TokenManager();
  const tokenInfo = tokenManager.getTokenInfo();
  
  console.log('🔍 Token Information:');
  console.log('=====================\n');
  
  if (tokenInfo.hasTokens) {
    console.log('✅ Tokens found');
    console.log(`📁 Location: ${tokenInfo.location}`);
    console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
    console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
    console.log(`📊 Status: ${tokenInfo.status}`);
    console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
  } else {
    console.log('❌ No tokens found');
    console.log(`📁 Expected location: ${tokenInfo.location}`);
    console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
  }
  
  process.exit(0);
}

// Run setup
setupOAuth().catch((error) => {
  console.error('💥 Unexpected error:', error);
  process.exit(1);
});
      
    } else {
      console.log('\n⚠️ OAuth completed but no refresh token received.');
      console.log('🔄 You may need to revoke and re-authorize the application.');
      console.log('📖 Check the Google Cloud Console for your OAuth settings.');
    }
    
  } catch (error) {
    console.error('\n❌ OAuth setup failed:', error.message);
    console.log('\n🔧 Troubleshooting:');
    console.log('   1. Check your internet connection');
    console.log('   2. Verify your CLIENT_ID and CLIENT_SECRET are correct');
    console.log('   3. Ensure the redirect URI is registered in Google Cloud Console');
    console.log('   4. Make sure Google Apps Script API is enabled');
    console.log('   5. Try revoking and re-creating your OAuth credentials');
    console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
    process.exit(1);
  }
}

// Run setup if this script is executed directly
console.log('🔍 Debug: process.argv[1]:', process.argv[1]);
console.log('🔍 Debug: endsWith check:', process.argv[1] && process.argv[1].endsWith('oauth-setup.js'));

if (process.argv[1] && process.argv[1].endsWith('oauth-setup.js')) {
  console.log('🚀 Starting OAuth setup...');
  setupOAuth();
} else {
  console.log('❌ Script not executed directly, skipping setup');
}

```

--------------------------------------------------------------------------------
/OAUTH_SETUP.md:
--------------------------------------------------------------------------------

```markdown
# Google Apps Script API OAuth Setup Guide

This guide will help you set up OAuth authentication for the Google Apps Script API MCP server.

## Prerequisites

1. **Google Cloud Project**: You need a Google Cloud Project with Google Apps Script API enabled
2. **OAuth 2.0 Credentials**: You need Client ID and Client Secret from Google Cloud Console
3. **Node.js**: Make sure you have Node.js 16+ installed

## Step 1: Google Cloud Console Setup

### 1.1 Create/Select a Project
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Note your project ID

### 1.2 Enable Google Apps Script API
1. In the Google Cloud Console, go to **APIs & Services** > **Library**
2. Search for "Google Apps Script API"
3. Click on it and press **Enable**

### 1.3 Create OAuth 2.0 Credentials
1. Go to **APIs & Services** > **Credentials**
2. Click **+ CREATE CREDENTIALS** > **OAuth 2.0 Client IDs**
3. If prompted, configure the OAuth consent screen:
   - Choose **External** (unless you're in a Google Workspace organization)
   - Fill in the required fields:
     - App name: "Google Apps Script MCP Server"
     - User support email: Your email
     - Developer contact information: Your email
   - Add scopes (optional for testing):
     - `https://www.googleapis.com/auth/script.projects`
     - `https://www.googleapis.com/auth/script.projects.readonly`
     - `https://www.googleapis.com/auth/script.deployments.readonly`
     - `https://www.googleapis.com/auth/script.metrics`
4. For Application Type, choose **Web application**
5. Add authorized redirect URIs:
   - `http://localhost:3001/oauth/callback`
6. Click **Create**
7. Copy your **Client ID** and **Client Secret**

## Step 2: Configure Environment Variables

### 2.1 Update .env File
Edit the `.env` file in your project root and add your credentials:

```env
# Google Apps Script API OAuth Configuration
GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here
GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here
GOOGLE_APP_SCRIPT_API_REFRESH_TOKEN=your_refresh_token_here

# OAuth Configuration
GOOGLE_APP_SCRIPT_API_REDIRECT_URI=http://localhost:3001/oauth/callback
```

Replace:
- `your_client_id_here` with your actual Client ID
- `your_client_secret_here` with your actual Client Secret
- Keep `your_refresh_token_here` as is (we'll get this in the next step)

## Step 3: Run OAuth Setup

### 3.1 Install Dependencies
```bash
npm install
```

### 3.2 Run OAuth Setup Script
```bash
npm run setup-oauth
```

This script will:
1. Validate your credentials
2. Open your browser for OAuth authorization
3. Handle the OAuth callback
4. Automatically update your `.env` file with the refresh token

### 3.3 Authorize the Application
1. Your browser will open automatically
2. Sign in to your Google account
3. Review and accept the permissions
4. The browser will show a success message
5. Return to your terminal - the setup should be complete

## Step 4: Test Your Setup

### 4.1 Run OAuth Test
```bash
npm run test-oauth
```

This will verify that your OAuth setup is working correctly.

### 4.2 Test with MCP Client
You can now use your MCP tools with any MCP-compatible client like:
- Claude Desktop
- Postman
- Other MCP clients

## Troubleshooting

### Common Issues

#### 1. "redirect_uri_mismatch" Error
- Make sure you added `http://localhost:3001/oauth/callback` to your authorized redirect URIs in Google Cloud Console
- Check that the URI is exactly correct (no trailing slash, correct port)

#### 2. "access_denied" Error
- Make sure you're signed in to the correct Google account
- Check that the OAuth consent screen is properly configured
- Verify that the Google Apps Script API is enabled

#### 3. "invalid_client" Error
- Double-check your Client ID and Client Secret in the `.env` file
- Make sure there are no extra spaces or quotes around the values

#### 4. Port Already in Use
- Make sure port 3001 is not being used by another application
- If needed, you can modify the port in `lib/oauth-helper.js` and update your redirect URI accordingly

#### 5. Refresh Token Not Saved
- Make sure the OAuth setup script has write permissions to the `.env` file
- Check that the `.env` file exists and is in the correct location

### Advanced Configuration

#### Custom Port
If you need to use a different port, update these files:
1. `lib/oauth-helper.js` - Change the `PORT` constant
2. `.env` - Update `GOOGLE_APP_SCRIPT_API_REDIRECT_URI`
3. Google Cloud Console - Update the authorized redirect URI

#### Custom Scopes
To modify the required permissions, edit the `SCOPES` array in `lib/oauth-helper.js`.

Available scopes:
- `https://www.googleapis.com/auth/script.projects` - Read/write access to Apps Script projects
- `https://www.googleapis.com/auth/script.projects.readonly` - Read-only access to Apps Script projects
- `https://www.googleapis.com/auth/script.deployments.readonly` - Read access to Apps Script deployments
- `https://www.googleapis.com/auth/script.metrics` - Access to Apps Script execution metrics

## Security Notes

1. **Keep your credentials secure**: Never commit your `.env` file to version control
2. **Refresh token**: The refresh token allows long-term access - treat it like a password
3. **Client secret**: Keep your client secret confidential
4. **Production use**: For production, consider using more secure token storage than environment variables

## Support

If you encounter issues:
1. Check the troubleshooting section above
2. Review the Google Apps Script API documentation
3. Check Google Cloud Console for any error messages
4. Ensure all prerequisites are met

For more information about Google Apps Script API:
- [Google Apps Script API Documentation](https://developers.google.com/apps-script/api)
- [OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server)
2. Google Apps Script API enabled
3. OAuth 2.0 credentials configured

## Step 1: Create OAuth 2.0 Credentials

1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Select your project (or create a new one)
3. Navigate to **APIs & Services** > **Credentials**
4. Click **+ CREATE CREDENTIALS** > **OAuth client ID**
5. Select **Desktop application** as the application type
6. Give it a name (e.g., "Google Apps Script MCP Client")
7. Click **Create**
8. Download the JSON file containing your credentials

## Step 2: Enable Required APIs

Make sure the following APIs are enabled in your GCP project:

1. Google Apps Script API
2. Google Drive API (if accessing script files)

To enable APIs:
1. Go to **APIs & Services** > **Library**
2. Search for each API and click **Enable**

## Step 3: Get a Refresh Token

You'll need to obtain a refresh token through the OAuth 2.0 flow. Here's a simple way to do it:

### Option A: Using Google OAuth 2.0 Playground

1. Go to [Google OAuth 2.0 Playground](https://developers.google.com/oauthplayground/)
2. Click the gear icon (⚙️) in the top right
3. Check **Use your own OAuth credentials**
4. Enter your **OAuth Client ID** and **OAuth Client secret**
5. In the left panel, find and select **Google Apps Script API v1**
6. Select the scope: `https://www.googleapis.com/auth/script.projects`
7. Click **Authorize APIs**
8. Complete the authorization flow
9. Click **Exchange authorization code for tokens**
10. Copy the **Refresh token** from the response

### Option B: Using curl (Advanced)

```bash
# Step 1: Get authorization code (open this URL in browser)
https://accounts.google.com/o/oauth2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/script.projects&response_type=code

# Step 2: Exchange code for tokens
curl -X POST https://oauth2.googleapis.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=AUTHORIZATION_CODE_FROM_STEP_1" \
  -d "grant_type=authorization_code" \
  -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob"
```

## Step 4: Configure Environment Variables

Update your `.env` file with the OAuth credentials:

```env
# Google Apps Script API OAuth Configuration
GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here
GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here
GOOGLE_APP_SCRIPT_API_REFRESH_TOKEN=your_refresh_token_here
```

Replace the placeholder values with:
- `your_client_id_here`: Your OAuth 2.0 Client ID
- `your_client_secret_here`: Your OAuth 2.0 Client Secret
- `your_refresh_token_here`: The refresh token obtained in Step 3

## Required Scopes

The following OAuth scopes are required for different operations:

- `https://www.googleapis.com/auth/script.projects` - Manage Google Apps Script projects
- `https://www.googleapis.com/auth/script.processes` - View Google Apps Script processes
- `https://www.googleapis.com/auth/script.deployments` - Manage deployments
- `https://www.googleapis.com/auth/drive` - Access Drive files (if needed)

## Security Notes

1. **Keep credentials secure**: Never commit your `.env` file to version control
2. **Refresh token rotation**: Google may rotate refresh tokens periodically
3. **Access token expiry**: Access tokens typically expire after 1 hour
4. **Scope principle**: Only request the minimum scopes needed for your application

## Troubleshooting

### Common Issues

1. **"Invalid credentials"**: Check that your client ID and secret are correct
2. **"Invalid scope"**: Ensure the required APIs are enabled in your GCP project
3. **"Refresh token expired"**: You may need to re-authorize and get a new refresh token
4. **"Project not found"**: Make sure the script project exists and you have access to it

### Testing Authentication

You can test your OAuth setup by running a simple API call:

```javascript
import { getOAuthAccessToken } from './lib/oauth-helper.js';

async function testAuth() {
  try {
    const token = await getOAuthAccessToken();
    console.log('OAuth authentication successful!');
    console.log('Access token received:', token.substring(0, 20) + '...');
  } catch (error) {
    console.error('OAuth authentication failed:', error.message);
  }
}

testAuth();
```

## Migration from API Key

If you were previously using an API key, the OAuth implementation provides:

1. **Better security**: OAuth tokens are time-limited and can be revoked
2. **Fine-grained access**: Specific scopes control what the application can access
3. **User context**: Operations are performed in the context of the authenticated user
4. **Compliance**: Meets Google's authentication requirements for sensitive APIs

The OAuth helper automatically handles token refresh, so your application will continue working even after access tokens expire.

```

--------------------------------------------------------------------------------
/test/deploy-complete-webapp.js:
--------------------------------------------------------------------------------

```javascript
import { getOAuthAccessToken } from '../lib/oauth-helper.js';

const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';

async function updateScriptWithWebAppConfig() {
  try {
    const token = await getOAuthAccessToken();
    
    console.log('Updating script with web app configuration...');
    
    // Updated manifest with web app configuration
    const updatedManifest = {
      "timeZone": "America/New_York",
      "dependencies": {},
      "exceptionLogging": "STACKDRIVER", 
      "runtimeVersion": "V8",
      "webapp": {
        "access": "ANYONE",
        "executeAs": "USER_ACCESSING"
      }
    };

    // Prepare the updated script content
    const scriptContent = {
      files: [
        {
          name: "appsscript",
          type: "JSON",
          source: JSON.stringify(updatedManifest, null, 2)
        },
        {
          name: "code",
          type: "SERVER_JS",
          source: `/**
 * Serves the HTML page when the web app is accessed
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('Hello World App')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Server-side function that can be called from the client
 */
function getGreeting(name) {
  if (!name) {
    name = 'World';
  }
  return \`Hello, \${name}! This is a Google Apps Script web app.\`;
}

/**
 * Function to get current timestamp
 */
function getCurrentTime() {
  return new Date().toLocaleString();
}`
        },
        {
          name: "index",
          type: "HTML",
          source: `<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Hello World - Google Apps Script</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      color: white;
    }
    
    .container {
      background: rgba(255, 255, 255, 0.1);
      backdrop-filter: blur(10px);
      border-radius: 15px;
      padding: 30px;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
      text-align: center;
    }
    
    h1 {
      font-size: 2.5em;
      margin-bottom: 20px;
      text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
    }
    
    .input-group {
      margin: 20px 0;
    }
    
    input[type="text"] {
      padding: 12px 20px;
      font-size: 16px;
      border: none;
      border-radius: 25px;
      width: 250px;
      text-align: center;
      background: rgba(255, 255, 255, 0.9);
      color: #333;
    }
    
    button {
      background: #ff6b6b;
      color: white;
      border: none;
      padding: 12px 25px;
      font-size: 16px;
      border-radius: 25px;
      cursor: pointer;
      margin: 10px;
      transition: all 0.3s ease;
    }
    
    button:hover {
      background: #ff5252;
      transform: translateY(-2px);
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    }
    
    .result {
      margin: 20px 0;
      padding: 20px;
      background: rgba(255, 255, 255, 0.1);
      border-radius: 10px;
      font-size: 18px;
      min-height: 50px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .loading {
      display: none;
      color: #ffd700;
    }
    
    .timestamp {
      font-size: 14px;
      color: rgba(255, 255, 255, 0.8);
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>🌟 Hello World App 🌟</h1>
    <p>Welcome to your Google Apps Script web application!</p>
    
    <div class="input-group">
      <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
    </div>
    
    <div>
      <button onclick="sayHello()">Say Hello</button>
      <button onclick="getTime()">Get Current Time</button>
    </div>
    
    <div id="result" class="result">
      Click a button to see the magic! ✨
    </div>
    
    <div id="loading" class="loading">Loading...</div>
    
    <div id="timestamp" class="timestamp"></div>
  </div>

  <script>
    function sayHello() {
      showLoading();
      const name = document.getElementById('nameInput').value;
      
      google.script.run
        .withSuccessHandler(onSuccess)
        .withFailureHandler(onFailure)
        .getGreeting(name);
    }
    
    function getTime() {
      showLoading();
      
      google.script.run
        .withSuccessHandler(onTimeSuccess)
        .withFailureHandler(onFailure)
        .getCurrentTime();
    }
    
    function onSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 20px; font-weight: bold;">
          \${result}
        </div>
      \`;
    }
    
    function onTimeSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 18px;">
          🕐 Current server time: <br>
          <strong>\${result}</strong>
        </div>
      \`;
    }
    
    function onFailure(error) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="color: #ff6b6b;">
          ❌ Error: \${error.message}
        </div>
      \`;
    }
    
    function showLoading() {
      document.getElementById('loading').style.display = 'block';
      document.getElementById('result').style.display = 'none';
    }
    
    function hideLoading() {
      document.getElementById('loading').style.display = 'none';
      document.getElementById('result').style.display = 'flex';
    }
    
    // Initialize the page
    window.onload = function() {
      document.getElementById('timestamp').innerHTML = 
        \`Page loaded at: \${new Date().toLocaleString()}\`;
    };
  </script>
</body>
</html>`
        }
      ]
    };

    console.log('Updated manifest:', JSON.stringify(updatedManifest, null, 2));

    // Update the script content
    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/content`, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(scriptContent)
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('Error response:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('✅ Script updated successfully');
    
    return data;
  } catch (error) {
    console.error('Error updating script:', error);
    return null;
  }
}

async function createNewVersion() {
  try {
    const token = await getOAuthAccessToken();
    
    console.log('Creating new version...');
    
    const versionData = {
      description: "Version with web app configuration"
    };

    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/versions`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(versionData)
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('Error response:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('✅ New version created:', data.versionNumber);
    
    return data;
  } catch (error) {
    console.error('Error creating version:', error);
    return null;
  }
}

async function createWebAppDeployment(versionNumber) {
  try {
    const token = await getOAuthAccessToken();
    
    console.log('Creating web app deployment...');
    
    const deploymentConfig = {
      description: "Hello World Web App - Public Access",
      manifestFileName: "appsscript",
      versionNumber: versionNumber
    };

    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(deploymentConfig)
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('Error response:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('✅ Deployment created:', JSON.stringify(data, null, 2));
    
    return data;
  } catch (error) {
    console.error('Error creating deployment:', error);
    return null;
  }
}

async function getDeploymentDetails(deploymentId) {
  try {
    const token = await getOAuthAccessToken();
      console.log(`Getting deployment details for: ${deploymentId}`);
    
    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments/${deploymentId}`, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json'
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('Error response:', errorText);      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('Deployment details:', JSON.stringify(data, null, 2));
    
    if (data.entryPoints && data.entryPoints[0] && data.entryPoints[0].webApp) {
      console.log('\n🎉 SUCCESS! Your web app is deployed!');
      console.log('Web App URL:', data.entryPoints[0].webApp.url);
      console.log('Access Level:', data.entryPoints[0].webApp.access);
      console.log('Execute As:', data.entryPoints[0].webApp.executeAs);
      console.log('\n📱 You can now access your Hello World app at the URL above!');
    } else {
      console.log('\n⚠️  No web app entry point found.');
    }
    
    return data;
  } catch (error) {
    console.error('Error getting deployment details:', error);
    return null;
  }
}

async function main() {
  console.log('=== Creating Google Apps Script Web App ===\n');
  
  // Step 1: Update script with web app configuration
  console.log('Step 1: Updating script with web app configuration...');
  const updateResult = await updateScriptWithWebAppConfig();
  if (!updateResult) return;
  
  console.log('\n' + '='.repeat(50) + '\n');
  
  // Step 2: Create a new version
  console.log('Step 2: Creating a new version...');
  const versionResult = await createNewVersion();
  if (!versionResult) return;
  
  console.log('\n' + '='.repeat(50) + '\n');
  
  // Step 3: Create deployment with the new version
  console.log('Step 3: Creating deployment...');
  const deploymentResult = await createWebAppDeployment(versionResult.versionNumber);
  if (!deploymentResult) return;
  
  console.log('\n' + '='.repeat(50) + '\n');
  
  // Step 4: Get deployment details to see the web app URL
  console.log('Step 4: Getting deployment details...');
  await getDeploymentDetails(deploymentResult.deploymentId);
}

main().catch(console.error);

```

--------------------------------------------------------------------------------
/test/update-and-deploy-dark-theme.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * Update script content to dark theme and deploy as web app
 */

import { getOAuthAccessToken } from '../lib/oauth-helper.js';

const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';

// Dark theme HTML content
const darkThemeHTML = `<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <title>Hello World App via MCP - Dark Theme</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      max-width: 600px;
      margin: 50px auto;
      padding: 20px;
      background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
      color: #e0e0e0;
      text-align: center;
      min-height: 100vh;
      box-sizing: border-box;
    }
    .container {
      background: rgba(255, 255, 255, 0.05);
      border: 1px solid rgba(255, 255, 255, 0.1);
      border-radius: 20px;
      padding: 40px;
      backdrop-filter: blur(15px);
      box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.3);
    }
    h1 {
      color: #ffffff;
      margin-bottom: 30px;
      font-size: 2.5em;
      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
    }
    p {
      color: #b0b0b0;
      font-size: 1.1em;
      margin-bottom: 30px;
    }
    input {
      padding: 14px;
      margin: 10px;
      border: 2px solid #444;
      border-radius: 10px;
      font-size: 16px;
      width: 250px;
      background: #2a2a2a;
      color: #e0e0e0;
      transition: border-color 0.3s, box-shadow 0.3s;
    }
    input:focus {
      outline: none;
      border-color: #64b5f6;
      box-shadow: 0 0 0 3px rgba(100, 181, 246, 0.2);
    }
    input::placeholder {
      color: #888;
    }
    button {
      background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
      color: white;
      border: none;
      padding: 14px 28px;
      margin: 10px;
      border-radius: 10px;
      cursor: pointer;
      font-size: 16px;
      font-weight: 600;
      transition: all 0.3s;
      box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
    }
    button:hover {
      background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
      transform: translateY(-2px);
      box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
    }
    button:active {
      transform: translateY(0);
    }
    .result {
      margin-top: 25px;
      padding: 25px;
      background: rgba(255, 255, 255, 0.08);
      border: 1px solid rgba(255, 255, 255, 0.12);
      border-radius: 12px;
      min-height: 60px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #f0f0f0;
      font-size: 18px;
    }
    .loading {
      display: none;
      color: #64b5f6;
      font-size: 18px;
      font-weight: 600;
    }
    .timestamp {
      margin-top: 15px;
      font-size: 0.9em;
      color: #888;
      font-style: italic;
    }
    .emoji {
      font-size: 1.2em;
      margin: 0 5px;
    }
    @keyframes pulse {
      0% { opacity: 0.6; }
      50% { opacity: 1; }
      100% { opacity: 0.6; }
    }
    .loading {
      animation: pulse 1.5s infinite;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1><span class="emoji">🌙</span> Dark Theme MCP App <span class="emoji">🌙</span></h1>
    <p>This sleek dark-themed web app was created using MCP (Model Context Protocol) tools!</p>
    
    <div class="input-group">
      <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
    </div>
    
    <div>
      <button onclick="sayHello()">🗨️ Say Hello</button>
      <button onclick="getTime()">🕒 Get Current Time</button>
    </div>
    
    <div id="result" class="result">
      Click a button to experience the dark MCP magic! ✨
    </div>
    
    <div id="loading" class="loading">⏳ Loading...</div>
    
    <div id="timestamp" class="timestamp"></div>
  </div>

  <script>
    function sayHello() {
      showLoading();
      const name = document.getElementById('nameInput').value;
      
      google.script.run
        .withSuccessHandler(onSuccess)
        .withFailureHandler(onFailure)
        .getGreeting(name);
    }
    
    function getTime() {
      showLoading();
      
      google.script.run
        .withSuccessHandler(onTimeSuccess)
        .withFailureHandler(onFailure)
        .getCurrentTime();
    }
    
    function onSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 20px; font-weight: bold; color: #64b5f6;">
          \${result}
        </div>
      \`;
      updateTimestamp();
    }
    
    function onTimeSuccess(result) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="font-size: 18px; color: #81c784;">
          🕒 Current Time: <strong style="color: #fff;">\${result}</strong>
        </div>
      \`;
      updateTimestamp();
    }
    
    function onFailure(error) {
      hideLoading();
      document.getElementById('result').innerHTML = \`
        <div style="color: #f48fb1;">
          ❌ Error: \${error.message || 'Something went wrong!'}
        </div>
      \`;
      updateTimestamp();
    }
    
    function showLoading() {
      document.getElementById('loading').style.display = 'block';
      document.getElementById('result').style.display = 'none';
    }
    
    function hideLoading() {
      document.getElementById('loading').style.display = 'none';
      document.getElementById('result').style.display = 'flex';
    }
    
    function updateTimestamp() {
      document.getElementById('timestamp').textContent = 
        'Last updated: ' + new Date().toLocaleTimeString();
    }
    
    // Initial timestamp
    updateTimestamp();
    
    // Add some interactive effects
    document.addEventListener('DOMContentLoaded', function() {
      const buttons = document.querySelectorAll('button');
      buttons.forEach(button => {
        button.addEventListener('mouseenter', function() {
          this.style.transform = 'translateY(-2px) scale(1.02)';
        });
        button.addEventListener('mouseleave', function() {
          this.style.transform = 'translateY(0) scale(1)';
        });
      });
    });
  </script>
</body>
</html>`;

// Updated server-side code
const serverCode = `/**
 * Serves the HTML page when the web app is accessed
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('Hello World App via MCP - Dark Theme')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Server-side function that can be called from the client
 */
function getGreeting(name) {
  if (!name) {
    name = 'World';
  }
  return \`Hello, \${name}! This dark-themed web app was created via MCP tools.\`;
}

/**
 * Get current time
 */
function getCurrentTime() {
  return new Date().toLocaleString();
}`;

// Configuration
const appsScriptConfig = `{
  "timeZone": "America/New_York",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "access": "ANYONE",
    "executeAs": "USER_ACCESSING"
  }
}`;

async function updateScriptContent() {
  try {
    const token = await getOAuthAccessToken();
    console.log('🔄 Updating script content with dark theme...');
    
    const files = [
      {
        name: "appsscript",
        type: "JSON",
        source: appsScriptConfig
      },
      {
        name: "code",
        type: "SERVER_JS", 
        source: serverCode
      },
      {
        name: "index",
        type: "HTML",
        source: darkThemeHTML
      }
    ];

    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/content`, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({ files })
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('❌ Error updating content:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('✅ Script content updated successfully!');
    return data;
  } catch (error) {
    console.error('❌ Error updating script content:', error);
    return null;
  }
}

async function createVersion() {
  try {
    const token = await getOAuthAccessToken();
    console.log('📦 Creating new version...');
    
    const versionData = {
      description: "Dark theme version via MCP"
    };

    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/versions`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(versionData)
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('❌ Error creating version:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log(`✅ Version ${data.versionNumber} created successfully!`);
    return data;
  } catch (error) {
    console.error('❌ Error creating version:', error);
    return null;
  }
}

async function createDeployment(versionNumber) {
  try {
    const token = await getOAuthAccessToken();
    console.log('🚀 Creating web app deployment...');
    
    const deploymentConfig = {
      description: "Dark Theme MCP Web App - Public Access",
      manifestFileName: "appsscript",
      versionNumber: versionNumber
    };

    const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(deploymentConfig)
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error('❌ Error creating deployment:', errorText);
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }

    const data = await response.json();
    console.log('✅ Deployment created successfully!');
    
    // Extract web app URL
    if (data.entryPoints && data.entryPoints[0] && data.entryPoints[0].webApp) {
      const webAppUrl = data.entryPoints[0].webApp.url;
      console.log('🌐 Web App URL:', webAppUrl);
    }
    
    console.log('📋 Deployment Details:', JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error('❌ Error creating deployment:', error);
    return null;
  }
}

async function main() {
  console.log('🌙 Starting dark theme update and deployment...');
  console.log('='.repeat(60));
  
  // Step 1: Update script content
  const updateResult = await updateScriptContent();
  if (!updateResult) {
    console.log('❌ Failed to update content. Stopping.');
    return;
  }
  
  console.log('\n' + '='.repeat(60) + '\n');
  
  // Step 2: Create new version
  const versionResult = await createVersion();
  if (!versionResult) {
    console.log('❌ Failed to create version. Stopping.');
    return;
  }
  
  console.log('\n' + '='.repeat(60) + '\n');
  
  // Step 3: Create deployment
  const deploymentResult = await createDeployment(versionResult.versionNumber);
  if (!deploymentResult) {
    console.log('❌ Failed to create deployment. Stopping.');
    return;
  }
  
  console.log('\n🎉 Process completed successfully!');
  console.log('Your dark-themed web app is now deployed and accessible.');
}

main().catch(console.error);

```

--------------------------------------------------------------------------------
/lib/oauth-helper.js:
--------------------------------------------------------------------------------

```javascript
/**
 * OAuth authentication helper for Google Apps Script API
 */

import 'dotenv/config';
import { google } from 'googleapis';
import { createServer } from 'http';
import open from 'open';
import { URL } from 'url';
import { createConnection } from 'net';
import { TokenManager } from './tokenManager.js';
import { logger } from './logger.js';

// Configuration - Comprehensive scopes for all Google APIs
const SCOPES = [
  // Google Apps Script API - Full access
  'https://www.googleapis.com/auth/script.projects',
  'https://www.googleapis.com/auth/script.projects.readonly',
  'https://www.googleapis.com/auth/script.deployments',
  'https://www.googleapis.com/auth/script.deployments.readonly',
  'https://www.googleapis.com/auth/script.metrics',
  'https://www.googleapis.com/auth/script.processes',
  'https://www.googleapis.com/auth/script.webapp.deploy'
  
  
];

const REDIRECT_URI = 'http://localhost:3001/oauth/callback';
const PORT = 3001;

// Token manager instance
const tokenManager = new TokenManager();

/**
 * Finds an available port starting from the given port
 * @param {number} startPort - Port to start checking from
 * @returns {Promise<number>} Available port number
 */
async function findAvailablePort(startPort = PORT) {
  return new Promise((resolve) => {
    const server = createServer();
    
    server.listen(startPort, () => {
      const port = server.address().port;
      server.close(() => {
        resolve(port);
      });
    });
    
    server.on('error', () => {
      findAvailablePort(startPort + 1).then(resolve);
    });
  });
}

/**
 * Creates and configures OAuth2 client
 * @returns {OAuth2Client} Configured OAuth2 client
 */
function createOAuth2Client() {
  const clientId = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID;
  const clientSecret = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    logger.error('AUTH', 'Missing required OAuth credentials', {
      hasClientId: !!clientId,
      hasClientSecret: !!clientSecret
    });
    throw new Error('Missing required OAuth credentials: GOOGLE_APP_SCRIPT_API_CLIENT_ID and GOOGLE_APP_SCRIPT_API_CLIENT_SECRET must be set in environment variables');
  }

  logger.info('AUTH', 'Creating OAuth2 client', {
    clientId: clientId.substring(0, 20) + '...',
    redirectUri: REDIRECT_URI,
    scopeCount: SCOPES.length,
    scopes: SCOPES
  });

  console.log('🔐 Creating OAuth2 client...');
  console.log('   - Client ID:', clientId);
  console.log('   - Redirect URI:', REDIRECT_URI);
  console.log('   - Scopes:', SCOPES.length, 'permissions');

  return new google.auth.OAuth2(clientId, clientSecret, REDIRECT_URI);
}

/**
 * Starts OAuth flow with browser automation
 * @returns {Promise<Object>} OAuth tokens
 */
async function startOAuthFlow() {
  console.log('🚀 Starting OAuth flow...');
  
  const oAuth2Client = createOAuth2Client();
  
  return new Promise(async (resolve, reject) => {
    try {
      // Use the exact port that matches Google Cloud Console configuration
      const callbackPort = PORT; // Must match Google Cloud Console redirect URI
      
      console.log(`🌐 Starting OAuth callback server on port ${callbackPort}`);
      console.log(`🔗 Redirect URI: ${REDIRECT_URI}`);
      
      // Create temporary HTTP server for OAuth callback
      const server = createServer(async (req, res) => {
        console.log('📥 OAuth callback received:', req.url);
        
        try {
          const url = new URL(req.url, `http://localhost:${callbackPort}`);
          
          if (url.pathname === '/oauth/callback') {
            const code = url.searchParams.get('code');
            const error = url.searchParams.get('error');
            
            if (error) {
              console.error('❌ OAuth error:', error);
              res.writeHead(400, { 'Content-Type': 'text/html' });
              res.end(`
                <html>
                  <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
                    <h2 style="color: #dc3545;">❌ Authentication Failed</h2>
                    <p>Error: ${error}</p>
                    <p>You can close this window.</p>
                  </body>
                </html>
              `);
              server.close();
              reject(new Error(`OAuth error: ${error}`));
              return;
            }
            
            if (!code) {
              console.error('❌ No authorization code received');
              res.writeHead(400, { 'Content-Type': 'text/html' });
              res.end(`
                <html>
                  <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
                    <h2 style="color: #dc3545;">❌ No Authorization Code</h2>
                    <p>No authorization code received from Google.</p>
                    <p>You can close this window.</p>
                  </body>
                </html>
              `);
              server.close();
              reject(new Error('No authorization code received'));
              return;
            }
            
            console.log('🔄 Exchanging authorization code for tokens...');
            console.log('🔑 Authorization code:', code.substring(0, 20) + '...');
              try {
              const { tokens: newTokens } = await oAuth2Client.getToken(code);
              
              console.log('✅ Token exchange successful!');
              console.log('🎟️ Token details:');
              console.log('   - Access token:', newTokens.access_token ? '✅ Received' : '❌ Missing');
              console.log('   - Refresh token:', newTokens.refresh_token ? '✅ Received' : '❌ Missing');
              console.log('   - Token type:', newTokens.token_type || 'Not specified');
              console.log('   - Expires in:', newTokens.expiry_date ? new Date(newTokens.expiry_date).toISOString() : 'No expiry');
              console.log('   - Scope:', newTokens.scope || 'Not specified');
              
              // Success response
              res.writeHead(200, { 'Content-Type': 'text/html' });
              res.end(`
                <html>
                  <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
                    <h2 style="color: #28a745;">✅ Authentication Successful!</h2>
                    <p>You have been successfully authenticated with Google Apps Script API.</p>
                    <p><strong>Access Token:</strong> ${newTokens.access_token ? 'Received ✅' : 'Missing ❌'}</p>
                    <p><strong>Refresh Token:</strong> ${newTokens.refresh_token ? 'Received ✅' : 'Missing ❌'}</p>
                    <p><strong>You can now close this window and return to your application.</strong></p>
                    <script>
                      setTimeout(() => {
                        window.close();
                      }, 5000);
                    </script>
                  </body>
                </html>
              `);
              
              server.close();
              resolve(newTokens);
              
            } catch (tokenError) {
              console.error('❌ Error exchanging code for tokens:', tokenError);
              res.writeHead(500, { 'Content-Type': 'text/html' });
              res.end(`
                <html>
                  <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
                    <h2 style="color: #dc3545;">❌ Token Exchange Failed</h2>
                    <p>Failed to exchange authorization code for tokens.</p>
                    <pre style="text-align: left; background: #f8f9fa; padding: 20px;">${tokenError.message}</pre>
                    <p>You can close this window.</p>
                  </body>
                </html>
                `);
              server.close();
              reject(tokenError);
            }
          } else {
            // Handle other paths
            res.writeHead(404, { 'Content-Type': 'text/plain' });
            res.end('Not Found');
          }
        } catch (err) {
          console.error('❌ Server error:', err);
          res.writeHead(500, { 'Content-Type': 'text/plain' });
          res.end('Internal Server Error');
          server.close();
          reject(err);
        }
      });

      // Start server on the specific port
      server.listen(callbackPort, () => {
        console.log(`🌐 OAuth callback server started on port ${callbackPort}`);
        
        // Generate authorization URL
        const authUrl = oAuth2Client.generateAuthUrl({
          access_type: 'offline',
          scope: SCOPES,
          prompt: 'consent'
        });
        
        console.log('🔗 Opening OAuth URL in browser...');
        console.log('📋 OAuth URL:', authUrl);
        
        // Open browser
        open(authUrl).catch(err => {
          console.error('❌ Failed to open browser:', err);
          console.log('🔗 Please manually open this URL in your browser:');
          console.log(authUrl);
        });
      });

      // Handle server errors
      server.on('error', (err) => {
        console.error('❌ Server error:', err);
        if (err.code === 'EADDRINUSE') {
          reject(new Error(`Port ${callbackPort} is already in use. Please close other applications using this port or update your Google Cloud Console redirect URI to use a different port.`));
        } else {
          reject(err);
        }
      });

      // Timeout after 5 minutes
      setTimeout(() => {
        server.close();
        reject(new Error('OAuth flow timed out after 5 minutes'));
      }, 5 * 60 * 1000);
      
    } catch (error) {
      reject(error);
    }
  });
}

/**
 * Gets an OAuth access token using TokenManager
 * @returns {Promise<string>} Access token
 */
export async function getOAuthAccessToken() {
  logger.info('AUTH', 'Requesting OAuth access token');
  console.log('🔐 Getting OAuth access token...');
  
  const clientId = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID;
  const clientSecret = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    logger.error('AUTH', 'Missing OAuth credentials', {
      hasClientId: !!clientId,
      hasClientSecret: !!clientSecret
    });
    throw new Error('Missing required OAuth credentials: GOOGLE_APP_SCRIPT_API_CLIENT_ID and GOOGLE_APP_SCRIPT_API_CLIENT_SECRET must be set in environment variables');
  }
  
  try {
    const startTime = Date.now();
    logger.debug('AUTH', 'Getting valid access token from token manager');
    
    const accessToken = await tokenManager.getValidAccessToken(clientId, clientSecret);
    const duration = Date.now() - startTime;
    
    logger.info('AUTH', 'Access token obtained successfully', {
      duration: `${duration}ms`,
      tokenLength: accessToken ? accessToken.length : 0,
      tokenPrefix: accessToken ? accessToken.substring(0, 10) + '...' : null
    });
    
    console.log('✅ Access token obtained successfully');
    return accessToken;
  } catch (error) {
    logger.error('AUTH', 'Failed to obtain access token', {
      error: {
        message: error.message,
        stack: error.stack
      },
      hasStoredTokens: tokenManager.hasStoredTokens()
    });
    
    if (error.message.includes('No tokens found')) {
      console.error('❌ No OAuth tokens found.');
      console.log('💡 Please run the OAuth setup first:');
      console.log('   node oauth-setup.js');
      throw new Error('OAuth tokens not found. Please run: node oauth-setup.js');
    }
    
    console.error('❌ Error getting access token:', error);
    throw error;
  }
}

/**
 * Helper function to get authorization headers for API requests
 * @returns {Promise<Object>} Headers object with Authorization
 */
export async function getAuthHeaders() {
  logger.debug('AUTH', 'Creating authorization headers');
  console.log('📋 Creating authorization headers...');
  
  const accessToken = await getOAuthAccessToken();
  
  const headers = {
    'Authorization': `Bearer ${accessToken}`,
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  };
  
  logger.debug('AUTH', 'Authorization headers created', {
    hasAuthorization: !!headers.Authorization,
    authHeaderLength: headers.Authorization ? headers.Authorization.length : 0,
    contentType: headers['Content-Type'],
    accept: headers.Accept
  });
  
  console.log('✅ Authorization headers created successfully');
  return headers;
}

/**
 * Manually trigger OAuth flow (useful for testing)
 * @returns {Promise<Object>} OAuth tokens
 */
export async function manualOAuthFlow() {
  console.log('🔄 Starting manual OAuth flow...');
  const tokens = await startOAuthFlow();
  
  // Save tokens using TokenManager
  tokenManager.saveTokens(tokens);
  
  return tokens;
}

/**
 * Check if we have valid tokens
 * @returns {boolean} True if tokens are available
 */
export function hasValidTokens() {
  return tokenManager.hasStoredTokens();
}

/**
 * Clear stored tokens (logout)
 */
export function clearTokens() {
  console.log('🚪 Clearing stored tokens...');
  tokenManager.clearTokens();
  console.log('✅ Tokens cleared successfully');
}

/**
 * Get current token information (for debugging)
 * @returns {Object|null} Current token info
 */
export function getTokenInfo() {
  return tokenManager.getTokenInfo();
}

```

--------------------------------------------------------------------------------
/oauth-setup.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * OAuth Setup Script for Google Apps Script API
 * This script helps you obtain and securely store OAuth tokens
 * Enhanced with detailed logging for debugging and monitoring
 */

import 'dotenv/config';
import { manualOAuthFlow } from './lib/oauth-helper.js';
import { TokenManager } from './lib/tokenManager.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import os from 'os';

// Enhanced logging utilities
const log = {
  info: (msg, data = null) => {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] ℹ️  ${msg}`);
    if (data) console.log(`[${timestamp}] 📊 Data:`, data);
  },
  success: (msg, data = null) => {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] ✅ ${msg}`);
    if (data) console.log(`[${timestamp}] 📊 Data:`, data);
  },
  error: (msg, error = null) => {
    const timestamp = new Date().toISOString();
    console.error(`[${timestamp}] ❌ ${msg}`);
    if (error) {
      console.error(`[${timestamp}] 🐛 Error details:`, error.message);
      if (error.stack) console.error(`[${timestamp}] 📚 Stack trace:`, error.stack);
    }
  },
  warn: (msg, data = null) => {
    const timestamp = new Date().toISOString();
    console.warn(`[${timestamp}] ⚠️  ${msg}`);
    if (data) console.warn(`[${timestamp}] 📊 Data:`, data);
  },
  debug: (msg, data = null) => {
    if (process.env.DEBUG || process.argv.includes('--debug')) {
      const timestamp = new Date().toISOString();
      console.log(`[${timestamp}] 🔍 DEBUG: ${msg}`);
      if (data) console.log(`[${timestamp}] 🔍 DEBUG Data:`, JSON.stringify(data, null, 2));
    }
  },
  step: (step, total, msg) => {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] 🚀 Step ${step}/${total}: ${msg}`);
  },
  separator: () => {
    console.log('═'.repeat(80));
  },
  subseparator: () => {
    console.log('─'.repeat(60));
  }
};

// Performance timing utility
class Timer {
  constructor(name) {
    this.name = name;
    this.start = Date.now();
    log.debug(`Timer started: ${name}`);
  }
  
  lap(description) {
    const elapsed = Date.now() - this.start;
    log.debug(`Timer ${this.name} - ${description}: ${elapsed}ms`);
    return elapsed;
  }
  
  end(description = 'completed') {
    const elapsed = Date.now() - this.start;
    log.info(`Timer ${this.name} ${description} in ${elapsed}ms`);
    return elapsed;
  }
}

// System information logging
function logSystemInfo() {
  log.separator();
  log.info('📋 System Information');
  log.subseparator();
  
  const systemInfo = {
    platform: os.platform(),
    arch: os.arch(),
    nodeVersion: process.version,
    workingDirectory: process.cwd(),
    scriptPath: fileURLToPath(import.meta.url),
    arguments: process.argv.slice(2),
    environment: process.env.NODE_ENV || 'development',
    timestamp: new Date().toISOString()
  };
  
  Object.entries(systemInfo).forEach(([key, value]) => {
    log.info(`  ${key}: ${value}`);
  });
  
  log.subseparator();
}

// Environment validation with detailed logging
function validateEnvironment() {
  const timer = new Timer('Environment Validation');
  log.step(1, 8, 'Validating environment configuration');
  
  const envVars = {
    'GOOGLE_APP_SCRIPT_API_CLIENT_ID': process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID,
    'GOOGLE_APP_SCRIPT_API_CLIENT_SECRET': process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET,
    'GOOGLE_APP_SCRIPT_API_REDIRECT_URI': process.env.GOOGLE_APP_SCRIPT_API_REDIRECT_URI
  };
  
  log.debug('Environment variables loaded:', {
    hasClientId: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID,
    hasClientSecret: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET,
    hasRedirectUri: !!envVars.GOOGLE_APP_SCRIPT_API_REDIRECT_URI,
    clientIdLength: envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID?.length || 0,
    clientSecretLength: envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET?.length || 0
  });
  
  const validation = {
    hasClientId: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID && 
                 envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID !== 'your_client_id_here',
    hasClientSecret: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET && 
                     envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET !== 'your_client_secret_here',
    hasRedirectUri: !!envVars.GOOGLE_APP_SCRIPT_API_REDIRECT_URI
  };
  
  log.info('Environment validation results:', validation);
  
  if (!validation.hasClientId) {
    log.error('Missing or invalid CLIENT_ID in environment');
    throw new Error('GOOGLE_APP_SCRIPT_API_CLIENT_ID is required');
  }
  
  if (!validation.hasClientSecret) {
    log.error('Missing or invalid CLIENT_SECRET in environment');
    throw new Error('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET is required');
  }
  
  if (!validation.hasRedirectUri) {
    log.warn('No REDIRECT_URI specified, using default');
  }
  
  timer.end();
  log.success('Environment validation completed successfully');
  return envVars;
}

// File system operations with detailed logging
function validateEnvFile() {
  const timer = new Timer('Env File Validation');
  log.step(2, 8, 'Validating .env file');
  
  const envPath = '.env';
  let envContent = '';
  
  try {
    log.debug(`Reading .env file from: ${envPath}`);
    envContent = readFileSync(envPath, 'utf8');
    const lines = envContent.split('\n');
    const nonEmptyLines = lines.filter(line => line.trim() && !line.trim().startsWith('#'));
    
    log.success('Successfully read .env file', {
      totalLines: lines.length,
      nonEmptyLines: nonEmptyLines.length,
      fileSize: envContent.length
    });
    
    // Analyze .env file content
    const envAnalysis = {
      hasClientId: envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID='),
      hasClientSecret: envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET='),
      hasRedirectUri: envContent.includes('GOOGLE_APP_SCRIPT_API_REDIRECT_URI='),
      hasComments: envContent.includes('#'),
      hasPlaceholders: envContent.includes('your_client_id_here') || envContent.includes('your_client_secret_here')
    };
    
    log.debug('Env file analysis:', envAnalysis);
    
    if (envAnalysis.hasPlaceholders) {
      log.warn('Found placeholder values in .env file - these need to be replaced with actual credentials');
    }
    
  } catch (error) {
    log.error('Failed to read .env file', error);
    log.info('Expected .env file location:', process.cwd() + '/.env');
    log.info('Please create .env file with required credentials');
    throw error;
  }
  
  timer.end();
  return envContent;
}

// Token management with detailed logging
function analyzeExistingTokens(tokenManager) {
  const timer = new Timer('Token Analysis');
  log.step(3, 8, 'Analyzing existing tokens');
  
  try {
    const tokenInfo = tokenManager.getTokenInfo();
    
    log.debug('Token analysis started');
    log.info('Token information retrieved:', {
      hasTokens: tokenInfo.hasTokens,
      location: tokenInfo.location,
      savedAt: tokenInfo.savedAt,
      expiresAt: tokenInfo.expiresAt,
      status: tokenInfo.status,
      isExpired: tokenInfo.isExpired
    });
    
    if (tokenInfo.hasTokens) {
      log.success('Found existing tokens');
      
      if (tokenInfo.isExpired) {
        log.warn('Existing tokens are expired');
      } else {
        log.success('Existing tokens are still valid');
      }
        // Additional token validation
      try {
        const tokenData = tokenManager.loadTokens();
        if (tokenData) {
          log.debug('Token data structure validation:', {
            hasAccessToken: !!tokenData.access_token,
            hasRefreshToken: !!tokenData.refresh_token,
            hasTokenType: !!tokenData.token_type,
            hasScope: !!tokenData.scope,
            expiresIn: tokenData.expires_in
          });
        }
      } catch (tokenError) {
        log.warn('Could not validate token data structure', tokenError);
      }
    } else {
      log.info('No existing tokens found');
    }
    
    timer.end();
    return tokenInfo;
    
  } catch (error) {
    log.error('Failed to analyze existing tokens', error);
    timer.end();
    throw error;
  }
}

// OAuth flow with enhanced logging
async function executeOAuthFlow() {
  const timer = new Timer('OAuth Flow');
  log.step(4, 8, 'Starting OAuth authorization flow');
  
  try {
    log.info('Initiating manual OAuth flow');
    log.debug('OAuth flow configuration:', {
      clientId: process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID?.substring(0, 10) + '...',
      redirectUri: process.env.GOOGLE_APP_SCRIPT_API_REDIRECT_URI,
      scope: 'https://www.googleapis.com/auth/script.projects'
    });
    
    log.info('🌐 Opening browser for OAuth authorization...');
    log.info('📱 Please complete the authorization in your browser');
    log.info('⏳ Waiting for OAuth callback...');
    
    const tokens = await manualOAuthFlow();
    
    log.debug('OAuth flow completed, analyzing response');
    const tokenAnalysis = {
      hasAccessToken: !!tokens.access_token,
      hasRefreshToken: !!tokens.refresh_token,
      hasTokenType: !!tokens.token_type,
      hasScope: !!tokens.scope,
      hasExpiresIn: !!tokens.expires_in,
      accessTokenLength: tokens.access_token?.length || 0,
      refreshTokenLength: tokens.refresh_token?.length || 0,
      tokenType: tokens.token_type,
      scope: tokens.scope,
      expiresIn: tokens.expires_in
    };
    
    log.success('OAuth tokens received', tokenAnalysis);
    
    if (!tokens.refresh_token) {
      log.error('No refresh token received - this is required for long-term access');
      throw new Error('Refresh token missing from OAuth response');
    }
    
    timer.end();
    return tokens;
    
  } catch (error) {
    log.error('OAuth flow failed', error);
    timer.end();
    throw error;
  }
}

// Token storage with detailed logging
function saveTokensSecurely(tokenManager, tokens) {
  const timer = new Timer('Token Storage');
  log.step(5, 8, 'Saving tokens securely');
  
  try {
    log.debug('Preparing to save tokens');
    log.info('Token storage location:', tokenManager.getTokenInfo().location);
    
    // Pre-save validation
    const preValidation = {
      hasAccessToken: !!tokens.access_token,
      hasRefreshToken: !!tokens.refresh_token,
      tokenManagerReady: !!tokenManager
    };
    
    log.debug('Pre-save validation:', preValidation);
    
    if (!preValidation.hasAccessToken || !preValidation.hasRefreshToken) {
      throw new Error('Invalid token data - missing required tokens');
    }
    
    log.info('💾 Writing tokens to secure storage...');
    tokenManager.saveTokens(tokens);
    
    // Post-save verification
    const verification = tokenManager.getTokenInfo();
    log.success('Tokens saved successfully', {
      location: verification.location,
      hasTokens: verification.hasTokens,
      savedAt: verification.savedAt,
      status: verification.status
    });
      // Additional verification - try to read back the tokens
    try {
      const savedTokens = tokenManager.loadTokens();
      const verificationCheck = {
        canReadBack: !!savedTokens,
        accessTokenMatches: savedTokens?.access_token === tokens.access_token,
        refreshTokenMatches: savedTokens?.refresh_token === tokens.refresh_token
      };
      
      log.debug('Token verification check:', verificationCheck);
      
      if (!verificationCheck.canReadBack) {
        throw new Error('Cannot read back saved tokens');
      }
      
      if (!verificationCheck.accessTokenMatches || !verificationCheck.refreshTokenMatches) {
        log.warn('Saved tokens do not match original tokens');
      } else {
        log.success('Token integrity verified');
      }
      
    } catch (verificationError) {
      log.warn('Could not verify saved tokens', verificationError);
    }
    
    timer.end();
    return verification;
    
  } catch (error) {
    log.error('Failed to save tokens securely', error);
    timer.end();
    throw error;
  }
}

// Main setup function with comprehensive logging
async function setupOAuth() {
  const mainTimer = new Timer('Complete OAuth Setup');
  
  log.separator();
  log.info('🔐 Google Apps Script API OAuth Setup - Enhanced Logging Version');
  log.separator();
  
  // Log system information
  logSystemInfo();
  
  try {
    log.info('📋 Starting OAuth setup process...');
    log.info('📝 This script will guide you through OAuth authentication setup');
    
    // Initialize token manager
    log.step(0, 8, 'Initializing token manager');
    const tokenManager = new TokenManager();
    log.success('Token manager initialized');
    
    // Handle command line arguments
    const args = process.argv.slice(2);
    log.debug('Command line arguments:', args);
    
    // Handle info command
    if (args.includes('--info')) {
      log.info('📊 Information mode requested');
      const tokenInfo = analyzeExistingTokens(tokenManager);
      
      console.log('\n🔍 Token Information Summary:');
      console.log('═'.repeat(40));
      
      if (tokenInfo.hasTokens) {
        console.log('✅ Status: Tokens found');
        console.log(`📁 Location: ${tokenInfo.location}`);
        console.log(`💾 Saved: ${tokenInfo.savedAt}`);
        console.log(`⏰ Expires: ${tokenInfo.expiresAt}`);
        console.log(`📊 Status: ${tokenInfo.status}`);
        console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
      } else {
        console.log('❌ Status: No tokens found');
        console.log(`📁 Expected location: ${tokenInfo.location}`);
        console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
      }
      
      mainTimer.end('info command completed');
      process.exit(0);
    }
    
    // Handle clear command
    if (args.includes('--clear')) {
      log.info('🗑️ Clear tokens mode requested');
      const tokenInfo = analyzeExistingTokens(tokenManager);
      
      if (tokenInfo.hasTokens) {
        log.info('Clearing existing tokens...');
        tokenManager.clearTokens();
        log.success('Tokens cleared successfully');
      } else {
        log.info('No tokens found to clear');
      }
      
      mainTimer.end('clear command completed');
      process.exit(0);
    }
    
    // Validate environment
    const envVars = validateEnvironment();
    const envContent = validateEnvFile();
    
    // Analyze existing tokens
    const tokenInfo = analyzeExistingTokens(tokenManager);
    
    // Check if we should proceed with OAuth flow
    if (tokenInfo.hasTokens && !tokenInfo.isExpired && !args.includes('--force')) {
      log.success('Valid tokens already exist');
      console.log('\n✅ You already have valid OAuth tokens stored.');
      console.log('💡 To force new token generation: node oauth-setup.js --force');
      console.log('🗑️ To clear existing tokens: node oauth-setup.js --clear');
      console.log('📊 To view token info: node oauth-setup.js --info');
      
      mainTimer.end('setup skipped - valid tokens exist');
      process.exit(0);
    }
    
    if (tokenInfo.hasTokens && args.includes('--force')) {
      log.warn('Force mode enabled - will replace existing tokens');
    }
    
    // Execute OAuth flow
    const tokens = await executeOAuthFlow();
    
    // Save tokens securely
    const saveResult = saveTokensSecurely(tokenManager, tokens);
    
    // Final verification and success message
    log.step(6, 8, 'Performing final verification');
    const finalVerification = analyzeExistingTokens(tokenManager);
    
    if (finalVerification.hasTokens && !finalVerification.isExpired) {
      log.success('Final verification passed - OAuth setup completed successfully');
    } else {
      log.error('Final verification failed');
      throw new Error('Setup completed but final verification failed');
    }
    
    // Success summary
    log.step(7, 8, 'Generating setup summary');
    log.separator();
    log.success('🎉 OAuth Setup Completed Successfully!');
    log.subseparator();
    
    const summary = {
      tokenLocation: saveResult.location,
      setupDuration: mainTimer.end('setup completed'),
      tokenStatus: finalVerification.status,
      nextSteps: [
        'Test your setup: node test-token-management.js',
        'Run MCP server: node mcpServer.js',
        'Check token info: node oauth-setup.js --info'
      ]
    };
    
    log.info('Setup Summary:', summary);
    
    console.log('\n🎉 Setup Complete! Next Steps:');
    summary.nextSteps.forEach((step, index) => {
      console.log(`   ${index + 1}. ${step}`);
    });
    
    log.step(8, 8, 'OAuth setup process completed');
    
  } catch (error) {
    log.error('OAuth setup failed', error);
    
    console.log('\n🔧 Troubleshooting Guide:');
    console.log('════════════════════════');
    console.log('1. 🌐 Check your internet connection');
    console.log('2. 🔑 Verify CLIENT_ID and CLIENT_SECRET are correct');
    console.log('3. 🔗 Ensure redirect URI is registered in Google Cloud Console');
    console.log('4. 🔌 Confirm Google Apps Script API is enabled');
    console.log('5. 🔄 Try revoking and re-creating OAuth credentials');
    console.log('6. 🐛 Enable debug mode: node oauth-setup.js --debug');
    console.log('\n📖 For detailed instructions, see OAUTH_SETUP.md');
    
    mainTimer.end('setup failed');
    process.exit(1);
  }
}

// Error handling and execution
setupOAuth().catch((error) => {
  log.error('💥 Unexpected error during setup', error);
  console.error('\n🚨 Critical Error Details:');
  console.error('Message:', error.message);
  if (error.stack) {
    console.error('Stack:', error.stack);
  }
  process.exit(1);
});
```
Page 2/2FirstPrevNextLast