#
tokens: 47273/50000 21/72 files (page 2/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 3. Use http://codebase.md/mohalmah/google-appscript-mcp-server?lines=true&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

--------------------------------------------------------------------------------
/helpers/convert-to-oauth.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import fs from 'fs';
  4 | import path from 'path';
  5 | import { fileURLToPath } from 'url';
  6 | 
  7 | const __filename = fileURLToPath(import.meta.url);
  8 | const __dirname = path.dirname(__filename);
  9 | 
 10 | // Files to update
 11 | const filesToUpdate = [
 12 |   'tools/google-app-script-api/apps-script-api/script-projects-deployments-create.js',
 13 |   'tools/google-app-script-api/apps-script-api/script-projects-deployments-get.js',
 14 |   'tools/google-app-script-api/apps-script-api/script-projects-deployments-list.js',
 15 |   'tools/google-app-script-api/apps-script-api/script-projects-deployments-update.js',
 16 |   'tools/google-app-script-api/apps-script-api/script-projects-get-content.js',
 17 |   'tools/google-app-script-api/apps-script-api/script-projects-get-metrics.js',
 18 |   'tools/google-app-script-api/apps-script-api/script-projects-update-content.js',
 19 |   'tools/google-app-script-api/apps-script-api/script-projects-versions-create.js',
 20 |   'tools/google-app-script-api/apps-script-api/script-projects-versions-get.js',
 21 |   'tools/google-app-script-api/apps-script-api/script-projects-versions-list.js',
 22 |   'tools/google-app-script-api/apps-script-api/script-processes-list-script-processes.js'
 23 | ];
 24 | 
 25 | function updateFileForOAuth(filePath) {
 26 |   const fullPath = path.join(__dirname, filePath);
 27 |   
 28 |   if (!fs.existsSync(fullPath)) {
 29 |     console.log(`⚠️  File not found: ${filePath}`);
 30 |     return;
 31 |   }
 32 |   
 33 |   let content = fs.readFileSync(fullPath, 'utf8');
 34 |   
 35 |   // Check if already updated
 36 |   if (content.includes("import { getAuthHeaders } from '../../../lib/oauth-helper.js';")) {
 37 |     console.log(`✅ Already updated: ${filePath}`);
 38 |     return;
 39 |   }
 40 |   
 41 |   // Add import at the top
 42 |   if (!content.includes("import { getAuthHeaders }")) {
 43 |     content = `import { getAuthHeaders } from '../../../lib/oauth-helper.js';\n\n${content}`;
 44 |   }
 45 |   
 46 |   // Remove API key token assignment
 47 |   content = content.replace(/\s*const token = process\.env\.GOOGLE_APP_SCRIPT_API_API_KEY;\s*/g, '');
 48 |   content = content.replace(/\s*const apiKey = process\.env\.GOOGLE_APP_SCRIPT_API_API_KEY;\s*/g, '');
 49 |   
 50 |   // Replace header setup
 51 |   const headerPatterns = [
 52 |     // Pattern 1: Simple headers with token check
 53 |     /(\s*)\/\/ Set up headers for the request\s*\n\s*const headers = \{\s*\n\s*['"]Accept['"]:\s*['"]application\/json['"],?\s*\n\s*\};\s*\n\s*\/\/ If a token is provided, add it to the Authorization header\s*\n\s*if \(token\) \{\s*\n\s*headers\[['"]Authorization['"]\] = `Bearer \$\{token\}`;?\s*\n\s*\}/g,
 54 |     
 55 |     // Pattern 2: Headers with Content-Type
 56 |     /(\s*)\/\/ Set up headers for the request\s*\n\s*const headers = \{\s*\n\s*['"]Content-Type['"]:\s*['"]application\/json['"],?\s*\n\s*['"]Accept['"]:\s*['"]application\/json['"],?\s*\n\s*\};\s*\n\s*\/\/ If a token is provided, add it to the Authorization header\s*\n\s*if \(token\) \{\s*\n\s*headers\[['"]Authorization['"]\] = `Bearer \$\{token\}`;?\s*\n\s*\}/g,
 57 |     
 58 |     // Pattern 3: Authorization-only headers
 59 |     /(\s*)\/\/ Set up headers for the request\s*\n\s*const headers = \{\s*\n\s*['"]Authorization['"]:\s*`Bearer \$\{token\}`,?\s*\n\s*['"]Accept['"]:\s*['"]application\/json['"],?\s*\n\s*\}/g
 60 |   ];
 61 |   
 62 |   let headerReplaced = false;
 63 |   headerPatterns.forEach(pattern => {
 64 |     if (pattern.test(content) && !headerReplaced) {
 65 |       content = content.replace(pattern, '$1// Get OAuth headers\n$1const headers = await getAuthHeaders();');
 66 |       headerReplaced = true;
 67 |     }
 68 |   });
 69 |   
 70 |   // If no pattern matched, try a simpler approach
 71 |   if (!headerReplaced) {
 72 |     // Replace any Authorization header assignment
 73 |     content = content.replace(/headers\[['"]Authorization['"]\]\s*=\s*`Bearer \$\{token\}`;?/g, '');
 74 |     
 75 |     // Add OAuth headers if we can find where headers are defined
 76 |     if (content.includes('const headers = {') && !content.includes('await getAuthHeaders()')) {
 77 |       content = content.replace(
 78 |         /(const headers = \{[^}]*\});/,
 79 |         'const headers = await getAuthHeaders();'
 80 |       );
 81 |     }
 82 |   }
 83 |   
 84 |   // Wrap the fetch request in try-catch if not already
 85 |   if (!content.includes('try {') && content.includes('const response = await fetch')) {
 86 |     content = content.replace(
 87 |       /(const executeFunction = async \([^}]*\) => \{)/,
 88 |       '$1\n  try {'
 89 |     );
 90 |     content = content.replace(
 91 |       /(return data;\s*\}\s*catch)/,
 92 |       '$1'
 93 |     );
 94 |   }
 95 |   
 96 |   // Write the updated content
 97 |   fs.writeFileSync(fullPath, content, 'utf8');
 98 |   console.log(`✅ Updated: ${filePath}`);
 99 | }
100 | 
101 | // Update all files
102 | console.log('🔄 Converting Google Apps Script API files to use OAuth authentication...\n');
103 | 
104 | filesToUpdate.forEach(updateFileForOAuth);
105 | 
106 | console.log('\n✨ OAuth conversion completed!');
107 | console.log('\n📝 Next steps:');
108 | console.log('1. Update your .env file with OAuth credentials');
109 | console.log('2. Follow the OAUTH_SETUP.md guide to get your credentials');
110 | console.log('3. Test the authentication with one of the tools');
111 | 
```

--------------------------------------------------------------------------------
/OAUTH_IMPLEMENTATION.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Enhanced OAuth Implementation Summary
  2 | 
  3 | ## What We've Implemented
  4 | 
  5 | ### 1. **Complete OAuth2 Flow** (`lib/oauth-helper.js`)
  6 | - ✅ **Automatic browser opening** for OAuth authorization
  7 | - ✅ **Local callback server** to handle OAuth responses
  8 | - ✅ **Token management** with automatic refresh
  9 | - ✅ **Fallback mechanisms** when refresh tokens expire
 10 | - ✅ **Detailed logging** throughout the process
 11 | - ✅ **Error handling** with specific troubleshooting guidance
 12 | 
 13 | ### 2. **Enhanced Test Script** (`test-oauth.js`)
 14 | - ✅ **Comprehensive logging** with timestamps and performance metrics
 15 | - ✅ **Detailed error information** including stack traces
 16 | - ✅ **Environment validation** checking for .env file and credentials
 17 | - ✅ **System information** logging for debugging
 18 | - ✅ **OAuth credential verification** (without exposing sensitive data)
 19 | 
 20 | ### 3. **OAuth Setup Script** (`oauth-setup.js`)
 21 | - ✅ **Interactive OAuth flow** to obtain refresh tokens
 22 | - ✅ **Automatic .env file updates** with new refresh tokens
 23 | - ✅ **Credential validation** before starting the flow
 24 | - ✅ **User-friendly web interface** for OAuth completion
 25 | - ✅ **Comprehensive error handling** and troubleshooting
 26 | 
 27 | ### 4. **Updated Configuration**
 28 | - ✅ **Package.json scripts** for easy access
 29 | - ✅ **Environment variables** properly configured
 30 | - ✅ **Dependencies installed** (googleapis, open)
 31 | - ✅ **Comprehensive setup guide** (OAUTH_SETUP.md)
 32 | 
 33 | ## Key Features Similar to Your Working App
 34 | 
 35 | ### 🔐 **OAuth2 Client Creation**
 36 | ```javascript
 37 | const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
 38 | ```
 39 | 
 40 | ### 🌐 **Local Server for Callback**
 41 | ```javascript
 42 | const server = createServer(async (req, res) => {
 43 |   // Handle OAuth callback with detailed logging
 44 | });
 45 | ```
 46 | 
 47 | ### 🔄 **Token Exchange & Storage**
 48 | ```javascript
 49 | const { tokens: newTokens } = await oAuth2Client.getToken(code);
 50 | tokens = newTokens;
 51 | ```
 52 | 
 53 | ### 📱 **Browser Automation**
 54 | ```javascript
 55 | open(authUrl).catch(err => {
 56 |   console.error('❌ Failed to open browser:', err);
 57 |   console.log('🔗 Please manually open this URL in your browser:', authUrl);
 58 | });
 59 | ```
 60 | 
 61 | ### 🔄 **Automatic Token Refresh**
 62 | ```javascript
 63 | if (refreshToken && refreshToken !== 'your_refresh_token_here') {
 64 |   // Use refresh token to get new access token
 65 | }
 66 | ```
 67 | 
 68 | ## How to Use
 69 | 
 70 | ### 1. **First Time Setup**
 71 | ```bash
 72 | npm run setup-oauth
 73 | ```
 74 | - Opens browser automatically
 75 | - Handles OAuth flow
 76 | - Updates .env file with refresh token
 77 | 
 78 | ### 2. **Test OAuth Setup**
 79 | ```bash
 80 | npm run test-oauth
 81 | ```
 82 | - Validates credentials
 83 | - Tests token retrieval
 84 | - Shows detailed diagnostic information
 85 | 
 86 | ### 3. **Use in Your MCP Tools**
 87 | The OAuth helper will now:
 88 | - ✅ Use refresh token if available
 89 | - ✅ Start interactive OAuth flow if needed
 90 | - ✅ Handle token expiration automatically
 91 | - ✅ Provide detailed error information
 92 | 
 93 | ## Enhanced Error Logging
 94 | 
 95 | ### **Success Path Logging:**
 96 | - ⏰ Timestamps for all operations
 97 | - 📊 Performance metrics
 98 | - 🔑 Token information (safely masked)
 99 | - 📋 Request/response details
100 | 
101 | ### **Error Path Logging:**
102 | - 🕐 Error timestamps
103 | - 📋 Complete error details (message, stack, status codes)
104 | - 🔍 Environment diagnostics
105 | - 📂 File system checks
106 | - 🔧 Comprehensive troubleshooting steps
107 | 
108 | ### **Example Enhanced Error Output:**
109 | ```
110 | ❌ OAuth authentication failed!
111 | 🕐 Error occurred at: 2025-05-31T10:30:45.123Z
112 | 
113 | 📋 Error Details:
114 |   📄 Message: Failed to refresh token: invalid_grant
115 |   🏷️  Name: Error
116 |   📊 Stack trace:
117 |     Error: Failed to refresh token: invalid_grant
118 |         at getOAuthAccessToken (file:///oauth-helper.js:245:13)
119 |         at testOAuthAuthentication (file:///test-oauth.js:28:31)
120 | 
121 | 🔍 Environment Check:
122 |   📂 Current directory: c:\Users\mohal\Downloads\postman-mcp-server
123 |   🔧 Node.js version: v18.17.0
124 |   📄 .env file exists: true
125 |   🔑 GOOGLE_CLIENT_ID present: true
126 |   🔑 GOOGLE_CLIENT_SECRET present: true
127 |   🔑 GOOGLE_REFRESH_TOKEN present: true
128 | 
129 | 🔧 Troubleshooting steps:
130 | 1. Check that your .env file contains valid OAuth credentials
131 | 2. Verify your client ID and client secret are correct
132 | 3. Ensure your refresh token is valid and not expired
133 | 4. Follow the OAUTH_SETUP.md guide to obtain new credentials if needed
134 | 5. Make sure the Google Apps Script API is enabled in your GCP project
135 | 6. Check your internet connection and firewall settings
136 | 7. Verify that the oauth-helper.js file exists and is accessible
137 | ```
138 | 
139 | ## Security Features
140 | 
141 | - 🔐 **No credentials exposed** in logs
142 | - 🔑 **Secure token storage** in environment variables
143 | - 🌐 **Local-only callback server** (port 3001)
144 | - ⏱️ **Automatic server timeout** (5 minutes)
145 | - 🚪 **Clean token cleanup** on logout
146 | 
147 | ## Next Steps
148 | 
149 | 1. **Run the setup**: `npm run setup-oauth`
150 | 2. **Test the implementation**: `npm run test-oauth`
151 | 3. **Use your MCP tools** with confidence!
152 | 
153 | The OAuth implementation now matches the robustness and user experience of your working Express app while providing enhanced debugging capabilities for easier troubleshooting.
154 | 
```

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

```javascript
  1 | import { getOAuthAccessToken } from '../../../lib/oauth-helper.js';
  2 | import { logger } from '../../../lib/logger.js';
  3 | 
  4 | /**
  5 |  * Function to list the deployments of a Google Apps Script project.
  6 |  *
  7 |  * @param {Object} args - Arguments for the deployment listing.
  8 |  * @param {string} args.scriptId - The ID of the script project.
  9 |  * @param {number} [args.pageSize=50] - The number of deployments to return per page.
 10 |  * @param {string} [args.pageToken] - Token for pagination.
 11 |  * @param {string} [args.fields] - Selector specifying which fields to include in a partial response.
 12 |  * @param {boolean} [args.prettyPrint=true] - Returns response with indentations and line breaks.
 13 |  * @returns {Promise<Object>} - The result of the deployments listing.
 14 |  */
 15 | const executeFunction = async ({ scriptId, pageSize = 50, pageToken, fields, prettyPrint = true }) => {
 16 |   const baseUrl = 'https://script.googleapis.com';
 17 |   const startTime = Date.now();
 18 | 
 19 |   logger.info('API_CALL', 'Starting deployments list request', {
 20 |     scriptId,
 21 |     pageSize,
 22 |     pageToken: pageToken ? 'provided' : 'none',
 23 |     fields: fields || 'all',
 24 |     baseUrl
 25 |   });
 26 | 
 27 |   try {
 28 |     // Get OAuth access token
 29 |     logger.debug('API_CALL', 'Getting OAuth access token');
 30 |     const token = await getOAuthAccessToken();
 31 |     logger.debug('API_CALL', 'OAuth token obtained successfully');
 32 |     
 33 |     // Construct the URL with query parameters
 34 |     const url = new URL(`${baseUrl}/v1/projects/${scriptId}/deployments`);
 35 |     url.searchParams.append('pageSize', pageSize.toString());
 36 |     if (pageToken) url.searchParams.append('pageToken', pageToken);
 37 |     if (fields) url.searchParams.append('fields', fields);
 38 |     url.searchParams.append('alt', 'json');
 39 |     url.searchParams.append('prettyPrint', prettyPrint.toString());
 40 | 
 41 |     logger.debug('API_CALL', 'Constructed API URL', {
 42 |       url: url.toString(),
 43 |       pathSegments: url.pathname.split('/'),
 44 |       queryParams: Object.fromEntries(url.searchParams)
 45 |     });
 46 | 
 47 |     // Set up headers for the request
 48 |     const headers = {
 49 |       'Accept': 'application/json',
 50 |       'Authorization': `Bearer ${token}`
 51 |     };
 52 | 
 53 |     logger.logAPICall('GET', url.toString(), headers);
 54 | 
 55 |     // Perform the fetch request
 56 |     const fetchStartTime = Date.now();
 57 |     const response = await fetch(url.toString(), {
 58 |       method: 'GET',
 59 |       headers
 60 |     });
 61 |     
 62 |     const fetchDuration = Date.now() - fetchStartTime;
 63 |     const responseSize = response.headers.get('content-length') || 'unknown';
 64 |     
 65 |     logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);
 66 | 
 67 |     // Check if the response was successful
 68 |     if (!response.ok) {
 69 |       const errorText = await response.text();
 70 |       
 71 |       logger.error('API_CALL', 'API request failed', {
 72 |         status: response.status,
 73 |         statusText: response.statusText,
 74 |         url: url.toString(),
 75 |         errorResponse: errorText,
 76 |         duration: Date.now() - startTime
 77 |       });
 78 |       
 79 |       console.error('API Error Response:', errorText);
 80 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
 81 |     }
 82 | 
 83 |     // Parse and return the response data
 84 |     const data = await response.json();
 85 |     const totalDuration = Date.now() - startTime;
 86 |     
 87 |     logger.info('API_CALL', 'Deployments list request completed successfully', {
 88 |       scriptId,
 89 |       deploymentCount: data.deployments ? data.deployments.length : 0,
 90 |       hasNextPageToken: !!data.nextPageToken,
 91 |       totalDuration: `${totalDuration}ms`,
 92 |       responseSize: JSON.stringify(data).length
 93 |     });
 94 |     
 95 |     return data;
 96 |   } catch (error) {
 97 |     const totalDuration = Date.now() - startTime;
 98 |     
 99 |     logger.error('API_CALL', 'Deployments list request failed', {
100 |       scriptId,
101 |       error: {
102 |         message: error.message,
103 |         stack: error.stack
104 |       },
105 |       totalDuration: `${totalDuration}ms`
106 |     });
107 |     
108 |     console.error('Error listing deployments:', error);
109 |     return { 
110 |       error: 'An error occurred while listing deployments.',
111 |       details: {
112 |         message: error.message,
113 |         scriptId,
114 |         timestamp: new Date().toISOString()
115 |       }
116 |     };
117 |   }
118 | };
119 | 
120 | /**
121 |  * Tool configuration for listing deployments of a Google Apps Script project.
122 |  * @type {Object}
123 |  */
124 | const apiTool = {
125 |   function: executeFunction,
126 |   definition: {
127 |     type: 'function',
128 |     function: {
129 |       name: 'script_projects_deployments_list',
130 |       description: 'Lists the deployments of an Apps Script project.',
131 |       parameters: {
132 |         type: 'object',
133 |         properties: {
134 |           scriptId: {
135 |             type: 'string',
136 |             description: 'The ID of the script project.'
137 |           },
138 |           pageSize: {
139 |             type: 'integer',
140 |             description: 'The number of deployments to return per page.'
141 |           },
142 |           pageToken: {
143 |             type: 'string',
144 |             description: 'Token for pagination.'
145 |           },
146 |           fields: {
147 |             type: 'string',
148 |             description: 'Selector specifying which fields to include in a partial response.'
149 |           },
150 |           prettyPrint: {
151 |             type: 'boolean',
152 |             description: 'Returns response with indentations and line breaks.'
153 |           }
154 |         },
155 |         required: ['scriptId']
156 |       }
157 |     }
158 |   }
159 | };
160 | 
161 | export { apiTool };
```

--------------------------------------------------------------------------------
/test/update-tools.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Script to automatically update all Google Apps Script API tools
  5 |  * to use automatic OAuth and enhanced error handling
  6 |  */
  7 | 
  8 | import { readFileSync, writeFileSync, readdirSync } from 'fs';
  9 | import { join } from 'path';
 10 | 
 11 | const toolsDir = 'tools/google-app-script-api/apps-script-api';
 12 | 
 13 | // Enhanced error handling template
 14 | const errorHandlingTemplate = `
 15 |   } catch (error) {
 16 |     console.error('❌ Error in API call:', {
 17 |       message: error.message,
 18 |       stack: error.stack,
 19 |       timestamp: new Date().toISOString()
 20 |     });
 21 |     
 22 |     // Return detailed error information for debugging
 23 |     return { 
 24 |       error: true,
 25 |       message: error.message,
 26 |       details: {
 27 |         timestamp: new Date().toISOString(),
 28 |         errorType: error.name || 'Unknown'
 29 |       }
 30 |     };
 31 |   }
 32 | `;
 33 | 
 34 | // Function to remove OAuth parameters from tool definitions
 35 | function updateToolDefinition(content) {
 36 |   // Remove OAuth-related parameters from the function signature
 37 |   content = content.replace(
 38 |     /(\{[^}]*?)(key|access_token|oauth_token)[\s\S]*?(,\s*)?/g,
 39 |     (match, start, param, comma) => {
 40 |       // If this is the last parameter before closing brace, remove the comma
 41 |       if (match.includes('}')) {
 42 |         return start.replace(/,\s*$/, '');
 43 |       }
 44 |       return start;
 45 |     }
 46 |   );
 47 | 
 48 |   // Remove OAuth parameters from URL construction
 49 |   content = content.replace(/\s*url\.searchParams\.append\([^)]*(?:key|access_token|oauth_token)[^)]*\);?\n?/g, '');
 50 | 
 51 |   // Update parameter validation to only require non-OAuth params
 52 |   content = content.replace(
 53 |     /required:\s*\[[^\]]*(?:key|access_token|oauth_token)[^\]]*\]/g,
 54 |     (match) => {
 55 |       // Extract non-OAuth required parameters
 56 |       const params = match.match(/'([^']+)'/g) || [];
 57 |       const nonOAuthParams = params.filter(p => 
 58 |         !p.includes('key') && 
 59 |         !p.includes('access_token') && 
 60 |         !p.includes('oauth_token')
 61 |       );
 62 |       return `required: [${nonOAuthParams.join(', ')}]`;
 63 |     }
 64 |   );
 65 | 
 66 |   // Remove OAuth parameter definitions from schema
 67 |   content = content.replace(/\s*(?:key|access_token|oauth_token):\s*\{[^}]*\},?\n?/g, '');
 68 | 
 69 |   // Add OAuth auto-handling note to description
 70 |   content = content.replace(
 71 |     /(description:\s*')([^']*?)(')/g,
 72 |     '$1$2 OAuth authentication is handled automatically.$3'
 73 |   );
 74 | 
 75 |   return content;
 76 | }
 77 | 
 78 | // Function to enhance error handling
 79 | function enhanceErrorHandling(content) {
 80 |   // Replace simple error handling with detailed version
 81 |   content = content.replace(
 82 |     /if\s*\(\s*!response\.ok\s*\)\s*\{[\s\S]*?throw new Error\([^)]*\);?\s*\}/g,
 83 |     `if (!response.ok) {
 84 |       const errorText = await response.text();
 85 |       let errorData;
 86 |       
 87 |       try {
 88 |         errorData = JSON.parse(errorText);
 89 |       } catch (parseError) {
 90 |         errorData = { message: errorText };
 91 |       }
 92 | 
 93 |       const detailedError = {
 94 |         status: response.status,
 95 |         statusText: response.statusText,
 96 |         url: url.toString(),
 97 |         error: errorData,
 98 |         timestamp: new Date().toISOString()
 99 |       };
100 | 
101 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
102 |       
103 |       throw new Error(\`API Error (\${response.status}): \${errorData.error?.message || errorData.message || 'Unknown error'}\`);
104 |     }`
105 |   );
106 | 
107 |   // Replace simple catch blocks with detailed error handling
108 |   content = content.replace(
109 |     /catch\s*\([^)]*\)\s*\{[\s\S]*?return\s*\{[^}]*error[^}]*\};?\s*\}/g,
110 |     errorHandlingTemplate.trim()
111 |   );
112 | 
113 |   return content;
114 | }
115 | 
116 | // Get all tool files
117 | const toolFiles = readdirSync(toolsDir).filter(file => file.endsWith('.js'));
118 | 
119 | console.log('🔧 Updating Google Apps Script API tools...');
120 | console.log(`📁 Found ${toolFiles.length} tool files to update`);
121 | 
122 | let updatedCount = 0;
123 | 
124 | for (const file of toolFiles) {
125 |   try {
126 |     const filePath = join(toolsDir, file);
127 |     console.log(`🔄 Processing: ${file}`);
128 |     
129 |     let content = readFileSync(filePath, 'utf8');
130 |     
131 |     // Skip if already updated (check for OAuth auto-handling comment)
132 |     if (content.includes('OAuth authentication is handled automatically')) {
133 |       console.log(`✅ ${file} already updated, skipping`);
134 |       continue;
135 |     }
136 |     
137 |     // Apply updates
138 |     content = updateToolDefinition(content);
139 |     content = enhanceErrorHandling(content);
140 |     
141 |     // Add logging for API calls
142 |     if (!content.includes('console.log') && content.includes('fetch(')) {
143 |       content = content.replace(
144 |         /(const url = new URL\([^;]+;)/,
145 |         '$1\n    console.log(\'🌐 API URL:\', url.toString());'
146 |       );
147 |       
148 |       content = content.replace(
149 |         /(const headers = await getAuthHeaders\(\);)/,
150 |         '$1\n    console.log(\'🔐 Authorization headers obtained successfully\');'
151 |       );
152 |       
153 |       content = content.replace(
154 |         /(const response = await fetch\([^;]+;)/,
155 |         '$1\n    console.log(\'📡 API Response Status:\', response.status, response.statusText);'
156 |       );
157 |     }
158 |     
159 |     writeFileSync(filePath, content);
160 |     updatedCount++;
161 |     console.log(`✅ Updated: ${file}`);
162 |     
163 |   } catch (error) {
164 |     console.error(`❌ Error updating ${file}:`, error.message);
165 |   }
166 | }
167 | 
168 | console.log(`\n🎉 Successfully updated ${updatedCount} out of ${toolFiles.length} files`);
169 | console.log('✅ All tools now use automatic OAuth authentication and enhanced error handling');
170 | 
```

--------------------------------------------------------------------------------
/tools/google-app-script-api/apps-script-api/script-projects-deployments-update.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { logger } from '../../../lib/logger.js';
  2 | 
  3 | /**
  4 |  * Function to update a deployment of an Apps Script project.
  5 |  *
  6 |  * @param {Object} args - Arguments for the update.
  7 |  * @param {string} args.scriptId - The ID of the script to update.
  8 |  * @param {string} args.deploymentId - The ID of the deployment to update.
  9 |  * @param {Object} args.deploymentConfig - The configuration for the deployment.
 10 |  * @param {string} args.deploymentConfig.manifestFileName - The name of the manifest file.
 11 |  * @param {number} args.deploymentConfig.versionNumber - The version number of the deployment.
 12 |  * @param {string} args.deploymentConfig.description - A description of the deployment.
 13 |  * @returns {Promise<Object>} - The result of the deployment update.
 14 |  */
 15 | const executeFunction = async ({ scriptId, deploymentId, deploymentConfig }) => {
 16 |   const baseUrl = 'https://script.googleapis.com';
 17 |   const token = process.env.GOOGLE_APP_SCRIPT_API_API_KEY;
 18 |   const apiKey = process.env.GOOGLE_APP_SCRIPT_API_API_KEY;
 19 |   const startTime = Date.now();
 20 | 
 21 |   try {
 22 |     logger.info('DEPLOYMENT_UPDATE', 'Starting deployment update', { 
 23 |       scriptId, 
 24 |       deploymentId, 
 25 |       versionNumber: deploymentConfig?.versionNumber 
 26 |     });
 27 | 
 28 |     // Construct the URL for the request
 29 |     const url = `${baseUrl}/v1/projects/${scriptId}/deployments/${deploymentId}?key=${apiKey}&prettyPrint=true`;
 30 | 
 31 |     // Set up headers for the request
 32 |     const headers = {
 33 |       'Content-Type': 'application/json',
 34 |       'Accept': 'application/json',
 35 |       'Authorization': `Bearer ${token}`
 36 |     };
 37 | 
 38 |     // Prepare the body of the request
 39 |     const requestBody = { deploymentConfig };
 40 |     const body = JSON.stringify(requestBody);
 41 | 
 42 |     logger.logAPICall('PUT', url, headers, requestBody);
 43 | 
 44 |     // Perform the fetch request
 45 |     const fetchStartTime = Date.now();
 46 |     const response = await fetch(url, {
 47 |       method: 'PUT',
 48 |       headers,
 49 |       body
 50 |     });
 51 |     
 52 |     const fetchDuration = Date.now() - fetchStartTime;
 53 |     const responseSize = response.headers.get('content-length') || 'unknown';
 54 |     
 55 |     logger.logAPIResponse('PUT', url, response.status, fetchDuration, responseSize);
 56 | 
 57 |     // Check if the response was successful
 58 |     if (!response.ok) {
 59 |       const errorText = await response.text();
 60 |       let errorData;
 61 |       
 62 |       try {
 63 |         errorData = JSON.parse(errorText);
 64 |       } catch (parseError) {
 65 |         errorData = { message: errorText };
 66 |       }
 67 | 
 68 |       const detailedError = {
 69 |         status: response.status,
 70 |         statusText: response.statusText,
 71 |         url,
 72 |         errorResponse: errorData,
 73 |         duration: Date.now() - startTime,
 74 |         scriptId,
 75 |         deploymentId,
 76 |         timestamp: new Date().toISOString()
 77 |       };
 78 | 
 79 |       logger.error('DEPLOYMENT_UPDATE', 'API request failed', detailedError);
 80 |       
 81 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
 82 |       
 83 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
 84 |     }
 85 | 
 86 |     // Parse and return the response data
 87 |     const data = await response.json();
 88 |     
 89 |     logger.info('DEPLOYMENT_UPDATE', 'Successfully updated deployment', {
 90 |       scriptId,
 91 |       deploymentId,
 92 |       duration: Date.now() - startTime
 93 |     });
 94 |     
 95 |     console.log('✅ Successfully updated deployment');
 96 |     return data;
 97 |   } catch (error) {
 98 |     const errorDetails = {
 99 |       message: error.message,
100 |       stack: error.stack,
101 |       scriptId,
102 |       deploymentId,
103 |       duration: Date.now() - startTime,
104 |       timestamp: new Date().toISOString(),
105 |       errorType: error.name || 'Unknown'
106 |     };
107 | 
108 |     logger.error('DEPLOYMENT_UPDATE', 'Error updating deployment', errorDetails);
109 |     
110 |     console.error('❌ Error updating deployment:', errorDetails);
111 |     
112 |     // Return detailed error information for debugging
113 |     return { 
114 |       error: true,
115 |       message: error.message,
116 |       details: errorDetails,
117 |       rawError: {
118 |         name: error.name,
119 |         stack: error.stack
120 |       }
121 |     };
122 |   }
123 | };
124 | 
125 | /**
126 |  * Tool configuration for updating a deployment of an Apps Script project.
127 |  * @type {Object}
128 |  */
129 | const apiTool = {
130 |   function: executeFunction,
131 |   definition: {
132 |     type: 'function',
133 |     function: {
134 |       name: 'script_projects_deployments_update',
135 |       description: 'Updates a deployment of an Apps Script project.',
136 |       parameters: {
137 |         type: 'object',
138 |         properties: {
139 |           scriptId: {
140 |             type: 'string',
141 |             description: 'The ID of the script to update.'
142 |           },
143 |           deploymentId: {
144 |             type: 'string',
145 |             description: 'The ID of the deployment to update.'
146 |           },
147 |           deploymentConfig: {
148 |             type: 'object',
149 |             properties: {
150 |               manifestFileName: {
151 |                 type: 'string',
152 |                 description: 'The name of the manifest file.'
153 |               },
154 |               versionNumber: {
155 |                 type: 'integer',
156 |                 description: 'The version number of the deployment.'
157 |               },
158 |               description: {
159 |                 type: 'string',
160 |                 description: 'A description of the deployment.'
161 |               }
162 |             },
163 |             required: ['manifestFileName', 'versionNumber', 'description']
164 |           }
165 |         },
166 |         required: ['scriptId', 'deploymentId', 'deploymentConfig']
167 |       }
168 |     }
169 |   }
170 | };
171 | 
172 | export { apiTool };
```

--------------------------------------------------------------------------------
/test/test-mcp-fetch-processes.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Simple MCP Server Test - Fetch Script Processes List
  5 |  * This test focuses on fetching script processes using the MCP tool to identify errors
  6 |  */
  7 | 
  8 | import { discoverTools } from './lib/tools.js';
  9 | import { logger } from './lib/logger.js';
 10 | 
 11 | // Known working script ID from previous tests
 12 | const SCRIPT_ID = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';
 13 | 
 14 | async function testFetchProcesses() {
 15 |   console.log('🔄 Testing MCP Server - Fetch Script Processes List\n');
 16 |   
 17 |   try {
 18 |     // Set logging to capture detailed information
 19 |     process.env.LOG_LEVEL = 'debug';
 20 |     
 21 |     console.log('🔍 Step 1: Discovering MCP tools...');
 22 |     const tools = await discoverTools();
 23 |     console.log(`✅ Found ${tools.length} tools\n`);
 24 |     
 25 |     // Find the script processes list tool
 26 |     const processListTool = tools.find(tool => 
 27 |       tool.definition?.function?.name === 'd94_script_processes_list' ||
 28 |       tool.definition?.function?.name === 'script_processes_list'
 29 |     );
 30 |     
 31 |     if (!processListTool) {
 32 |       console.error('❌ Script processes list tool not found');
 33 |       console.log('Available tools:');
 34 |       tools.forEach(tool => {
 35 |         console.log(`  - ${tool.definition?.function?.name}`);
 36 |       });
 37 |       return;
 38 |     }
 39 |     
 40 |     console.log('🎯 Step 2: Found script processes list tool');
 41 |     console.log(`   Name: ${processListTool.definition.function.name}`);
 42 |     console.log(`   Description: ${processListTool.definition.function.description}`);
 43 |     console.log(`   Required params: ${processListTool.definition.function.parameters.required?.join(', ') || 'none'}\n`);
 44 |     
 45 |     // Test the tool with minimal parameters
 46 |     console.log('🚀 Step 3: Calling tool to fetch processes...');
 47 |     console.log(`   Script ID: ${SCRIPT_ID}`);
 48 |     
 49 |     try {
 50 |       const startTime = Date.now();
 51 |       
 52 |       const result = await processListTool.function({
 53 |         scriptId: SCRIPT_ID,
 54 |         pageSize: 10
 55 |       });
 56 |       
 57 |       const duration = Date.now() - startTime;
 58 |       
 59 |       console.log(`✅ Success! Call completed in ${duration}ms`);
 60 |       console.log('\n📊 RESULT:');
 61 |       console.log('=' .repeat(50));
 62 |       
 63 |       if (result && typeof result === 'object') {
 64 |         if (result.processes && Array.isArray(result.processes)) {
 65 |           console.log(`Found ${result.processes.length} processes:`);
 66 |           result.processes.forEach((process, index) => {
 67 |             console.log(`\n  Process ${index + 1}:`);
 68 |             console.log(`    Function: ${process.functionName || 'N/A'}`);
 69 |             console.log(`    Type: ${process.processType || 'N/A'}`);
 70 |             console.log(`    Status: ${process.processStatus || 'N/A'}`);
 71 |             console.log(`    Start: ${process.startTime || 'N/A'}`);
 72 |             console.log(`    Duration: ${process.duration || 'N/A'}`);
 73 |           });
 74 |         } else {
 75 |           console.log('No processes found in result');
 76 |         }
 77 |         
 78 |         if (result.nextPageToken) {
 79 |           console.log(`\nNext page token available: ${result.nextPageToken.substring(0, 20)}...`);
 80 |         }
 81 |       } else {
 82 |         console.log('Unexpected result format:');
 83 |         console.log(JSON.stringify(result, null, 2));
 84 |       }
 85 |       
 86 |     } catch (error) {
 87 |       console.log('\n❌ ERROR OCCURRED:');
 88 |       console.log('=' .repeat(50));
 89 |       console.log(`Error Type: ${error.constructor.name}`);
 90 |       console.log(`Error Message: ${error.message}`);
 91 |       
 92 |       if (error.response) {
 93 |         console.log(`HTTP Status: ${error.response.status}`);
 94 |         console.log(`Response Data: ${JSON.stringify(error.response.data, null, 2)}`);
 95 |       }
 96 |       
 97 |       if (error.stack) {
 98 |         console.log('\nStack Trace:');
 99 |         console.log(error.stack);
100 |       }
101 |       
102 |       // Log additional context if available
103 |       if (error.config) {
104 |         console.log('\nRequest Config:');
105 |         console.log(`URL: ${error.config.url}`);
106 |         console.log(`Method: ${error.config.method?.toUpperCase()}`);
107 |         console.log(`Headers: ${JSON.stringify(error.config.headers, null, 2)}`);
108 |       }
109 |     }
110 |     
111 |   } catch (setupError) {
112 |     console.log('\n💥 SETUP ERROR:');
113 |     console.log('=' .repeat(50));
114 |     console.log(`Error: ${setupError.message}`);
115 |     console.log(`Stack: ${setupError.stack}`);
116 |   }
117 |   
118 |   console.log('\n🏁 Test completed\n');
119 | }
120 | 
121 | // Test with problematic fields parameter
122 | async function testProblematicFields() {
123 |   console.log('🔥 Testing Problematic Fields Parameter\n');
124 |   
125 |   try {
126 |     const tools = await discoverTools();
127 |     const processListTool = tools.find(tool => 
128 |       tool.definition?.function?.name === 'd94_script_processes_list' ||
129 |       tool.definition?.function?.name === 'script_processes_list'
130 |     );
131 |     
132 |     if (!processListTool) {
133 |       console.log('❌ Tool not found for fields test');
134 |       return;
135 |     }
136 |     
137 |     console.log('🧪 Testing with known problematic fields parameter...');
138 |     
139 |     try {
140 |       const result = await processListTool.function({
141 |         scriptId: SCRIPT_ID,
142 |         pageSize: 5,
143 |         fields: 'processes(processType,functionName,startTime,duration,status)'
144 |       });
145 |       
146 |       console.log('😮 Unexpected success with problematic fields!');
147 |       console.log(JSON.stringify(result, null, 2));
148 |       
149 |     } catch (error) {
150 |       console.log('✅ Expected error with invalid "status" field:');
151 |       console.log(`   Error: ${error.message}`);
152 |       
153 |       if (error.response?.data) {
154 |         console.log('   API Response:');
155 |         console.log(JSON.stringify(error.response.data, null, 2));
156 |       }
157 |     }
158 |     
159 |   } catch (error) {
160 |     console.log(`❌ Fields test setup error: ${error.message}`);
161 |   }
162 | }
163 | 
164 | // Run both tests
165 | async function runAllTests() {
166 |   await testFetchProcesses();
167 |   console.log('\n' + '='.repeat(60) + '\n');
168 |   await testProblematicFields();
169 | }
170 | 
171 | runAllTests().catch(console.error);
172 | 
```

--------------------------------------------------------------------------------
/tools/google-app-script-api/apps-script-api/script-projects-get-content.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { getOAuthAccessToken } from '../../../lib/oauth-helper.js';
  2 | import { logger } from '../../../lib/logger.js';
  3 | 
  4 | /**
  5 |  * Function to get the content of a Google Apps Script project.
  6 |  *
  7 |  * @param {Object} args - Arguments for the request.
  8 |  * @param {string} args.scriptId - The ID of the script project to retrieve content for.
  9 |  * @param {string} [args.versionNumber] - The version number of the script project.
 10 |  * @param {string} [args.fields] - Selector specifying which fields to include in a partial response.
 11 |  * @param {string} [args.alt="json"] - Data format for response.
 12 |  * @param {string} [args.key] - API key for the project.
 13 |  * @param {string} [args.access_token] - OAuth access token.
 14 |  * @param {string} [args.prettyPrint="true"] - Returns response with indentations and line breaks.
 15 |  * @returns {Promise<Object>} - The content of the script project.
 16 |  */
 17 | const executeFunction = async ({ scriptId, versionNumber, fields, alt = "json", key, access_token, prettyPrint = "true" }) => {
 18 |   const baseUrl = 'https://script.googleapis.com';
 19 |   const startTime = Date.now();
 20 |   
 21 |   try {
 22 |     logger.info('SCRIPT_GET_CONTENT', 'Starting script content retrieval', { scriptId, versionNumber });
 23 | 
 24 |     // Get OAuth access token
 25 |     const token = await getOAuthAccessToken();
 26 |     
 27 |     // Construct the URL with query parameters
 28 |     const url = new URL(`${baseUrl}/v1/projects/${scriptId}/content`);
 29 |     if (versionNumber) url.searchParams.append('versionNumber', versionNumber);
 30 |     if (fields) url.searchParams.append('fields', fields);
 31 |     url.searchParams.append('alt', alt);
 32 |     if (key) url.searchParams.append('key', key);
 33 |     if (prettyPrint) url.searchParams.append('prettyPrint', prettyPrint);
 34 | 
 35 |     logger.debug('SCRIPT_GET_CONTENT', 'Constructed API URL', {
 36 |       url: url.toString(),
 37 |       pathSegments: url.pathname.split('/'),
 38 |       queryParams: Object.fromEntries(url.searchParams)
 39 |     });
 40 | 
 41 |     // Set up headers for the request
 42 |     const headers = {
 43 |       'Accept': 'application/json',
 44 |       'Authorization': `Bearer ${token}`
 45 |     };
 46 | 
 47 |     logger.logAPICall('GET', url.toString(), headers);
 48 | 
 49 |     // Perform the fetch request
 50 |     const fetchStartTime = Date.now();
 51 |     const response = await fetch(url.toString(), {
 52 |       method: 'GET',
 53 |       headers
 54 |     });
 55 |     
 56 |     const fetchDuration = Date.now() - fetchStartTime;
 57 |     const responseSize = response.headers.get('content-length') || 'unknown';
 58 |     
 59 |     logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);
 60 | 
 61 |     // Check if the response was successful
 62 |     if (!response.ok) {
 63 |       const errorText = await response.text();
 64 |       let errorData;
 65 |       
 66 |       try {
 67 |         errorData = JSON.parse(errorText);
 68 |       } catch (parseError) {
 69 |         errorData = { message: errorText };
 70 |       }
 71 | 
 72 |       const detailedError = {
 73 |         status: response.status,
 74 |         statusText: response.statusText,
 75 |         url: url.toString(),
 76 |         errorResponse: errorData,
 77 |         duration: Date.now() - startTime,
 78 |         scriptId,
 79 |         versionNumber,
 80 |         timestamp: new Date().toISOString()
 81 |       };
 82 | 
 83 |       logger.error('SCRIPT_GET_CONTENT', 'API request failed', detailedError);
 84 |       
 85 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
 86 |       
 87 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
 88 |     }
 89 | 
 90 |     // Parse and return the response data
 91 |     const data = await response.json();
 92 |     
 93 |     logger.info('SCRIPT_GET_CONTENT', 'Successfully retrieved script content', {
 94 |       scriptId,
 95 |       versionNumber,
 96 |       filesCount: data.files?.length || 0,
 97 |       duration: Date.now() - startTime
 98 |     });
 99 |     
100 |     console.log('✅ Successfully retrieved script content');
101 |     return data;
102 |   } catch (error) {
103 |     const errorDetails = {
104 |       message: error.message,
105 |       stack: error.stack,
106 |       scriptId,
107 |       versionNumber,
108 |       duration: Date.now() - startTime,
109 |       timestamp: new Date().toISOString(),
110 |       errorType: error.name || 'Unknown'
111 |     };
112 | 
113 |     logger.error('SCRIPT_GET_CONTENT', 'Error getting script project content', errorDetails);
114 |     
115 |     console.error('❌ Error getting script project content:', errorDetails);
116 |     
117 |     // Return detailed error information for debugging
118 |     return { 
119 |       error: true,
120 |       message: error.message,
121 |       details: errorDetails,
122 |       rawError: {
123 |         name: error.name,
124 |         stack: error.stack
125 |       }
126 |     };
127 |   }
128 | };
129 | 
130 | /**
131 |  * Tool configuration for getting the content of a Google Apps Script project.
132 |  * @type {Object}
133 |  */
134 | const apiTool = {
135 |   function: executeFunction,
136 |   definition: {
137 |     type: 'function',
138 |     function: {
139 |       name: 'script_projects_get_content',
140 |       description: 'Get the content of a Google Apps Script project.',
141 |       parameters: {
142 |         type: 'object',
143 |         properties: {
144 |           scriptId: {
145 |             type: 'string',
146 |             description: 'The ID of the script project to retrieve content for.'
147 |           },
148 |           versionNumber: {
149 |             type: 'string',
150 |             description: 'The version number of the script project.'
151 |           },
152 |           fields: {
153 |             type: 'string',
154 |             description: 'Selector specifying which fields to include in a partial response.'
155 |           },
156 |           alt: {
157 |             type: 'string',
158 |             description: 'Data format for response.'
159 |           },
160 |           key: {
161 |             type: 'string',
162 |             description: 'API key for the project.'
163 |           },
164 |           access_token: {
165 |             type: 'string',
166 |             description: 'OAuth access token.'
167 |           },
168 |           prettyPrint: {
169 |             type: 'string',
170 |             description: 'Returns response with indentations and line breaks.'
171 |           }
172 |         },
173 |         required: ['scriptId']
174 |       }
175 |     }
176 |   }
177 | };
178 | 
179 | export { apiTool };
```

--------------------------------------------------------------------------------
/test/test-oauth.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Test script to verify OAuth authentication setup
  5 |  */
  6 | 
  7 | import 'dotenv/config';
  8 | import { getOAuthAccessToken, getAuthHeaders } from './lib/oauth-helper.js';
  9 | 
 10 | async function testOAuthAuthentication() {
 11 |   console.log('🔐 Testing OAuth Authentication for Google Apps Script API...\n');
 12 |   console.log('🕐 Test started at:', new Date().toISOString());
 13 |   console.log('📂 Working directory:', process.cwd());
 14 |   console.log('🔧 Node.js version:', process.version);
 15 |   console.log('');
 16 |   
 17 |   try {
 18 |     // Test 1: Get access token
 19 |     console.log('📋 Step 1: Getting OAuth access token...');
 20 |     console.log('⏳ Attempting to retrieve access token from OAuth helper...');
 21 |     
 22 |     const startTime = Date.now();
 23 |     const accessToken = await getOAuthAccessToken();
 24 |     const duration = Date.now() - startTime;
 25 |     
 26 |     console.log('✅ Successfully obtained access token:', accessToken.substring(0, 20) + '...');
 27 |     console.log('⏱️  Token retrieval took:', duration + 'ms');
 28 |     console.log('📏 Full token length:', accessToken.length, 'characters');
 29 |     console.log('');
 30 |     
 31 |     // Test 2: Get auth headers
 32 |     console.log('📋 Step 2: Creating authorization headers...');
 33 |     console.log('⏳ Building authorization headers for API requests...');
 34 |     
 35 |     const headerStartTime = Date.now();
 36 |     const headers = await getAuthHeaders();
 37 |     const headerDuration = Date.now() - headerStartTime;
 38 |     
 39 |     console.log('✅ Successfully created auth headers:', JSON.stringify(headers, null, 2));
 40 |     console.log('⏱️  Header creation took:', headerDuration + 'ms');
 41 |     console.log('📊 Header keys count:', Object.keys(headers).length);
 42 |     console.log('');
 43 |     
 44 |     // Test 3: Test API call (optional - requires valid script ID)
 45 |     console.log('📋 Step 3: Testing API connectivity...');
 46 |     console.log('ℹ️  To test a full API call, you would need a valid script ID.');
 47 |     console.log('ℹ️  You can test with the script_processes_list tool in your MCP client.\n');
 48 |     
 49 |     const totalDuration = Date.now() - startTime;
 50 |     console.log('🎉 OAuth authentication test completed successfully!');
 51 |     console.log('✅ Your OAuth setup is working correctly.');
 52 |     console.log('⏱️  Total test duration:', totalDuration + 'ms');
 53 |     console.log('🕐 Test completed at:', new Date().toISOString());
 54 |     console.log('');
 55 |     
 56 |     console.log('📝 Next steps:');
 57 |     console.log('1. Test one of the tools in your MCP client (Claude Desktop, Postman, etc.)');
 58 |     console.log('2. Use a valid Google Apps Script project ID when calling the tools');
 59 |     console.log('3. Ensure your OAuth token has the required scopes for the operations you want to perform');
 60 |     
 61 |   } catch (error) {
 62 |     console.error('❌ OAuth authentication failed!');
 63 |     console.error('🕐 Error occurred at:', new Date().toISOString());
 64 |     console.error('');
 65 |     
 66 |     // Detailed error logging
 67 |     console.error('📋 Error Details:');
 68 |     console.error('  📄 Message:', error.message);
 69 |     console.error('  🏷️  Name:', error.name);
 70 |     console.error('  📊 Stack trace:');
 71 |     if (error.stack) {
 72 |       console.error(error.stack.split('\n').map(line => '    ' + line).join('\n'));
 73 |     } else {
 74 |       console.error('    (No stack trace available)');
 75 |     }
 76 |     console.error('');
 77 |     
 78 |     // Additional error information
 79 |     if (error.code) {
 80 |       console.error('  🔢 Error code:', error.code);
 81 |     }
 82 |     if (error.status) {
 83 |       console.error('  📊 HTTP status:', error.status);
 84 |     }
 85 |     if (error.statusText) {
 86 |       console.error('  📝 Status text:', error.statusText);
 87 |     }
 88 |     if (error.response) {
 89 |       console.error('  📬 Response data:', JSON.stringify(error.response, null, 2));
 90 |     }
 91 |     console.error('');
 92 |     
 93 |     // Environment check
 94 |     console.log('🔍 Environment Check:');
 95 |     console.log('  📂 Current directory:', process.cwd());
 96 |     console.log('  🔧 Node.js version:', process.version);
 97 |     console.log('  💾 Platform:', process.platform);
 98 |     console.log('  🏗️  Architecture:', process.arch);
 99 |     
100 |     // Check for .env file
101 |     try {
102 |       const fs = await import('fs');
103 |       const envPath = '.env';
104 |       const envExists = fs.existsSync(envPath);
105 |       console.log('  📄 .env file exists:', envExists);
106 |       
107 |       if (envExists) {
108 |         const envContent = fs.readFileSync(envPath, 'utf8');
109 |         const envLines = envContent.split('\n').filter(line => line.trim() && !line.startsWith('#'));
110 |         console.log('  📋 .env file lines count:', envLines.length);
111 |           // Check for required OAuth variables (without showing values)
112 |         const requiredVars = ['GOOGLE_APP_SCRIPT_API_CLIENT_ID', 'GOOGLE_APP_SCRIPT_API_CLIENT_SECRET', 'GOOGLE_APP_SCRIPT_API_REFRESH_TOKEN'];
113 |         requiredVars.forEach(varName => {
114 |           const hasVar = envContent.includes(varName + '=');
115 |           console.log(`  🔑 ${varName} present:`, hasVar);
116 |         });
117 |       }
118 |     } catch (fsError) {
119 |       console.log('  ⚠️  Could not check .env file:', fsError.message);
120 |     }
121 |     console.log('');
122 |     
123 |     console.log('🔧 Troubleshooting steps:');
124 |     console.log('1. Check that your .env file contains valid OAuth credentials');
125 |     console.log('2. Verify your client ID and client secret are correct');
126 |     console.log('3. Ensure your refresh token is valid and not expired');
127 |     console.log('4. Follow the OAUTH_SETUP.md guide to obtain new credentials if needed');
128 |     console.log('5. Make sure the Google Apps Script API is enabled in your GCP project');
129 |     console.log('6. Check your internet connection and firewall settings');
130 |     console.log('7. Verify that the oauth-helper.js file exists and is accessible');
131 |     
132 |     process.exit(1);
133 |   }
134 | }
135 | 
136 | // Run the test if this script is executed directly
137 | console.log('🔍 Debug: process.argv[1]:', process.argv[1]);
138 | console.log('🔍 Debug: endsWith check:', process.argv[1] && process.argv[1].endsWith('test-oauth.js'));
139 | 
140 | if (process.argv[1] && process.argv[1].endsWith('test-oauth.js')) {
141 |   console.log('🚀 Starting OAuth test...');
142 |   testOAuthAuthentication();
143 | } else {
144 |   console.log('❌ Script not executed directly, skipping test');
145 | }
146 | 
```

--------------------------------------------------------------------------------
/tools/google-app-script-api/apps-script-api/script-projects-update-content.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { getOAuthAccessToken } from '../../../lib/oauth-helper.js';
  2 | import { logger } from '../../../lib/logger.js';
  3 | 
  4 | /**
  5 |  * Function to update the content of a Google Apps Script project.
  6 |  *
  7 |  * @param {Object} args - Arguments for the update.
  8 |  * @param {string} args.scriptId - The ID of the script project to update.
  9 |  * @param {Array<Object>} args.files - The files to be updated in the script project.
 10 |  * @returns {Promise<Object>} - The result of the update operation.
 11 |  */
 12 | const executeFunction = async ({ scriptId, files }) => {
 13 |   const baseUrl = 'https://script.googleapis.com';
 14 |   const startTime = Date.now();
 15 | 
 16 |   try {
 17 |     logger.info('SCRIPT_UPDATE_CONTENT', 'Starting script content update', { 
 18 |       scriptId, 
 19 |       filesCount: files?.length || 0 
 20 |     });
 21 | 
 22 |     // Get OAuth access token
 23 |     const token = await getOAuthAccessToken();
 24 |     
 25 |     // Construct the URL for the request
 26 |     const url = `${baseUrl}/v1/projects/${scriptId}/content`;
 27 | 
 28 |     // Set up headers for the request
 29 |     const headers = {
 30 |       'Authorization': `Bearer ${token}`,
 31 |       'Content-Type': 'application/json',
 32 |       'Accept': 'application/json'
 33 |     };
 34 | 
 35 |     // Prepare the body of the request
 36 |     const requestBody = { scriptId, files };
 37 |     const body = JSON.stringify(requestBody);
 38 | 
 39 |     logger.logAPICall('PUT', url, headers, requestBody);
 40 | 
 41 |     // Perform the fetch request
 42 |     const fetchStartTime = Date.now();
 43 |     const response = await fetch(url, {
 44 |       method: 'PUT',
 45 |       headers,
 46 |       body
 47 |     });
 48 |     
 49 |     const fetchDuration = Date.now() - fetchStartTime;
 50 |     const responseSize = response.headers.get('content-length') || 'unknown';
 51 |     
 52 |     logger.logAPIResponse('PUT', url, response.status, fetchDuration, responseSize);
 53 | 
 54 |     // Check if the response was successful
 55 |     if (!response.ok) {
 56 |       const errorText = await response.text();
 57 |       let errorData;
 58 |       
 59 |       try {
 60 |         errorData = JSON.parse(errorText);
 61 |       } catch (parseError) {
 62 |         errorData = { message: errorText };
 63 |       }
 64 | 
 65 |       const detailedError = {
 66 |         status: response.status,
 67 |         statusText: response.statusText,
 68 |         url,
 69 |         errorResponse: errorData,
 70 |         duration: Date.now() - startTime,
 71 |         scriptId,
 72 |         filesCount: files?.length || 0,
 73 |         timestamp: new Date().toISOString()
 74 |       };
 75 | 
 76 |       logger.error('SCRIPT_UPDATE_CONTENT', 'API request failed', detailedError);
 77 |       
 78 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
 79 |       
 80 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
 81 |     }
 82 | 
 83 |     // Parse and return the response data
 84 |     const data = await response.json();
 85 |     
 86 |     logger.info('SCRIPT_UPDATE_CONTENT', 'Successfully updated script content', {
 87 |       scriptId,
 88 |       filesCount: files?.length || 0,
 89 |       duration: Date.now() - startTime
 90 |     });
 91 |     
 92 |     console.log('✅ Successfully updated script content');
 93 |     return data;
 94 |   } catch (error) {
 95 |     const errorDetails = {
 96 |       message: error.message,
 97 |       stack: error.stack,
 98 |       scriptId,
 99 |       filesCount: files?.length || 0,
100 |       duration: Date.now() - startTime,
101 |       timestamp: new Date().toISOString(),
102 |       errorType: error.name || 'Unknown'
103 |     };
104 | 
105 |     logger.error('SCRIPT_UPDATE_CONTENT', 'Error updating script project content', errorDetails);
106 |     
107 |     console.error('❌ Error updating script project content:', errorDetails);
108 |     
109 |     // Return detailed error information for debugging
110 |     return { 
111 |       error: true,
112 |       message: error.message,
113 |       details: errorDetails,
114 |       rawError: {
115 |         name: error.name,
116 |         stack: error.stack
117 |       }
118 |     };
119 |   }
120 | };
121 | 
122 | /**
123 |  * Tool configuration for updating Google Apps Script project content.
124 |  * @type {Object}
125 |  */
126 | const apiTool = {
127 |   function: executeFunction,
128 |   definition: {
129 |     type: 'function',
130 |     function: {
131 |       name: 'update_script_content',
132 |       description: 'Updates the content of a specified Google Apps Script project.',
133 |       parameters: {
134 |         type: 'object',
135 |         properties: {
136 |           scriptId: {
137 |             type: 'string',
138 |             description: 'The ID of the script project to update.'
139 |           },
140 |           files: {
141 |             type: 'array',
142 |             items: {
143 |               type: 'object',
144 |               properties: {
145 |                 name: {
146 |                   type: 'string',
147 |                   description: 'The name of the file.'
148 |                 },
149 |                 lastModifyUser: {
150 |                   type: 'object',
151 |                   properties: {
152 |                     photoUrl: { type: 'string' },
153 |                     domain: { type: 'string' },
154 |                     name: { type: 'string' },
155 |                     email: { type: 'string' }
156 |                   }
157 |                 },
158 |                 type: {
159 |                   type: 'string',
160 |                   description: 'The type of the file.'
161 |                 },
162 |                 updateTime: { type: 'string' },
163 |                 source: { type: 'string' },
164 |                 createTime: { type: 'string' },
165 |                 functionSet: {
166 |                   type: 'object',
167 |                   properties: {
168 |                     values: {
169 |                       type: 'array',
170 |                       items: {
171 |                         type: 'object',
172 |                         properties: {
173 |                           parameters: {
174 |                             type: 'array',
175 |                             items: {
176 |                               type: 'object',
177 |                               properties: {
178 |                                 value: { type: 'string' }
179 |                               }
180 |                             }
181 |                           },
182 |                           name: { type: 'string' }
183 |                         }
184 |                       }
185 |                     }
186 |                   }
187 |                 }
188 |               }
189 |             },
190 |             description: 'The files to be updated in the script project.'
191 |           }
192 |         },
193 |         required: ['scriptId', 'files']
194 |       }
195 |     }
196 |   }
197 | };
198 | 
199 | export { apiTool };
```

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

```javascript
  1 | import { getOAuthAccessToken } from '../../../lib/oauth-helper.js';
  2 | import { logger } from '../../../lib/logger.js';
  3 | 
  4 | /**
  5 |  * Function to list the versions of a Google Apps Script project.
  6 |  *
  7 |  * @param {Object} args - Arguments for the request.
  8 |  * @param {string} args.scriptId - The ID of the script project.
  9 |  * @param {number} [args.pageSize=100] - The number of versions to return per page.
 10 |  * @param {string} [args.pageToken] - The token for the next page of results.
 11 |  * @param {string} [args.fields] - Selector specifying which fields to include in a partial response.
 12 |  * @param {string} [args.alt='json'] - Data format for response.
 13 |  * @param {string} [args.key] - API key for the request.
 14 |  * @param {string} [args.access_token] - OAuth access token.
 15 |  * @param {string} [args.oauth_token] - OAuth 2.0 token for the current user.
 16 |  * @param {boolean} [args.prettyPrint=true] - Returns response with indentations and line breaks.
 17 |  * @returns {Promise<Object>} - The result of the request containing the versions of the script project.
 18 |  */
 19 | const executeFunction = async ({ scriptId, pageSize = 100, pageToken, fields, key, access_token, oauth_token, prettyPrint = true }) => {
 20 |   const baseUrl = 'https://script.googleapis.com';
 21 |   const startTime = Date.now();
 22 |   
 23 |   try {
 24 |     logger.info('SCRIPT_VERSIONS_LIST', 'Starting script versions list request', { scriptId, pageSize, pageToken });
 25 |     
 26 |     // Get OAuth access token
 27 |     const token = await getOAuthAccessToken();
 28 |     
 29 |     // Construct the URL with query parameters
 30 |     const url = new URL(`${baseUrl}/v1/projects/${scriptId}/versions`);
 31 |     url.searchParams.append('pageSize', pageSize.toString());
 32 |     if (pageToken) url.searchParams.append('pageToken', pageToken);
 33 |     if (fields) url.searchParams.append('fields', fields);
 34 |     url.searchParams.append('alt', 'json');
 35 |     if (key) url.searchParams.append('key', key);
 36 |     if (prettyPrint) url.searchParams.append('prettyPrint', prettyPrint.toString());
 37 | 
 38 |     logger.debug('SCRIPT_VERSIONS_LIST', 'Constructed API URL', {
 39 |       url: url.toString(),
 40 |       pathSegments: url.pathname.split('/'),
 41 |       queryParams: Object.fromEntries(url.searchParams)
 42 |     });
 43 | 
 44 |     // Set up headers for the request
 45 |     const headers = {
 46 |       'Accept': 'application/json',
 47 |       'Authorization': `Bearer ${token}`
 48 |     };
 49 | 
 50 |     logger.logAPICall('GET', url.toString(), headers);
 51 | 
 52 |     // Perform the fetch request
 53 |     const fetchStartTime = Date.now();
 54 |     const response = await fetch(url.toString(), {
 55 |       method: 'GET',
 56 |       headers
 57 |     });
 58 |     
 59 |     const fetchDuration = Date.now() - fetchStartTime;
 60 |     const responseSize = response.headers.get('content-length') || 'unknown';
 61 |     
 62 |     logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);
 63 | 
 64 |     // Check if the response was successful
 65 |     if (!response.ok) {
 66 |       const errorText = await response.text();
 67 |       let errorData;
 68 |       
 69 |       try {
 70 |         errorData = JSON.parse(errorText);
 71 |       } catch (parseError) {
 72 |         errorData = { message: errorText };
 73 |       }
 74 | 
 75 |       const detailedError = {
 76 |         status: response.status,
 77 |         statusText: response.statusText,
 78 |         url: url.toString(),
 79 |         errorResponse: errorData,
 80 |         duration: Date.now() - startTime,
 81 |         scriptId,
 82 |         timestamp: new Date().toISOString()
 83 |       };
 84 | 
 85 |       logger.error('SCRIPT_VERSIONS_LIST', 'API request failed', detailedError);
 86 |       
 87 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
 88 |       
 89 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
 90 |     }
 91 | 
 92 |     // Parse and return the response data
 93 |     const data = await response.json();
 94 |     
 95 |     logger.info('SCRIPT_VERSIONS_LIST', 'Successfully retrieved script versions', {
 96 |       scriptId,
 97 |       versionsCount: data.versions?.length || 0,
 98 |       duration: Date.now() - startTime
 99 |     });
100 |     
101 |     console.log('✅ Successfully retrieved script versions');
102 |     return data;
103 |   } catch (error) {
104 |     const errorDetails = {
105 |       message: error.message,
106 |       stack: error.stack,
107 |       scriptId,
108 |       duration: Date.now() - startTime,
109 |       timestamp: new Date().toISOString(),
110 |       errorType: error.name || 'Unknown'
111 |     };
112 | 
113 |     logger.error('SCRIPT_VERSIONS_LIST', 'Error listing script versions', errorDetails);
114 |     
115 |     console.error('❌ Error listing script versions:', errorDetails);
116 |     
117 |     // Return detailed error information for debugging
118 |     return { 
119 |       error: true,
120 |       message: error.message,
121 |       details: errorDetails,
122 |       rawError: {
123 |         name: error.name,
124 |         stack: error.stack
125 |       }
126 |     };
127 |   }
128 | };
129 | 
130 | /**
131 |  * Tool configuration for listing versions of a Google Apps Script project.
132 |  * @type {Object}
133 |  */
134 | const apiTool = {
135 |   function: executeFunction,
136 |   definition: {
137 |     type: 'function',
138 |     function: {
139 |       name: 'script_projects_versions_list',
140 |       description: 'List the versions of a Google Apps Script project.',
141 |       parameters: {
142 |         type: 'object',
143 |         properties: {
144 |           scriptId: {
145 |             type: 'string',
146 |             description: 'The ID of the script project.'
147 |           },
148 |           pageSize: {
149 |             type: 'integer',
150 |             description: 'The number of versions to return per page.'
151 |           },
152 |           pageToken: {
153 |             type: 'string',
154 |             description: 'The token for the next page of results.'
155 |           },
156 |           fields: {
157 |             type: 'string',
158 |             description: 'Selector specifying which fields to include in a partial response.'
159 |           },
160 |           alt: {
161 |             type: 'string',
162 |             enum: ['json'],
163 |             description: 'Data format for response.'
164 |           },
165 |           key: {
166 |             type: 'string',
167 |             description: 'API key for the request.'
168 |           },
169 |           access_token: {
170 |             type: 'string',
171 |             description: 'OAuth access token.'
172 |           },
173 |           oauth_token: {
174 |             type: 'string',
175 |             description: 'OAuth 2.0 token for the current user.'
176 |           },
177 |           prettyPrint: {
178 |             type: 'boolean',
179 |             description: 'Returns response with indentations and line breaks.'
180 |           }
181 |         },
182 |         required: ['scriptId']
183 |       }
184 |     }
185 |   }
186 | };
187 | 
188 | export { apiTool };
```

--------------------------------------------------------------------------------
/test/test-mcp-errors.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Comprehensive MCP Server Error Testing Script
  5 |  * Tests the script processes list tool with real script ID to identify specific errors
  6 |  */
  7 | 
  8 | import { discoverTools } from './lib/tools.js';
  9 | import { logger } from './lib/logger.js';
 10 | 
 11 | // Real script ID used throughout the codebase
 12 | const REAL_SCRIPT_ID = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';
 13 | 
 14 | async function testMCPErrors() {
 15 |   console.log('🚨 MCP Server Error Analysis - Script Processes List\n');
 16 |   
 17 |   try {
 18 |     // Set high verbosity for maximum details
 19 |     process.env.LOG_LEVEL = 'trace';
 20 |     
 21 |     // Discover tools
 22 |     logger.info('TEST', 'Starting comprehensive error analysis');
 23 |     const tools = await discoverTools();
 24 |     
 25 |     // Find the script processes list tool
 26 |     const processListTool = tools.find(tool => 
 27 |       tool.definition?.function?.name === 'script_processes_list'
 28 |     );
 29 |     
 30 |     if (!processListTool) {
 31 |       console.error('❌ script_processes_list tool not found');
 32 |       return;
 33 |     }
 34 |     
 35 |     console.log('🔍 Found script_processes_list tool');
 36 |     console.log(`📝 Description: ${processListTool.definition.function.description}`);
 37 |     console.log(`🔧 Required: ${processListTool.definition.function.parameters.required.join(', ')}`);
 38 |     console.log(`📋 Properties: ${Object.keys(processListTool.definition.function.parameters.properties).join(', ')}\n`);
 39 |     
 40 |     // TEST 1: Basic call with real script ID
 41 |     console.log('🧪 TEST 1: Basic call with real script ID');
 42 |     console.log(`🎯 Using script ID: ${REAL_SCRIPT_ID}`);
 43 |     try {
 44 |       const result1 = await processListTool.function({
 45 |         scriptId: REAL_SCRIPT_ID
 46 |       });
 47 |       console.log('✅ Success - Basic call worked');
 48 |       console.log(`📊 Result type: ${typeof result1}`);
 49 |       console.log(`📊 Result content: ${JSON.stringify(result1, null, 2)}`);
 50 |     } catch (error) {
 51 |       console.log('❌ Error in basic call:');
 52 |       console.log(`   Message: ${error.message}`);
 53 |       console.log(`   Type: ${error.constructor.name}`);
 54 |     }
 55 |     
 56 |     // TEST 2: Call with minimal valid fields parameter
 57 |     console.log('\n🧪 TEST 2: Call with valid fields parameter');
 58 |     try {
 59 |       const result2 = await processListTool.function({
 60 |         scriptId: REAL_SCRIPT_ID,
 61 |         fields: 'processes'
 62 |       });
 63 |       console.log('✅ Success - With fields parameter');
 64 |       console.log(`📊 Result: ${JSON.stringify(result2, null, 2)}`);
 65 |     } catch (error) {
 66 |       console.log('❌ Error with fields parameter:');
 67 |       console.log(`   Message: ${error.message}`);
 68 |       console.log(`   Type: ${error.constructor.name}`);
 69 |     }
 70 |     
 71 |     // TEST 3: Call with specific valid field selections
 72 |     console.log('\n🧪 TEST 3: Call with specific field selections');
 73 |     const validFields = [
 74 |       'processes(processType,functionName,startTime,duration)',
 75 |       'processes(processType,functionName)',
 76 |       'processes(startTime)',
 77 |       'processes.processType,processes.functionName'
 78 |     ];
 79 |     
 80 |     for (const field of validFields) {
 81 |       console.log(`\n   📝 Testing field: ${field}`);
 82 |       try {
 83 |         const result = await processListTool.function({
 84 |           scriptId: REAL_SCRIPT_ID,
 85 |           fields: field,
 86 |           pageSize: 3
 87 |         });
 88 |         console.log(`   ✅ Success with field: ${field}`);
 89 |         console.log(`   📊 Result: ${JSON.stringify(result, null, 2)}`);
 90 |       } catch (error) {
 91 |         console.log(`   ❌ Error with field '${field}': ${error.message}`);
 92 |       }
 93 |     }
 94 |     
 95 |     // TEST 4: Test different parameter combinations
 96 |     console.log('\n🧪 TEST 4: Testing different parameter combinations');
 97 |     
 98 |     const testCases = [
 99 |       { name: 'With pageSize only', params: { scriptId: REAL_SCRIPT_ID, pageSize: 5 } },
100 |       { name: 'With pageToken', params: { scriptId: REAL_SCRIPT_ID, pageToken: 'test_token' } },
101 |       { name: 'With deploymentId filter', params: { scriptId: REAL_SCRIPT_ID, deploymentId: 'test_deployment' } },
102 |       { name: 'With functionName filter', params: { scriptId: REAL_SCRIPT_ID, functionName: 'myFunction' } },
103 |       { name: 'With time filters', params: { 
104 |         scriptId: REAL_SCRIPT_ID, 
105 |         startTime: '2024-01-01T00:00:00Z',
106 |         endTime: '2024-12-31T23:59:59Z'
107 |       }},
108 |     ];
109 |     
110 |     for (const testCase of testCases) {
111 |       console.log(`\n   📝 Testing: ${testCase.name}`);
112 |       try {
113 |         const result = await processListTool.function(testCase.params);
114 |         console.log(`   ✅ Success: ${testCase.name}`);
115 |         console.log(`   📊 Result: ${JSON.stringify(result, null, 2)}`);
116 |       } catch (error) {
117 |         console.log(`   ❌ Error in ${testCase.name}: ${error.message}`);
118 |       }
119 |     }
120 |     
121 |     // TEST 5: Check if the script actually exists by trying to get its metadata
122 |     console.log('\n🧪 TEST 5: Verify script exists by getting metadata');
123 |     const scriptGetTool = tools.find(tool => 
124 |       tool.definition?.function?.name === 'script_projects_get'
125 |     );
126 |     
127 |     if (scriptGetTool) {
128 |       try {
129 |         const metadata = await scriptGetTool.function({
130 |           scriptId: REAL_SCRIPT_ID
131 |         });
132 |         console.log('✅ Script metadata retrieved successfully');
133 |         console.log(`📋 Script title: ${metadata.title || 'No title'}`);
134 |         console.log(`📋 Script ID: ${metadata.scriptId || 'No ID'}`);
135 |         console.log(`📋 Create time: ${metadata.createTime || 'No create time'}`);
136 |       } catch (error) {
137 |         console.log('❌ Error getting script metadata:');
138 |         console.log(`   Message: ${error.message}`);
139 |         console.log('   This might indicate the script doesn\'t exist or access issues');
140 |       }
141 |     }
142 |     
143 |   } catch (error) {
144 |     logger.error('TEST', 'Test failed with unexpected error', {
145 |       error: {
146 |         message: error.message,
147 |         stack: error.stack
148 |       }
149 |     });
150 |     console.error('❌ Test failed:', error.message);
151 |   }
152 |   
153 |   console.log('\n🏁 Error Analysis Complete');
154 |   console.log('\n📋 Summary of Findings:');
155 |   console.log('   • Check the detailed logs above for specific API errors');
156 |   console.log('   • Look for authentication issues, invalid parameters, or API limitations');
157 |   console.log('   • Note which parameter combinations work vs. which fail');
158 |   console.log('   • Verify if the script ID is accessible with current OAuth scopes');
159 | }
160 | 
161 | // Run the error analysis
162 | testMCPErrors().catch(console.error);
163 | 
```

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

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * OAuth Setup Script for Google Apps Script API
  5 |  * This script helps you obtain and securely store OAuth tokens
  6 |  */
  7 | 
  8 | import 'dotenv/config';
  9 | import { manualOAuthFlow } from '../lib/oauth-helper.js';
 10 | import { TokenManager } from '../lib/tokenManager.js';
 11 | import { readFileSync } from 'fs';
 12 | 
 13 | console.log('🔐 Google Apps Script API OAuth Setup');
 14 | console.log('=====================================\n');
 15 | 
 16 | async function setupOAuth() {
 17 |   console.log('📋 This script will help you set up OAuth authentication for Google Apps Script API.');
 18 |   console.log('📝 You need to have your CLIENT_ID and CLIENT_SECRET configured in .env file.\n');
 19 |   
 20 |   const tokenManager = new TokenManager();
 21 |   
 22 |   // Handle info command
 23 |   if (process.argv.includes('--info')) {
 24 |     const tokenInfo = tokenManager.getTokenInfo();
 25 |     
 26 |     console.log('🔍 Token Information:');
 27 |     console.log('=====================\n');
 28 |     
 29 |     if (tokenInfo.hasTokens) {
 30 |       console.log('✅ Tokens found');
 31 |       console.log(`📁 Location: ${tokenInfo.location}`);
 32 |       console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
 33 |       console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
 34 |       console.log(`📊 Status: ${tokenInfo.status}`);
 35 |       console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
 36 |     } else {
 37 |       console.log('❌ No tokens found');
 38 |       console.log(`📁 Expected location: ${tokenInfo.location}`);
 39 |       console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
 40 |     }
 41 |     
 42 |     process.exit(0);
 43 |   }
 44 |   
 45 |   // Handle clear command
 46 |   if (process.argv.includes('--clear')) {
 47 |     tokenManager.clearTokens();
 48 |     console.log('✅ Tokens cleared successfully.');
 49 |     process.exit(0);
 50 |   }
 51 |   
 52 |   // Check if tokens already exist
 53 |   const tokenInfo = tokenManager.getTokenInfo();
 54 |   if (tokenInfo.hasTokens) {
 55 |     console.log('🔍 Found existing tokens:');
 56 |     console.log(`   📁 Location: ${tokenInfo.location}`);
 57 |     console.log(`   💾 Saved at: ${tokenInfo.savedAt}`);
 58 |     console.log(`   ⏰ Expires at: ${tokenInfo.expiresAt}`);
 59 |     console.log(`   📊 Status: ${tokenInfo.status}`);
 60 |     console.log(`   🔐 Scope: ${tokenInfo.scope || 'Not specified'}\n`);
 61 |     
 62 |     if (!tokenInfo.isExpired) {
 63 |       console.log('✅ You already have valid tokens stored.');
 64 |       console.log('💡 To get new tokens, run: node oauth-setup.js --force');
 65 |       console.log('🗑️ To clear existing tokens, run: node oauth-setup.js --clear\n');
 66 |       
 67 |       if (!process.argv.includes('--force')) {
 68 |         process.exit(0);
 69 |       }
 70 |     }
 71 |   }
 72 |   
 73 |   try {
 74 |     // Check if .env file exists and has required credentials
 75 |     const envPath = '.env';
 76 |     let envContent = '';
 77 |     
 78 |     try {
 79 |       envContent = readFileSync(envPath, 'utf8');
 80 |       console.log('✅ Found .env file');
 81 |     } catch (error) {
 82 |       console.error('❌ No .env file found. Please create one first with your CLIENT_ID and CLIENT_SECRET.');
 83 |       console.log('\n📝 Example .env file content:');
 84 |       console.log('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
 85 |       console.log('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
 86 |       console.log('\n📖 Note: Refresh token is now stored securely and not needed in .env file');
 87 |       process.exit(1);
 88 |     }
 89 |     
 90 |     // Check for required credentials
 91 |     const hasClientId = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=') && 
 92 |                        !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
 93 |     const hasClientSecret = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=') && 
 94 |                            !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
 95 |     
 96 |     if (!hasClientId || !hasClientSecret) {
 97 |       console.error('❌ Missing CLIENT_ID or CLIENT_SECRET in .env file.');
 98 |       console.log('\n🔧 Please update your .env file with valid credentials:');
 99 |       console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_actual_client_id');
100 |       console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_actual_client_secret');
101 |       console.log('\n📖 See OAUTH_SETUP.md for instructions on obtaining these credentials.');
102 |       process.exit(1);
103 |     }
104 |     
105 |     console.log('✅ Found required credentials in .env file');
106 |     console.log('\n🚀 Starting OAuth flow...');
107 |     console.log('📱 Your browser will open automatically');
108 |     console.log('🔐 Please authorize the application when prompted');
109 |     console.log('⏳ Waiting for authorization...\n');
110 |     
111 |     // Start OAuth flow
112 |     const tokens = await manualOAuthFlow();
113 |     
114 |     if (tokens.refresh_token) {
115 |       console.log('\n🎉 OAuth setup successful!');
116 |       console.log('🔑 Access token obtained:', tokens.access_token ? '✅' : '❌');
117 |       console.log('🔄 Refresh token obtained:', tokens.refresh_token ? '✅' : '❌');
118 |       
119 |       // Save tokens securely using TokenManager
120 |       try {
121 |         tokenManager.saveTokens(tokens);
122 |         console.log('💾 Tokens saved securely');
123 |         
124 |         const tokenInfo = tokenManager.getTokenInfo();
125 |         console.log(`📁 Token location: ${tokenInfo.location}`);
126 |         console.log(`🔒 File permissions: Owner read/write only`);
127 |         
128 |         console.log('\n✅ Setup complete! Your OAuth tokens are now stored securely.');
129 |         console.log('🔐 Refresh tokens are stored in a secure OS-specific location');
130 |         console.log('🚀 You can now use the MCP server and API tools');
131 |         
132 |         console.log('\n🧪 Test your setup with:');
133 |         console.log('   node test-token-management.js');
134 |         
135 |       } catch (saveError) {
136 |         console.error('\n❌ Failed to save tokens:', saveError.message);
137 |         console.log('🔧 Please check file permissions and try again');
138 |         process.exit(1);
139 |       }
140 |       
141 |     } else {
142 |       console.log('\n⚠️ OAuth completed but no refresh token received.');
143 |       console.log('🔄 You may need to revoke and re-authorize the application.');
144 |       console.log('📖 Check the Google Cloud Console for your OAuth settings.');
145 |     }
146 |     
147 |   } catch (error) {
148 |     console.error('\n❌ OAuth setup failed:', error.message);
149 |     console.log('\n🔧 Troubleshooting:');
150 |     console.log('   1. Check your internet connection');
151 |     console.log('   2. Verify your CLIENT_ID and CLIENT_SECRET are correct');
152 |     console.log('   3. Ensure the redirect URI is registered in Google Cloud Console');
153 |     console.log('   4. Make sure Google Apps Script API is enabled');
154 |     console.log('   5. Try revoking and re-creating your OAuth credentials');
155 |     console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
156 |     process.exit(1);
157 |   }
158 | }
159 | 
160 | // Run setup
161 | setupOAuth().catch((error) => {
162 |   console.error('💥 Unexpected error:', error);
163 |   process.exit(1);
164 | });
165 | 
```

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

```javascript
  1 | import { logger } from '../../../lib/logger.js';
  2 | 
  3 | /**
  4 |  * Function to list script processes for a given script ID.
  5 |  *
  6 |  * @param {Object} args - Arguments for listing script processes.
  7 |  * @param {string} args.scriptId - The ID of the script to list processes for.
  8 |  * @param {number} [args.pageSize=100] - The number of processes to return per page.
  9 |  * @param {string} [args.functionName] - Filter by function name.
 10 |  * @param {string} [args.pageToken] - Token for pagination.
 11 |  * @param {string} [args.startTime] - Filter by start time.
 12 |  * @param {string} [args.endTime] - Filter by end time.
 13 |  * @param {string} [args.deploymentId] - Filter by deployment ID.
 14 |  * @param {string} [args.types] - Filter by process types.
 15 |  * @param {string} [args.statuses] - Filter by process statuses.
 16 |  * @param {string} [args.userAccessLevels] - Filter by user access levels.
 17 |  * @returns {Promise<Object>} - The result of the script processes listing.
 18 |  */
 19 | const executeFunction = async ({ scriptId, pageSize = 100, functionName, pageToken, startTime, endTime, deploymentId, types, statuses, userAccessLevels }) => {
 20 |   const baseUrl = 'https://script.googleapis.com';
 21 |   const accessToken = ''; // will be provided by the user
 22 |   const startTimeMs = Date.now();
 23 |   
 24 |   try {
 25 |     logger.info('SCRIPT_PROCESSES_LIST', 'Starting script processes list request', { scriptId, pageSize, functionName });
 26 | 
 27 |     // Construct the URL with query parameters
 28 |     const url = new URL(`${baseUrl}/v1/processes:listScriptProcesses`);
 29 |     url.searchParams.append('scriptId', scriptId);
 30 |     url.searchParams.append('pageSize', pageSize.toString());
 31 |     if (functionName) url.searchParams.append('scriptProcessFilter.functionName', functionName);
 32 |     if (pageToken) url.searchParams.append('pageToken', pageToken);
 33 |     if (startTime) url.searchParams.append('scriptProcessFilter.startTime', startTime);
 34 |     if (endTime) url.searchParams.append('scriptProcessFilter.endTime', endTime);
 35 |     if (deploymentId) url.searchParams.append('scriptProcessFilter.deploymentId', deploymentId);
 36 |     if (types) url.searchParams.append('scriptProcessFilter.types', types);
 37 |     if (statuses) url.searchParams.append('scriptProcessFilter.statuses', statuses);
 38 |     if (userAccessLevels) url.searchParams.append('scriptProcessFilter.userAccessLevels', userAccessLevels);
 39 |     url.searchParams.append('alt', 'json');
 40 |     url.searchParams.append('prettyPrint', 'true');
 41 | 
 42 |     logger.debug('SCRIPT_PROCESSES_LIST', 'Constructed API URL', {
 43 |       url: url.toString(),
 44 |       pathSegments: url.pathname.split('/'),
 45 |       queryParams: Object.fromEntries(url.searchParams)
 46 |     });
 47 | 
 48 |     // Set up headers for the request
 49 |     const headers = {
 50 |       'Authorization': `Bearer ${accessToken}`,
 51 |       'Accept': 'application/json'
 52 |     };
 53 | 
 54 |     logger.logAPICall('GET', url.toString(), headers);
 55 | 
 56 |     // Perform the fetch request
 57 |     const fetchStartTime = Date.now();
 58 |     const response = await fetch(url.toString(), {
 59 |       method: 'GET',
 60 |       headers
 61 |     });
 62 |     
 63 |     const fetchDuration = Date.now() - fetchStartTime;
 64 |     const responseSize = response.headers.get('content-length') || 'unknown';
 65 |     
 66 |     logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);
 67 | 
 68 |     // Check if the response was successful
 69 |     if (!response.ok) {
 70 |       const errorText = await response.text();
 71 |       let errorData;
 72 |       
 73 |       try {
 74 |         errorData = JSON.parse(errorText);
 75 |       } catch (parseError) {
 76 |         errorData = { message: errorText };
 77 |       }
 78 | 
 79 |       const detailedError = {
 80 |         status: response.status,
 81 |         statusText: response.statusText,
 82 |         url: url.toString(),
 83 |         errorResponse: errorData,
 84 |         duration: Date.now() - startTimeMs,
 85 |         scriptId,
 86 |         timestamp: new Date().toISOString()
 87 |       };
 88 | 
 89 |       logger.error('SCRIPT_PROCESSES_LIST', 'API request failed', detailedError);
 90 |       
 91 |       console.error('❌ API Error Details:', JSON.stringify(detailedError, null, 2));
 92 |       
 93 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
 94 |     }
 95 | 
 96 |     // Parse and return the response data
 97 |     const data = await response.json();
 98 |     
 99 |     logger.info('SCRIPT_PROCESSES_LIST', 'Successfully retrieved script processes', {
100 |       scriptId,
101 |       processesCount: data.processes?.length || 0,
102 |       duration: Date.now() - startTimeMs
103 |     });
104 |     
105 |     console.log('✅ Successfully retrieved script processes');
106 |     return data;
107 |   } catch (error) {
108 |     const errorDetails = {
109 |       message: error.message,
110 |       stack: error.stack,
111 |       scriptId,
112 |       duration: Date.now() - startTimeMs,
113 |       timestamp: new Date().toISOString(),
114 |       errorType: error.name || 'Unknown'
115 |     };
116 | 
117 |     logger.error('SCRIPT_PROCESSES_LIST', 'Error listing script processes', errorDetails);
118 |     
119 |     console.error('❌ Error listing script processes:', errorDetails);
120 |     
121 |     // Return detailed error information for debugging
122 |     return { 
123 |       error: true,
124 |       message: error.message,
125 |       details: errorDetails,
126 |       rawError: {
127 |         name: error.name,
128 |         stack: error.stack
129 |       }
130 |     };
131 |   }
132 | };
133 | 
134 | /**
135 |  * Tool configuration for listing script processes on Google Apps Script.
136 |  * @type {Object}
137 |  */
138 | const apiTool = {
139 |   function: executeFunction,
140 |   definition: {
141 |     type: 'function',
142 |     function: {
143 |       name: 'list_script_processes',
144 |       description: 'List information about a script\'s executed processes.',
145 |       parameters: {
146 |         type: 'object',
147 |         properties: {
148 |           scriptId: {
149 |             type: 'string',
150 |             description: 'The ID of the script to list processes for.'
151 |           },
152 |           pageSize: {
153 |             type: 'integer',
154 |             description: 'The number of processes to return per page.'
155 |           },
156 |           functionName: {
157 |             type: 'string',
158 |             description: 'Filter by function name.'
159 |           },
160 |           pageToken: {
161 |             type: 'string',
162 |             description: 'Token for pagination.'
163 |           },
164 |           startTime: {
165 |             type: 'string',
166 |             description: 'Filter by start time.'
167 |           },
168 |           endTime: {
169 |             type: 'string',
170 |             description: 'Filter by end time.'
171 |           },
172 |           deploymentId: {
173 |             type: 'string',
174 |             description: 'Filter by deployment ID.'
175 |           },
176 |           types: {
177 |             type: 'string',
178 |             description: 'Filter by process types.'
179 |           },
180 |           statuses: {
181 |             type: 'string',
182 |             description: 'Filter by process statuses.'
183 |           },
184 |           userAccessLevels: {
185 |             type: 'string',
186 |             description: 'Filter by user access levels.'
187 |           }
188 |         },
189 |         required: ['scriptId']
190 |       }
191 |     }
192 |   }
193 | };
194 | 
195 | export { apiTool };
```

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

```javascript
  1 | /**
  2 |  * OAuth Token Manager for secure token storage and management
  3 |  * Handles access token refresh and secure storage of refresh tokens
  4 |  */
  5 | 
  6 | import fs from 'fs';
  7 | import path from 'path';
  8 | import os from 'os';
  9 | 
 10 | export class TokenManager {
 11 |   constructor() {
 12 |     this.tokenDir = this.getTokenDirectory();
 13 |     this.tokenFile = path.join(this.tokenDir, 'tokens.json');
 14 |     this.ensureTokenDirectory();
 15 |   }
 16 | 
 17 |   /**
 18 |    * Get platform-specific directory for token storage
 19 |    * @returns {string} Token directory path
 20 |    */
 21 |   getTokenDirectory() {
 22 |     const platform = os.platform();
 23 |     const homedir = os.homedir();
 24 |     
 25 |     switch (platform) {
 26 |       case 'win32':
 27 |         return path.join(homedir, 'AppData', 'Roaming', 'google-apps-script-mcp');
 28 |       case 'darwin':
 29 |         return path.join(homedir, 'Library', 'Application Support', 'google-apps-script-mcp');
 30 |       default:
 31 |         return path.join(homedir, '.config', 'google-apps-script-mcp');
 32 |     }
 33 |   }
 34 | 
 35 |   /**
 36 |    * Ensure token directory exists
 37 |    */
 38 |   ensureTokenDirectory() {
 39 |     if (!fs.existsSync(this.tokenDir)) {
 40 |       fs.mkdirSync(this.tokenDir, { recursive: true });
 41 |       console.log(`📁 Created token directory: ${this.tokenDir}`);
 42 |     }
 43 |   }
 44 | 
 45 |   /**
 46 |    * Save OAuth tokens securely
 47 |    * @param {Object} tokens - OAuth tokens object
 48 |    */
 49 |   saveTokens(tokens) {
 50 |     const tokenData = {
 51 |       access_token: tokens.access_token,
 52 |       refresh_token: tokens.refresh_token,
 53 |       expires_at: Date.now() + (tokens.expires_in * 1000),
 54 |       token_type: tokens.token_type || 'Bearer',
 55 |       scope: tokens.scope,
 56 |       saved_at: new Date().toISOString()
 57 |     };
 58 |     
 59 |     try {
 60 |       fs.writeFileSync(this.tokenFile, JSON.stringify(tokenData, null, 2), { mode: 0o600 });
 61 |       console.log(`💾 Tokens saved securely to: ${this.tokenFile}`);
 62 |       console.log(`🔒 File permissions: 600 (owner read/write only)`);
 63 |     } catch (error) {
 64 |       console.error('❌ Failed to save tokens:', error.message);
 65 |       throw new Error(`Failed to save tokens: ${error.message}`);
 66 |     }
 67 |   }
 68 | 
 69 |   /**
 70 |    * Load stored OAuth tokens
 71 |    * @returns {Object|null} Stored tokens or null if not found
 72 |    */
 73 |   loadTokens() {
 74 |     if (!fs.existsSync(this.tokenFile)) {
 75 |       console.log('📝 No stored tokens found');
 76 |       return null;
 77 |     }
 78 |     
 79 |     try {
 80 |       const tokenData = JSON.parse(fs.readFileSync(this.tokenFile, 'utf8'));
 81 |       console.log(`📖 Loaded tokens from: ${this.tokenFile}`);
 82 |       console.log(`💾 Tokens saved at: ${tokenData.saved_at || 'Unknown'}`);
 83 |       return tokenData;
 84 |     } catch (error) {
 85 |       console.error('❌ Failed to load tokens:', error.message);
 86 |       return null;
 87 |     }
 88 |   }
 89 | 
 90 |   /**
 91 |    * Check if current access token is expired or will expire soon
 92 |    * @param {Object} tokens - Token object
 93 |    * @returns {boolean} True if token is expired or will expire within 1 minute
 94 |    */
 95 |   isTokenExpired(tokens) {
 96 |     if (!tokens || !tokens.expires_at) {
 97 |       return true;
 98 |     }
 99 |     
100 |     // Consider token expired if it expires within the next minute
101 |     const bufferTime = 60000; // 1 minute buffer
102 |     const isExpired = Date.now() >= (tokens.expires_at - bufferTime);
103 |     
104 |     if (isExpired) {
105 |       console.log('⏰ Access token is expired or will expire soon');
106 |     } else {
107 |       const expiresIn = Math.round((tokens.expires_at - Date.now()) / 1000 / 60);
108 |       console.log(`⏰ Access token expires in ${expiresIn} minutes`);
109 |     }
110 |     
111 |     return isExpired;
112 |   }
113 | 
114 |   /**
115 |    * Refresh access token using stored refresh token
116 |    * @param {string} clientId - OAuth client ID
117 |    * @param {string} clientSecret - OAuth client secret
118 |    * @returns {Promise<Object>} New tokens
119 |    */
120 |   async refreshAccessToken(clientId, clientSecret) {
121 |     const tokens = this.loadTokens();
122 |     if (!tokens || !tokens.refresh_token) {
123 |       throw new Error('No refresh token available. Please run OAuth setup again: node oauth-setup.js');
124 |     }
125 | 
126 |     console.log('🔄 Refreshing access token...');
127 |     
128 |     try {
129 |       const response = await fetch('https://oauth2.googleapis.com/token', {
130 |         method: 'POST',
131 |         headers: {
132 |           'Content-Type': 'application/x-www-form-urlencoded',
133 |         },
134 |         body: new URLSearchParams({
135 |           client_id: clientId,
136 |           client_secret: clientSecret,
137 |           refresh_token: tokens.refresh_token,
138 |           grant_type: 'refresh_token'
139 |         })
140 |       });
141 | 
142 |       if (!response.ok) {
143 |         const errorText = await response.text();
144 |         console.error('❌ Token refresh failed:', response.status, errorText);
145 |         throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
146 |       }
147 | 
148 |       const newTokens = await response.json();
149 |       
150 |       // Keep the existing refresh token if not provided in response
151 |       if (!newTokens.refresh_token) {
152 |         newTokens.refresh_token = tokens.refresh_token;
153 |       }
154 | 
155 |       // Save the refreshed tokens
156 |       this.saveTokens(newTokens);
157 |       console.log('✅ Access token refreshed successfully');
158 |       
159 |       return newTokens;
160 |     } catch (error) {
161 |       console.error('❌ Failed to refresh access token:', error.message);
162 |       throw error;
163 |     }
164 |   }
165 | 
166 |   /**
167 |    * Get a valid access token, refreshing if necessary
168 |    * @param {string} clientId - OAuth client ID
169 |    * @param {string} clientSecret - OAuth client secret
170 |    * @returns {Promise<string>} Valid access token
171 |    */
172 |   async getValidAccessToken(clientId, clientSecret) {
173 |     let tokens = this.loadTokens();
174 |     
175 |     if (!tokens) {
176 |       throw new Error('No tokens found. Please run OAuth setup first: node oauth-setup.js');
177 |     }
178 |     
179 |     if (this.isTokenExpired(tokens)) {
180 |       console.log('🔄 Token expired, refreshing...');
181 |       tokens = await this.refreshAccessToken(clientId, clientSecret);
182 |     } else {
183 |       console.log('✅ Using existing valid access token');
184 |     }
185 |     
186 |     return tokens.access_token;
187 |   }
188 | 
189 |   /**
190 |    * Check if tokens are stored and available
191 |    * @returns {boolean} True if refresh token is available
192 |    */
193 |   hasStoredTokens() {
194 |     const tokens = this.loadTokens();
195 |     return tokens && tokens.refresh_token;
196 |   }
197 | 
198 |   /**
199 |    * Clear stored tokens (for logout/reset)
200 |    */
201 |   clearTokens() {
202 |     if (fs.existsSync(this.tokenFile)) {
203 |       fs.unlinkSync(this.tokenFile);
204 |       console.log('🗑️ Stored tokens cleared');
205 |     }
206 |   }
207 | 
208 |   /**
209 |    * Get token storage information
210 |    * @returns {Object} Token storage info
211 |    */
212 |   getTokenInfo() {
213 |     const tokens = this.loadTokens();
214 |     if (!tokens) {
215 |       return {
216 |         hasTokens: false,
217 |         location: this.tokenFile,
218 |         status: 'No tokens stored'
219 |       };
220 |     }
221 | 
222 |     const isExpired = this.isTokenExpired(tokens);
223 |     const expiresAt = new Date(tokens.expires_at);
224 |     
225 |     return {
226 |       hasTokens: true,
227 |       location: this.tokenFile,
228 |       savedAt: tokens.saved_at,
229 |       expiresAt: expiresAt.toISOString(),
230 |       isExpired,
231 |       scope: tokens.scope,
232 |       status: isExpired ? 'Token expired' : 'Token valid'
233 |     };
234 |   }
235 | }
236 | 
237 | // Export using named export (already done at class declaration)
238 | 
```

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

```javascript
  1 | import { getAuthHeaders } from '../../../lib/oauth-helper.js';
  2 | import { logger } from '../../../lib/logger.js';
  3 | 
  4 | /**
  5 |  * Function to list processes for a Google Apps Script project.
  6 |  *
  7 |  * @param {Object} args - Arguments for the process listing.
  8 |  * @param {string} args.scriptId - The ID of the script to filter processes.
  9 |  * @param {string} [args.startTime] - The start time for filtering processes.
 10 |  * @param {string} [args.functionName] - The name of the function to filter processes.
 11 |  * @param {string} [args.deploymentId] - The deployment ID to filter processes.
 12 |  * @param {string} [args.projectName] - The project name to filter processes.
 13 |  * @param {Array<string>} [args.statuses] - The statuses to filter processes.
 14 |  * @param {string} [args.pageToken] - Token for pagination.
 15 |  * @param {Array<string>} [args.types] - The types of processes to filter.
 16 |  * @param {Array<string>} [args.userAccessLevels] - User access levels to filter.
 17 |  * @param {number} [args.pageSize=100] - The number of processes to return per page.
 18 |  * @param {string} [args.endTime] - The end time for filtering processes.
 19 |  * @param {string} [args.fields] - Selector specifying which fields to include in a partial response.
 20 |  * @param {boolean} [args.prettyPrint=true] - Returns response with indentations and line breaks.
 21 |  * @returns {Promise<Object>} - The result of the process listing.
 22 |  */
 23 | const executeFunction = async ({
 24 |   scriptId,
 25 |   startTime,
 26 |   functionName,
 27 |   deploymentId,
 28 |   projectName,
 29 |   statuses,
 30 |   pageToken,
 31 |   types,
 32 |   userAccessLevels,
 33 |   pageSize = 100,
 34 |   endTime,
 35 |   fields,
 36 |   prettyPrint = true
 37 | }) => {
 38 |   const baseUrl = 'https://script.googleapis.com';
 39 |   const startTime_exec = Date.now();
 40 | 
 41 |   logger.info('API_CALL', 'Starting script processes list request', {
 42 |     scriptId,
 43 |     pageSize,
 44 |     startTime,
 45 |     endTime,
 46 |     functionName,
 47 |     deploymentId,
 48 |     baseUrl
 49 |   });
 50 | 
 51 |   try {
 52 |     // Validate required parameters
 53 |     if (!scriptId) {
 54 |       logger.error('API_CALL', 'Missing required parameter: scriptId');
 55 |       throw new Error('scriptId is required');
 56 |     }
 57 | 
 58 |     // Construct the URL with query parameters
 59 |     const url = new URL(`${baseUrl}/v1/processes`);
 60 |     const params = new URLSearchParams();
 61 |     params.append('userProcessFilter.scriptId', scriptId);
 62 |     if (startTime) params.append('userProcessFilter.startTime', startTime);
 63 |     if (functionName) params.append('userProcessFilter.functionName', functionName);
 64 |     if (deploymentId) params.append('userProcessFilter.deploymentId', deploymentId);
 65 |     if (projectName) params.append('userProcessFilter.projectName', projectName);
 66 |     if (statuses) params.append('userProcessFilter.statuses', statuses.join(','));
 67 |     if (pageToken) params.append('pageToken', pageToken);
 68 |     if (types) params.append('userProcessFilter.types', types.join(','));
 69 |     if (userAccessLevels) params.append('userProcessFilter.userAccessLevels', userAccessLevels.join(','));
 70 |     if (endTime) params.append('userProcessFilter.endTime', endTime);
 71 |     if (fields) params.append('fields', fields);
 72 |     params.append('pageSize', pageSize);
 73 |     params.append('prettyPrint', prettyPrint);
 74 |     
 75 |     url.search = params.toString();
 76 | 
 77 |     logger.debug('API_CALL', 'Constructed API URL', {
 78 |       url: url.toString(),
 79 |       queryParams: Object.fromEntries(params)
 80 |     });
 81 | 
 82 |     // Get OAuth headers
 83 |     logger.debug('API_CALL', 'Getting OAuth headers');
 84 |     const headers = await getAuthHeaders();
 85 | 
 86 |     logger.logAPICall('GET', url.toString(), headers);
 87 | 
 88 |     // Perform the fetch request
 89 |     const fetchStartTime = Date.now();
 90 |     const response = await fetch(url.toString(), {
 91 |       method: 'GET',
 92 |       headers
 93 |     });
 94 |     
 95 |     const fetchDuration = Date.now() - fetchStartTime;
 96 |     const responseSize = response.headers.get('content-length') || 'unknown';
 97 |     
 98 |     logger.logAPIResponse('GET', url.toString(), response.status, fetchDuration, responseSize);
 99 | 
100 |     // Check if the response was successful
101 |     if (!response.ok) {
102 |       const errorText = await response.text();
103 |       let errorData;
104 |       
105 |       try {
106 |         errorData = JSON.parse(errorText);
107 |       } catch (parseError) {
108 |         errorData = { message: errorText };
109 |       }
110 | 
111 |       logger.error('API_CALL', 'API request failed', {
112 |         status: response.status,
113 |         statusText: response.statusText,
114 |         url: url.toString(),
115 |         errorResponse: errorData,
116 |         scriptId
117 |       });
118 |       
119 |       throw new Error(`API Error (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
120 |     }
121 | 
122 |     // Parse and return the response data
123 |     const data = await response.json();
124 |     const totalDuration = Date.now() - startTime_exec;
125 |     
126 |     logger.info('API_CALL', 'Script processes list request completed successfully', {
127 |       scriptId,
128 |       processCount: data.processes ? data.processes.length : 0,
129 |       hasNextPageToken: !!data.nextPageToken,
130 |       totalDuration: `${totalDuration}ms`,
131 |       responseSize: JSON.stringify(data).length
132 |     });
133 |     
134 |     return data;
135 |   } catch (error) {
136 |     logger.error('API_CALL', 'Script processes list request failed', {
137 |       scriptId,
138 |       error: {
139 |         message: error.message,
140 |         stack: error.stack
141 |       }
142 |     });
143 |     
144 |     console.error('Error listing processes:', error);
145 |     return { 
146 |       error: true,
147 |       message: error.message,
148 |       details: {
149 |         scriptId,
150 |         timestamp: new Date().toISOString(),
151 |         errorType: error.name || 'Unknown'
152 |       }
153 |     };
154 |   }
155 | };
156 | 
157 | /**
158 |  * Tool configuration for listing processes in Google Apps Script.
159 |  * @type {Object}
160 |  */
161 | const apiTool = {
162 |   function: executeFunction,
163 |   definition: {
164 |     type: 'function',
165 |     function: {
166 |       name: 'script_processes_list',
167 |       description: 'List processes for a Google Apps Script project.',
168 |       parameters: {
169 |         type: 'object',
170 |         properties: {
171 |           scriptId: {
172 |             type: 'string',
173 |             description: 'The ID of the script to filter processes.'
174 |           },
175 |           startTime: {
176 |             type: 'string',
177 |             description: 'The start time for filtering processes.'
178 |           },
179 |           functionName: {
180 |             type: 'string',
181 |             description: 'The name of the function to filter processes.'
182 |           },
183 |           deploymentId: {
184 |             type: 'string',
185 |             description: 'The deployment ID to filter processes.'
186 |           },
187 |           projectName: {
188 |             type: 'string',
189 |             description: 'The project name to filter processes.'
190 |           },
191 |           statuses: {
192 |             type: 'array',
193 |             items: {
194 |               type: 'string'
195 |             },
196 |             description: 'The statuses to filter processes.'
197 |           },
198 |           pageToken: {
199 |             type: 'string',
200 |             description: 'Token for pagination.'
201 |           },
202 |           types: {
203 |             type: 'array',
204 |             items: {
205 |               type: 'string'
206 |             },
207 |             description: 'The types of processes to filter.'
208 |           },
209 |           userAccessLevels: {
210 |             type: 'array',
211 |             items: {
212 |               type: 'string'
213 |             },
214 |             description: 'User access levels to filter.'
215 |           },
216 |           pageSize: {
217 |             type: 'integer',
218 |             description: 'The number of processes to return per page.'
219 |           },
220 |           endTime: {
221 |             type: 'string',
222 |             description: 'The end time for filtering processes.'
223 |           },
224 |           fields: {
225 |             type: 'string',
226 |             description: 'Selector specifying which fields to include in a partial response.'
227 |           },
228 |           prettyPrint: {
229 |             type: 'boolean',
230 |             description: 'Returns response with indentations and line breaks.'
231 |           }
232 |         },
233 |         required: ['scriptId']
234 |       }
235 |     }
236 |   }
237 | };
238 | 
239 | export { apiTool };
```

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

```markdown
  1 | # Enhanced MCP Server Logging
  2 | 
  3 | This document describes the enhanced logging capabilities added to the MCP server for detailed monitoring and debugging of tool responses.
  4 | 
  5 | ## Overview
  6 | 
  7 | The MCP server now includes comprehensive logging that tracks:
  8 | - Tool discovery and initialization
  9 | - Tool execution requests and responses
 10 | - API calls to Google Apps Script services
 11 | - Authentication flows
 12 | - Error conditions with detailed context
 13 | - Performance metrics
 14 | 
 15 | ## Configuration
 16 | 
 17 | ### Log Levels
 18 | 
 19 | Set the `LOG_LEVEL` environment variable in your `.env` file:
 20 | 
 21 | ```env
 22 | # LOG_LEVEL can be: error, warn, info, debug, trace
 23 | LOG_LEVEL=info
 24 | ```
 25 | 
 26 | **Available Log Levels:**
 27 | - `error`: Only error messages
 28 | - `warn`: Errors and warnings
 29 | - `info`: Errors, warnings, and informational messages (default)
 30 | - `debug`: All above plus detailed debugging information
 31 | - `trace`: Maximum verbosity (includes all internal operations)
 32 | 
 33 | ### Environment Variables
 34 | 
 35 | Add to your `.env` file:
 36 | ```env
 37 | # Logging Configuration
 38 | LOG_LEVEL=info
 39 | ```
 40 | 
 41 | ## Features
 42 | 
 43 | ### 1. Tool Execution Logging
 44 | 
 45 | Every tool execution is logged with:
 46 | - **Request ID**: Unique identifier for tracking
 47 | - **Tool Name**: Which tool was called
 48 | - **Arguments**: Input parameters (sanitized)
 49 | - **Execution Time**: How long the tool took to execute
 50 | - **Response Size**: Size of the response data
 51 | - **Success/Failure Status**: Whether the tool completed successfully
 52 | 
 53 | **Example Output:**
 54 | ```
 55 | [2025-06-01T10:30:15.123Z] [INFO] [TOOL_REQUEST] Executing tool: script_projects_get
 56 | {
 57 |   "tool": "script_projects_get",
 58 |   "arguments": {
 59 |     "scriptId": "1BxKdN9XvlHF8rF9mF8..."
 60 |   },
 61 |   "requestId": "req_1717234215123_abc123def"
 62 | }
 63 | 
 64 | [2025-06-01T10:30:15.456Z] [INFO] [TOOL_RESPONSE] Tool completed: script_projects_get
 65 | {
 66 |   "tool": "script_projects_get",
 67 |   "duration": "333ms",
 68 |   "requestId": "req_1717234215123_abc123def",
 69 |   "responseSize": 2048,
 70 |   "success": true
 71 | }
 72 | ```
 73 | 
 74 | ### 2. API Call Logging
 75 | 
 76 | All HTTP requests to Google APIs are logged with:
 77 | - **Method and URL**: Complete request details
 78 | - **Headers**: Sanitized headers (Authorization tokens are redacted)
 79 | - **Response Status**: HTTP status codes
 80 | - **Response Time**: Network latency
 81 | - **Response Size**: Payload size
 82 | 
 83 | **Example Output:**
 84 | ```
 85 | [2025-06-01T10:30:15.200Z] [DEBUG] [API_CALL] Making API request: GET https://script.googleapis.com/v1/projects/123
 86 | {
 87 |   "method": "GET",
 88 |   "url": "https://script.googleapis.com/v1/projects/123",
 89 |   "headers": {
 90 |     "Authorization": "Bearer ***REDACTED***",
 91 |     "Accept": "application/json",
 92 |     "Content-Type": "application/json"
 93 |   }
 94 | }
 95 | 
 96 | [2025-06-01T10:30:15.400Z] [DEBUG] [API_RESPONSE] API response: GET https://script.googleapis.com/v1/projects/123
 97 | {
 98 |   "method": "GET",
 99 |   "url": "https://script.googleapis.com/v1/projects/123",
100 |   "status": 200,
101 |   "responseTime": "200ms",
102 |   "responseSize": "1024 bytes"
103 | }
104 | ```
105 | 
106 | ### 3. Authentication Logging
107 | 
108 | OAuth authentication flows are tracked:
109 | - **Token Requests**: When access tokens are requested
110 | - **Token Refresh**: When tokens are refreshed
111 | - **Authentication Failures**: Failed auth attempts with reasons
112 | - **Token Information**: Token types and scopes (sanitized)
113 | 
114 | **Example Output:**
115 | ```
116 | [2025-06-01T10:30:14.500Z] [INFO] [AUTH] Requesting OAuth access token
117 | [2025-06-01T10:30:14.800Z] [INFO] [AUTH] Access token obtained successfully
118 | {
119 |   "duration": "300ms",
120 |   "tokenLength": 195,
121 |   "tokenPrefix": "ya29.a0AWY7C..."
122 | }
123 | ```
124 | 
125 | ### 4. Error Logging
126 | 
127 | Comprehensive error tracking includes:
128 | - **Stack traces**: Full error call stacks
129 | - **Context information**: What was being attempted
130 | - **Request parameters**: Input data that caused the error
131 | - **Timing information**: How long the operation took before failing
132 | 
133 | **Example Output:**
134 | ```
135 | [2025-06-01T10:30:16.123Z] [ERROR] [TOOL_ERROR] Tool failed: script_projects_get
136 | {
137 |   "tool": "script_projects_get",
138 |   "duration": "667ms",
139 |   "requestId": "req_1717234215456_def456ghi",
140 |   "error": {
141 |     "message": "API Error (404): Script project not found",
142 |     "stack": "Error: API Error (404): Script project not found\n    at executeFunction...",
143 |     "name": "Error"
144 |   }
145 | }
146 | ```
147 | 
148 | ### 5. Performance Metrics
149 | 
150 | Track performance across all operations:
151 | - **Tool execution times**: How long each tool takes
152 | - **API response times**: Network latency to Google services
153 | - **Authentication times**: OAuth token acquisition time
154 | - **Overall request duration**: End-to-end request processing
155 | 
156 | ## Usage
157 | 
158 | ### 1. Running with Enhanced Logging
159 | 
160 | ```bash
161 | # Start the MCP server with info level logging
162 | npm start
163 | 
164 | # Start with debug logging for detailed information
165 | LOG_LEVEL=debug npm start
166 | 
167 | # Start with minimal error-only logging
168 | LOG_LEVEL=error npm start
169 | ```
170 | 
171 | ### 2. Testing the Logging
172 | 
173 | Run the logging test script:
174 | ```bash
175 | node test-logging.js
176 | ```
177 | 
178 | This will demonstrate all logging features and show you what to expect.
179 | 
180 | ### 3. Log Output Examples
181 | 
182 | **Startup Logs:**
183 | ```
184 | [2025-06-01T10:30:10.000Z] [INFO] [STARTUP] Starting MCP server in STDIO mode
185 | [2025-06-01T10:30:10.100Z] [INFO] [DISCOVERY] Starting tool discovery for 17 tool paths
186 | [2025-06-01T10:30:10.500Z] [INFO] [DISCOVERY] Tool discovery completed
187 | {
188 |   "totalPaths": 17,
189 |   "successfullyLoaded": 17,
190 |   "failed": 0,
191 |   "toolNames": ["script_projects_get", "script_projects_deployments_list", ...]
192 | }
193 | ```
194 | 
195 | **Request Handling:**
196 | ```
197 | [2025-06-01T10:30:15.000Z] [INFO] [REQUEST] CallTool request received
198 | {
199 |   "requestId": "req_1717234215000_xyz789",
200 |   "toolName": "script_projects_get",
201 |   "arguments": {
202 |     "scriptId": "1BxKdN9XvlHF8rF9mF8..."
203 |   },
204 |   "timestamp": "2025-06-01T10:30:15.000Z"
205 | }
206 | ```
207 | 
208 | ## Log Categories
209 | 
210 | The logging system uses categories to organize different types of events:
211 | 
212 | - **STARTUP**: Server initialization and configuration
213 | - **DISCOVERY**: Tool loading and discovery process  
214 | - **REQUEST**: MCP protocol request handling
215 | - **EXECUTION**: Tool execution and results
216 | - **API_CALL**: HTTP requests to external APIs
217 | - **API_RESPONSE**: HTTP response processing
218 | - **AUTH**: Authentication and authorization
219 | - **ERROR**: Error conditions and failures
220 | - **TEST**: Testing and debugging operations
221 | 
222 | ## Benefits
223 | 
224 | 1. **Debugging**: Quickly identify issues with specific tools or API calls
225 | 2. **Performance Monitoring**: Track response times and identify bottlenecks
226 | 3. **Audit Trail**: Complete record of all operations for compliance
227 | 4. **Error Analysis**: Detailed error context for troubleshooting
228 | 5. **Usage Analytics**: Understand which tools are used most frequently
229 | 6. **Security Monitoring**: Track authentication events and failures
230 | 
231 | ## Log File Integration
232 | 
233 | While the current implementation outputs to console, you can easily redirect logs to files:
234 | 
235 | ```bash
236 | # Log everything to a file
237 | node mcpServer.js 2>&1 | tee mcp-server.log
238 | 
239 | # Log only errors to a separate file
240 | node mcpServer.js 2>error.log
241 | 
242 | # Use with log rotation tools like logrotate
243 | node mcpServer.js 2>&1 | logger -t mcp-server
244 | ```
245 | 
246 | ## Customization
247 | 
248 | The logging system is built with the `MCPLogger` class in `lib/logger.js`. You can:
249 | 
250 | 1. **Add new log categories**: Extend the logger for specific use cases
251 | 2. **Customize log format**: Modify the `formatMessage` method
252 | 3. **Add log filtering**: Implement custom filtering logic
253 | 4. **Integrate with external systems**: Send logs to monitoring services
254 | 
255 | ## Security Considerations
256 | 
257 | - **Token Redaction**: OAuth tokens are automatically redacted in logs
258 | - **Sensitive Data**: Personal data in request parameters should be sanitized
259 | - **Log Storage**: Ensure log files are properly secured if stored
260 | - **Retention**: Implement appropriate log retention policies
261 | 
262 | ## Troubleshooting
263 | 
264 | **Common Issues:**
265 | 
266 | 1. **Too much logging**: Set `LOG_LEVEL=warn` or `LOG_LEVEL=error`
267 | 2. **Missing logs**: Check that `LOG_LEVEL` is set appropriately
268 | 3. **Performance impact**: Debug level logging may impact performance in production
269 | 
270 | **Log Level Guidelines:**
271 | - **Production**: Use `info` or `warn` level
272 | - **Development**: Use `debug` level for detailed information
273 | - **Troubleshooting**: Use `trace` level for maximum detail
274 | - **Error monitoring**: Use `error` level for minimal output
275 | 
```

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

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import dotenv from "dotenv";
  4 | import express from "express";
  5 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  6 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  7 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
  8 | import {
  9 |   CallToolRequestSchema,
 10 |   ErrorCode,
 11 |   ListToolsRequestSchema,
 12 |   McpError,
 13 | } from "@modelcontextprotocol/sdk/types.js";
 14 | import { discoverTools } from "./lib/tools.js";
 15 | import { logger } from "./lib/logger.js";
 16 | 
 17 | import path from "path";
 18 | import { fileURLToPath } from "url";
 19 | 
 20 | const __filename = fileURLToPath(import.meta.url);
 21 | const __dirname = path.dirname(__filename);
 22 | 
 23 | dotenv.config({ path: path.resolve(__dirname, ".env") });
 24 | 
 25 | const SERVER_NAME = "generated-mcp-server";
 26 | 
 27 | async function transformTools(tools) {
 28 |   logger.info('SETUP', `Transforming ${tools.length} discovered tools`);
 29 |   const transformedTools = tools
 30 |     .map((tool) => {
 31 |       const definitionFunction = tool.definition?.function;
 32 |       if (!definitionFunction) {
 33 |         logger.warn('SETUP', `Tool missing definition function`, { toolPath: tool.path });
 34 |         return;
 35 |       }
 36 |       logger.debug('SETUP', `Transformed tool: ${definitionFunction.name}`, {
 37 |         name: definitionFunction.name,
 38 |         description: definitionFunction.description,
 39 |         requiredParams: definitionFunction.parameters?.required || []
 40 |       });
 41 |       return {
 42 |         name: definitionFunction.name,
 43 |         description: definitionFunction.description,
 44 |         inputSchema: definitionFunction.parameters,
 45 |       };
 46 |     })
 47 |     .filter(Boolean);
 48 |   
 49 |   logger.info('SETUP', `Successfully transformed ${transformedTools.length} tools`);
 50 |   return transformedTools;
 51 | }
 52 | 
 53 | async function setupServerHandlers(server, tools) {
 54 |   server.setRequestHandler(ListToolsRequestSchema, async () => {
 55 |     logger.info('REQUEST', 'Handling ListTools request');
 56 |     const transformedTools = await transformTools(tools);
 57 |     logger.info('REQUEST', `Returning ${transformedTools.length} available tools`, {
 58 |       toolNames: transformedTools.map(t => t.name)
 59 |     });
 60 |     return { tools: transformedTools };
 61 |   });
 62 | 
 63 |   server.setRequestHandler(CallToolRequestSchema, async (request) => {
 64 |     const startTime = Date.now();
 65 |     const requestId = logger.generateRequestId();
 66 |     const toolName = request.params.name;
 67 |     const args = request.params.arguments;
 68 |     
 69 |     logger.info('REQUEST', `CallTool request received`, {
 70 |       requestId,
 71 |       toolName,
 72 |       arguments: args,
 73 |       timestamp: new Date().toISOString()
 74 |     });
 75 | 
 76 |     const tool = tools.find((t) => t.definition.function.name === toolName);
 77 |     if (!tool) {
 78 |       logger.error('REQUEST', `Tool not found: ${toolName}`, {
 79 |         requestId,
 80 |         availableTools: tools.map(t => t.definition.function.name)
 81 |       });
 82 |       throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
 83 |     }
 84 | 
 85 |     const requiredParameters = tool.definition?.function?.parameters?.required || [];
 86 |     
 87 |     // Validate required parameters
 88 |     for (const requiredParameter of requiredParameters) {
 89 |       if (!(requiredParameter in args)) {
 90 |         logger.error('REQUEST', `Missing required parameter`, {
 91 |           requestId,
 92 |           toolName,
 93 |           missingParameter: requiredParameter,
 94 |           providedArgs: Object.keys(args)
 95 |         });
 96 |         throw new McpError(
 97 |           ErrorCode.InvalidParams,
 98 |           `Missing required parameter: ${requiredParameter}`
 99 |         );
100 |       }
101 |     }
102 | 
103 |     try {
104 |       logger.info('EXECUTION', `Executing tool: ${toolName}`, {
105 |         requestId,
106 |         toolPath: tool.path,
107 |         validatedArgs: args
108 |       });
109 | 
110 |       const result = await tool.function(args);
111 |       const duration = Date.now() - startTime;
112 |       
113 |       logger.info('EXECUTION', `Tool execution completed successfully`, {
114 |         requestId,
115 |         toolName,
116 |         duration: `${duration}ms`,
117 |         resultType: typeof result,
118 |         resultSize: JSON.stringify(result).length,
119 |         hasError: !!result.error
120 |       });
121 | 
122 |       // Log detailed result for debugging
123 |       logger.debug('EXECUTION', `Tool result details`, {
124 |         requestId,
125 |         toolName,
126 |         result: result
127 |       });
128 | 
129 |       return {
130 |         content: [
131 |           {
132 |             type: "text",
133 |             text: JSON.stringify(result, null, 2),
134 |           },
135 |         ],
136 |       };
137 |     } catch (error) {
138 |       const duration = Date.now() - startTime;
139 |       
140 |       logger.error('EXECUTION', `Tool execution failed`, {
141 |         requestId,
142 |         toolName,
143 |         duration: `${duration}ms`,
144 |         error: {
145 |           message: error.message,
146 |           stack: error.stack,
147 |           name: error.name
148 |         },
149 |         args: args
150 |       });
151 |       
152 |       console.error("[Error] Failed to fetch data:", error);
153 |       throw new McpError(
154 |         ErrorCode.InternalError,
155 |         `API error: ${error.message}`
156 |       );
157 |     }
158 |   });
159 | }
160 | 
161 | async function run() {
162 |   const args = process.argv.slice(2);
163 |   const isSSE = args.includes("--sse");
164 |   
165 |   logger.info('STARTUP', `Starting MCP server in ${isSSE ? 'SSE' : 'STDIO'} mode`);
166 |   
167 |   const tools = await discoverTools();
168 |   logger.info('STARTUP', `Discovered ${tools.length} tools`, {
169 |     toolPaths: tools.map(t => t.path),
170 |     toolNames: tools.map(t => t.definition?.function?.name).filter(Boolean)
171 |   });
172 | 
173 |   if (isSSE) {
174 |     const app = express();
175 |     const transports = {};
176 |     const servers = {};
177 | 
178 |     app.get("/sse", async (_req, res) => {
179 |       const sessionId = Date.now().toString();
180 |       logger.info('SSE', `New SSE connection established`, { sessionId });
181 |       
182 |       // Create a new Server instance for each session
183 |       const server = new Server(
184 |         {
185 |           name: SERVER_NAME,
186 |           version: "0.1.0",
187 |         },
188 |         {
189 |           capabilities: {
190 |             tools: {},
191 |           },
192 |         }
193 |       );
194 |       
195 |       server.onerror = (error) => {
196 |         logger.error('SSE', `Server error for session ${sessionId}`, error);
197 |         console.error("[Error]", error);
198 |       };
199 |       
200 |       await setupServerHandlers(server, tools);
201 | 
202 |       const transport = new SSEServerTransport("/messages", res);
203 |       transports[transport.sessionId] = transport;
204 |       servers[transport.sessionId] = server;
205 | 
206 |       res.on("close", async () => {
207 |         logger.info('SSE', `SSE connection closed`, { sessionId: transport.sessionId });
208 |         delete transports[transport.sessionId];
209 |         await server.close();
210 |         delete servers[transport.sessionId];
211 |       });
212 | 
213 |       await server.connect(transport);
214 |     });
215 | 
216 |     app.post("/messages", async (req, res) => {
217 |       const sessionId = req.query.sessionId;
218 |       const transport = transports[sessionId];
219 |       const server = servers[sessionId];
220 | 
221 |       if (transport && server) {
222 |         logger.debug('SSE', `Processing message for session`, { sessionId });
223 |         await transport.handlePostMessage(req, res);
224 |       } else {
225 |         logger.warn('SSE', `No transport/server found for session`, { sessionId });
226 |         res.status(400).send("No transport/server found for sessionId");
227 |       }
228 |     });
229 | 
230 |     const port = process.env.PORT || 3001;
231 |     app.listen(port, () => {
232 |       logger.info('SSE', `SSE server running on port ${port}`);
233 |       console.log(`[SSE Server] running on port ${port}`);
234 |     });
235 |   } else {
236 |     // stdio mode: single server instance
237 |     logger.info('STDIO', 'Initializing STDIO server');
238 |     
239 |     const server = new Server(
240 |       {
241 |         name: SERVER_NAME,
242 |         version: "0.1.0",
243 |       },
244 |       {
245 |         capabilities: {
246 |           tools: {},
247 |         },
248 |       }
249 |     );
250 |     
251 |     server.onerror = (error) => {
252 |       logger.error('STDIO', 'Server error', error);
253 |       console.error("[Error]", error);
254 |     };
255 |     
256 |     await setupServerHandlers(server, tools);
257 | 
258 |     process.on("SIGINT", async () => {
259 |       logger.info('STDIO', 'Received SIGINT, shutting down gracefully');
260 |       await server.close();
261 |       process.exit(0);
262 |     });
263 | 
264 |     const transport = new StdioServerTransport();
265 |     logger.info('STDIO', 'STDIO server ready for connections');
266 |     await server.connect(transport);
267 |   }
268 | }
269 | 
270 | run().catch(console.error);
271 | 
```

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

```javascript
  1 | // Complete test script to create a web app deployment using MCP tools
  2 | import { apiTool as updateContentTool } from './tools/google-app-script-api/apps-script-api/script-projects-update-content.js';
  3 | import { apiTool as createVersionTool } from './tools/google-app-script-api/apps-script-api/script-projects-versions-create.js';
  4 | import { apiTool as createDeploymentTool } from './tools/google-app-script-api/apps-script-api/script-projects-deployments-create.js';
  5 | import { apiTool as getDeploymentTool } from './tools/google-app-script-api/apps-script-api/script-projects-deployments-get.js';
  6 | 
  7 | const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';
  8 | 
  9 | async function createCompleteWebAppViaMCP() {
 10 |   console.log('🚀 Creating complete web app deployment via MCP tools...');
 11 |   
 12 |   try {
 13 |     // Step 1: Update script content with webapp configuration
 14 |     console.log('\n📝 Step 1: Updating script content with webapp configuration...');
 15 |     
 16 |     const updatedManifest = {
 17 |       "timeZone": "America/New_York",
 18 |       "dependencies": {},
 19 |       "exceptionLogging": "STACKDRIVER", 
 20 |       "runtimeVersion": "V8",
 21 |       "webapp": {
 22 |         "access": "ANYONE",
 23 |         "executeAs": "USER_ACCESSING"
 24 |       }
 25 |     };
 26 | 
 27 |     const scriptContent = {
 28 |       files: [
 29 |         {
 30 |           name: "appsscript",
 31 |           type: "JSON",
 32 |           source: JSON.stringify(updatedManifest, null, 2)
 33 |         },
 34 |         {
 35 |           name: "code",
 36 |           type: "SERVER_JS",
 37 |           source: `/**
 38 |  * Serves the HTML page when the web app is accessed
 39 |  */
 40 | function doGet() {
 41 |   return HtmlService.createHtmlOutputFromFile('index')
 42 |     .setTitle('Hello World App via MCP')
 43 |     .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
 44 | }
 45 | 
 46 | /**
 47 |  * Server-side function that can be called from the client
 48 |  */
 49 | function getGreeting(name) {
 50 |   if (!name) {
 51 |     name = 'World';
 52 |   }
 53 |   return \`Hello, \${name}! This web app was created via MCP tools.\`;
 54 | }
 55 | 
 56 | /**
 57 |  * Get current time
 58 |  */
 59 | function getCurrentTime() {
 60 |   return new Date().toLocaleString();
 61 | }`
 62 |         },
 63 |         {
 64 |           name: "index",
 65 |           type: "HTML",
 66 |           source: `<!DOCTYPE html>
 67 | <html>
 68 | <head>
 69 |   <base target="_top">
 70 |   <title>Hello World App via MCP</title>
 71 |   <style>
 72 |     body {
 73 |       font-family: 'Google Sans', Roboto, Arial, sans-serif;
 74 |       max-width: 600px;
 75 |       margin: 50px auto;
 76 |       padding: 20px;
 77 |       background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 78 |       color: white;
 79 |       text-align: center;
 80 |     }
 81 |     .container {
 82 |       background: rgba(255, 255, 255, 0.1);
 83 |       border-radius: 15px;
 84 |       padding: 30px;
 85 |       backdrop-filter: blur(10px);
 86 |       box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
 87 |     }
 88 |     h1 {
 89 |       color: #fff;
 90 |       margin-bottom: 30px;
 91 |       font-size: 2.5em;
 92 |     }
 93 |     input {
 94 |       padding: 12px;
 95 |       margin: 10px;
 96 |       border: none;
 97 |       border-radius: 8px;
 98 |       font-size: 16px;
 99 |       width: 250px;
100 |     }
101 |     button {
102 |       background: #4285f4;
103 |       color: white;
104 |       border: none;
105 |       padding: 12px 24px;
106 |       margin: 10px;
107 |       border-radius: 8px;
108 |       cursor: pointer;
109 |       font-size: 16px;
110 |       transition: background 0.3s;
111 |     }
112 |     button:hover {
113 |       background: #3367d6;
114 |     }
115 |     .result {
116 |       margin-top: 20px;
117 |       padding: 20px;
118 |       background: rgba(255, 255, 255, 0.2);
119 |       border-radius: 8px;
120 |       min-height: 50px;
121 |       display: flex;
122 |       align-items: center;
123 |       justify-content: center;
124 |     }
125 |     .loading {
126 |       display: none;
127 |       color: #ffd700;
128 |     }
129 |     .timestamp {
130 |       margin-top: 10px;
131 |       font-size: 0.9em;
132 |       opacity: 0.8;
133 |     }
134 |   </style>
135 | </head>
136 | <body>
137 |   <div class="container">
138 |     <h1>🌟 Hello World App via MCP 🌟</h1>
139 |     <p>This web app was created using MCP (Model Context Protocol) tools!</p>
140 |     
141 |     <div class="input-group">
142 |       <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
143 |     </div>
144 |     
145 |     <div>
146 |       <button onclick="sayHello()">Say Hello</button>
147 |       <button onclick="getTime()">Get Current Time</button>
148 |     </div>
149 |     
150 |     <div id="result" class="result">
151 |       Click a button to see the MCP magic! ✨
152 |     </div>
153 |     
154 |     <div id="loading" class="loading">Loading...</div>
155 |     
156 |     <div id="timestamp" class="timestamp"></div>
157 |   </div>
158 | 
159 |   <script>
160 |     function sayHello() {
161 |       showLoading();
162 |       const name = document.getElementById('nameInput').value;
163 |       
164 |       google.script.run
165 |         .withSuccessHandler(onSuccess)
166 |         .withFailureHandler(onFailure)
167 |         .getGreeting(name);
168 |     }
169 |     
170 |     function getTime() {
171 |       showLoading();
172 |       
173 |       google.script.run
174 |         .withSuccessHandler(onTimeSuccess)
175 |         .withFailureHandler(onFailure)
176 |         .getCurrentTime();
177 |     }
178 |     
179 |     function onSuccess(result) {
180 |       hideLoading();
181 |       document.getElementById('result').innerHTML = \`
182 |         <div style="font-size: 20px; font-weight: bold;">
183 |           \${result}
184 |         </div>
185 |       \`;
186 |       updateTimestamp();
187 |     }
188 |     
189 |     function onTimeSuccess(result) {
190 |       hideLoading();
191 |       document.getElementById('result').innerHTML = \`
192 |         <div style="font-size: 18px;">
193 |           🕒 Current Time: <strong>\${result}</strong>
194 |         </div>
195 |       \`;
196 |       updateTimestamp();
197 |     }
198 |     
199 |     function onFailure(error) {
200 |       hideLoading();
201 |       document.getElementById('result').innerHTML = \`
202 |         <div style="color: #ff6b6b;">
203 |           ❌ Error: \${error.message || 'Something went wrong!'}
204 |         </div>
205 |       \`;
206 |       updateTimestamp();
207 |     }
208 |     
209 |     function showLoading() {
210 |       document.getElementById('loading').style.display = 'block';
211 |       document.getElementById('result').style.display = 'none';
212 |     }
213 |     
214 |     function hideLoading() {
215 |       document.getElementById('loading').style.display = 'none';
216 |       document.getElementById('result').style.display = 'flex';
217 |     }
218 |     
219 |     function updateTimestamp() {
220 |       document.getElementById('timestamp').textContent = 
221 |         'Last updated: ' + new Date().toLocaleTimeString();
222 |     }
223 |     
224 |     // Initial timestamp
225 |     updateTimestamp();
226 |   </script>
227 | </body>
228 | </html>`
229 |         }
230 |       ]
231 |     };
232 | 
233 |     const updateResult = await updateContentTool.function({
234 |       scriptId: scriptId,
235 |       files: scriptContent.files
236 |     });
237 |     
238 |     console.log('✅ Script content updated:', JSON.stringify(updateResult, null, 2));
239 |     
240 |     // Step 2: Create a new version
241 |     console.log('\n📦 Step 2: Creating new version...');
242 |     
243 |     const versionResult = await createVersionTool.function({
244 |       scriptId: scriptId,
245 |       description: 'Web app version created via MCP tools'
246 |     });
247 |     
248 |     console.log('✅ Version created:', JSON.stringify(versionResult, null, 2));
249 |     
250 |     if (!versionResult.versionNumber) {
251 |       throw new Error('Failed to create version');
252 |     }
253 |     
254 |     // Step 3: Create deployment
255 |     console.log('\n🚀 Step 3: Creating deployment...');
256 |     
257 |     const deploymentResult = await createDeploymentTool.function({
258 |       scriptId: scriptId,
259 |       manifestFileName: 'appsscript',
260 |       versionNumber: versionResult.versionNumber,
261 |       description: 'Web app deployment via MCP tools'
262 |     });
263 |     
264 |     console.log('✅ Deployment created:', JSON.stringify(deploymentResult, null, 2));
265 |     
266 |     if (!deploymentResult.deploymentId) {
267 |       throw new Error('Failed to create deployment');
268 |     }
269 |     
270 |     // Step 4: Get deployment details with entry points
271 |     console.log('\n🔍 Step 4: Getting deployment details...');
272 |     
273 |     const deploymentDetails = await getDeploymentTool.function({
274 |       scriptId: scriptId,
275 |       deploymentId: deploymentResult.deploymentId
276 |     });
277 |     
278 |     console.log('✅ Deployment details:', JSON.stringify(deploymentDetails, null, 2));
279 |     
280 |     // Check for web app URL
281 |     if (deploymentDetails.entryPoints && deploymentDetails.entryPoints.length > 0) {
282 |       console.log('\n🎉 SUCCESS! Web app deployment completed!');
283 |       console.log('📱 Deployment ID:', deploymentResult.deploymentId);
284 |       
285 |       const webAppEntry = deploymentDetails.entryPoints.find(entry => entry.entryPointType === 'WEB_APP');
286 |       if (webAppEntry) {
287 |         console.log('🌐 Web App URL:', webAppEntry.webApp.url);
288 |         console.log('🔒 Access Level:', webAppEntry.webApp.access);
289 |         console.log('👤 Execute As:', webAppEntry.webApp.executeAs);
290 |         console.log('\n🚀 Your web app is now live and accessible to anyone!');
291 |       }
292 |     } else {
293 |       console.log('⚠️ Deployment created but no entry points found. May need to wait for propagation.');
294 |     }
295 |     
296 |   } catch (error) {
297 |     console.error('💥 Error during web app creation:', error);
298 |   }
299 | }
300 | 
301 | createCompleteWebAppViaMCP();
302 | 
```

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

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * OAuth Setup Script for Google Apps Script API
  5 |  * This script helps you obtain and securely store OAuth tokens
  6 |  */
  7 | 
  8 | import 'dotenv/config';
  9 | import { manualOAuthFlow } from '../lib/oauth-helper.js';
 10 | import { TokenManager } from '../lib/tokenManager.js';
 11 | import { readFileSync } from 'fs';
 12 | 
 13 | console.log('🔐 Google Apps Script API OAuth Setup');
 14 | console.log('=====================================\n');
 15 | 
 16 | async function setupOAuth() {
 17 |   console.log('📋 This script will help you set up OAuth authentication for Google Apps Script API.');
 18 |   console.log('📝 You need to have your CLIENT_ID and CLIENT_SECRET configured in .env file.\n');
 19 |   
 20 |   const tokenManager = new TokenManager();
 21 |   
 22 |   // Handle info command
 23 |   if (process.argv.includes('--info')) {
 24 |     const tokenInfo = tokenManager.getTokenInfo();
 25 |     
 26 |     console.log('🔍 Token Information:');
 27 |     console.log('=====================\n');
 28 |     
 29 |     if (tokenInfo.hasTokens) {
 30 |       console.log('✅ Tokens found');
 31 |       console.log(`📁 Location: ${tokenInfo.location}`);
 32 |       console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
 33 |       console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
 34 |       console.log(`📊 Status: ${tokenInfo.status}`);
 35 |       console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
 36 |     } else {
 37 |       console.log('❌ No tokens found');
 38 |       console.log(`📁 Expected location: ${tokenInfo.location}`);
 39 |       console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
 40 |     }
 41 |     
 42 |     process.exit(0);
 43 |   }
 44 |   
 45 |   // Check if tokens already exist
 46 |   const tokenInfo = tokenManager.getTokenInfo();
 47 |   if (tokenInfo.hasTokens) {
 48 |     console.log('🔍 Found existing tokens:');
 49 |     console.log(`   📁 Location: ${tokenInfo.location}`);
 50 |     console.log(`   💾 Saved at: ${tokenInfo.savedAt}`);
 51 |     console.log(`   ⏰ Expires at: ${tokenInfo.expiresAt}`);
 52 |     console.log(`   📊 Status: ${tokenInfo.status}`);
 53 |     console.log(`   🔐 Scope: ${tokenInfo.scope || 'Not specified'}\n`);
 54 |     
 55 |     if (!tokenInfo.isExpired) {
 56 |       console.log('✅ You already have valid tokens stored.');
 57 |       console.log('💡 To get new tokens, run: node oauth-setup.js --force');
 58 |       console.log('🗑️ To clear existing tokens, run: node oauth-setup.js --clear\n');
 59 |       
 60 |       if (!process.argv.includes('--force')) {
 61 |         process.exit(0);
 62 |       }
 63 |     }
 64 |   }
 65 |   
 66 |   // Handle clear command
 67 |   if (process.argv.includes('--clear')) {
 68 |     tokenManager.clearTokens();
 69 |     console.log('✅ Tokens cleared successfully.');
 70 |     process.exit(0);
 71 |   }
 72 |   
 73 |   try {
 74 |     // Check if .env file exists and has required credentials
 75 |     const envPath = '.env';
 76 |     let envContent = '';
 77 |     
 78 |     try {
 79 |       envContent = readFileSync(envPath, 'utf8');
 80 |       console.log('✅ Found .env file');
 81 |     } catch (error) {
 82 |       console.error('❌ No .env file found. Please create one first with your CLIENT_ID and CLIENT_SECRET.');
 83 |       console.log('\n📝 Example .env file content:');
 84 |       console.log('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
 85 |       console.log('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
 86 |       console.log('\n📖 Note: Refresh token is now stored securely and not needed in .env file');
 87 |       process.exit(1);
 88 |     }
 89 |       // Check for required credentials
 90 |     const hasClientId = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=') && 
 91 |                        !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here');
 92 |     const hasClientSecret = envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=') && 
 93 |                            !envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here');
 94 |     
 95 |     if (!hasClientId || !hasClientSecret) {
 96 |       console.error('❌ Missing CLIENT_ID or CLIENT_SECRET in .env file.');
 97 |       console.log('\n🔧 Please update your .env file with valid credentials:');
 98 |       console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_actual_client_id');
 99 |       console.log('   - GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_actual_client_secret');
100 |       console.log('\n📖 See OAUTH_SETUP.md for instructions on obtaining these credentials.');
101 |       process.exit(1);
102 |     }
103 |     
104 |     console.log('✅ Found required credentials in .env file');    console.log('\n🚀 Starting OAuth flow...');
105 |     console.log('📱 Your browser will open automatically');
106 |     console.log('🔐 Please authorize the application when prompted');
107 |     console.log('⏳ Waiting for authorization...\n');
108 |     
109 |     // Start OAuth flow
110 |     const tokens = await manualOAuthFlow();
111 |     
112 |     if (tokens.refresh_token) {
113 |       console.log('\n🎉 OAuth setup successful!');
114 |       console.log('🔑 Access token obtained:', tokens.access_token ? '✅' : '❌');
115 |       console.log('🔄 Refresh token obtained:', tokens.refresh_token ? '✅' : '❌');
116 |       
117 |       // Save tokens securely using TokenManager
118 |       try {
119 |         tokenManager.saveTokens(tokens);
120 |         console.log('💾 Tokens saved securely');
121 |         
122 |         const tokenInfo = tokenManager.getTokenInfo();
123 |         console.log(`📁 Token location: ${tokenInfo.location}`);
124 |         
125 |       } catch (error) {
126 |         console.error('❌ Failed to save tokens:', error.message);
127 |         console.log('\n📝 Please run the setup again or contact support.');
128 |         process.exit(1);
129 |       }
130 |       
131 |       console.log('\n📋 Setup Summary:');
132 |       console.log('   ✅ OAuth flow completed');
133 |       console.log('   ✅ Access token obtained');
134 |       console.log('   ✅ Refresh token obtained');
135 |       console.log('   ✅ Tokens stored securely');
136 |       console.log('\n🔐 Security Notes:');
137 |       console.log('   🔒 Refresh token is stored with restricted file permissions');
138 |       console.log('   ⏰ Access token will be refreshed automatically');
139 |       console.log('   🚫 No sensitive tokens are stored in .env file');
140 |       console.log('\n🎯 Next Steps:');
141 |       console.log('   1. Test the OAuth setup: npm run test-oauth');
142 |       console.log('   2. Configure your MCP client (Claude Desktop, VS Code, etc.)');
143 |       console.log('   3. Use your MCP tools with confidence!');
144 |       
145 |     } else {
146 |       console.error('\n❌ OAuth setup failed: No refresh token received');
147 |       console.log('🔧 This might happen if:');
148 |       console.log('   - Your OAuth app is not configured correctly');
149 |       console.log('   - You denied the authorization request');
150 |       console.log('   - There was a network error during the process');
151 |       console.log('\n📖 Please check the OAUTH_SETUP.md guide and try again.');
152 |       process.exit(1);
153 |     }
154 |     
155 |   } catch (error) {
156 |     console.error('\n❌ OAuth setup failed:', error.message);
157 |     
158 |     if (error.message.includes('EADDRINUSE')) {
159 |       console.log('\n🔧 Port already in use. Please:');
160 |       console.log('   1. Close any other applications using port 3001');
161 |       console.log('   2. Wait a moment and try again');
162 |     } else if (error.message.includes('CLIENT_ID') || error.message.includes('CLIENT_SECRET')) {
163 |       console.log('\n🔧 OAuth credential issue. Please:');
164 |       console.log('   1. Check your .env file has correct credentials');
165 |       console.log('   2. Verify credentials in Google Cloud Console');
166 |       console.log('   3. Make sure OAuth consent screen is configured');
167 |     } else {
168 |       console.log('\n🔧 Please check:');
169 |       console.log('   1. Your internet connection');
170 |       console.log('   2. Google Cloud Console OAuth configuration');
171 |       console.log('   3. That you authorized the application in the browser');
172 |     }
173 |     
174 |     console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
175 |     process.exit(1);
176 |   }
177 | }
178 | 
179 | // Handle command line arguments
180 | if (process.argv.includes('--help') || process.argv.includes('-h')) {
181 |   console.log('📖 Google Apps Script OAuth Setup');
182 |   console.log('\nUsage:');
183 |   console.log('  node oauth-setup.js           # Run OAuth setup');
184 |   console.log('  node oauth-setup.js --force   # Force new OAuth setup (overwrite existing tokens)');
185 |   console.log('  node oauth-setup.js --clear   # Clear stored tokens');
186 |   console.log('  node oauth-setup.js --info    # Show token information');
187 |   console.log('  node oauth-setup.js --help    # Show this help');
188 |   process.exit(0);
189 | }
190 | 
191 | if (process.argv.includes('--info')) {
192 |   const tokenManager = new TokenManager();
193 |   const tokenInfo = tokenManager.getTokenInfo();
194 |   
195 |   console.log('🔍 Token Information:');
196 |   console.log('=====================\n');
197 |   
198 |   if (tokenInfo.hasTokens) {
199 |     console.log('✅ Tokens found');
200 |     console.log(`📁 Location: ${tokenInfo.location}`);
201 |     console.log(`💾 Saved at: ${tokenInfo.savedAt}`);
202 |     console.log(`⏰ Expires at: ${tokenInfo.expiresAt}`);
203 |     console.log(`📊 Status: ${tokenInfo.status}`);
204 |     console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
205 |   } else {
206 |     console.log('❌ No tokens found');
207 |     console.log(`📁 Expected location: ${tokenInfo.location}`);
208 |     console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
209 |   }
210 |   
211 |   process.exit(0);
212 | }
213 | 
214 | // Run setup
215 | setupOAuth().catch((error) => {
216 |   console.error('💥 Unexpected error:', error);
217 |   process.exit(1);
218 | });
219 |       
220 |     } else {
221 |       console.log('\n⚠️ OAuth completed but no refresh token received.');
222 |       console.log('🔄 You may need to revoke and re-authorize the application.');
223 |       console.log('📖 Check the Google Cloud Console for your OAuth settings.');
224 |     }
225 |     
226 |   } catch (error) {
227 |     console.error('\n❌ OAuth setup failed:', error.message);
228 |     console.log('\n🔧 Troubleshooting:');
229 |     console.log('   1. Check your internet connection');
230 |     console.log('   2. Verify your CLIENT_ID and CLIENT_SECRET are correct');
231 |     console.log('   3. Ensure the redirect URI is registered in Google Cloud Console');
232 |     console.log('   4. Make sure Google Apps Script API is enabled');
233 |     console.log('   5. Try revoking and re-creating your OAuth credentials');
234 |     console.log('\n📖 For detailed setup instructions, see OAUTH_SETUP.md');
235 |     process.exit(1);
236 |   }
237 | }
238 | 
239 | // Run setup if this script is executed directly
240 | console.log('🔍 Debug: process.argv[1]:', process.argv[1]);
241 | console.log('🔍 Debug: endsWith check:', process.argv[1] && process.argv[1].endsWith('oauth-setup.js'));
242 | 
243 | if (process.argv[1] && process.argv[1].endsWith('oauth-setup.js')) {
244 |   console.log('🚀 Starting OAuth setup...');
245 |   setupOAuth();
246 | } else {
247 |   console.log('❌ Script not executed directly, skipping setup');
248 | }
249 | 
```

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

```markdown
  1 | # Google Apps Script API OAuth Setup Guide
  2 | 
  3 | This guide will help you set up OAuth authentication for the Google Apps Script API MCP server.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | 1. **Google Cloud Project**: You need a Google Cloud Project with Google Apps Script API enabled
  8 | 2. **OAuth 2.0 Credentials**: You need Client ID and Client Secret from Google Cloud Console
  9 | 3. **Node.js**: Make sure you have Node.js 16+ installed
 10 | 
 11 | ## Step 1: Google Cloud Console Setup
 12 | 
 13 | ### 1.1 Create/Select a Project
 14 | 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
 15 | 2. Create a new project or select an existing one
 16 | 3. Note your project ID
 17 | 
 18 | ### 1.2 Enable Google Apps Script API
 19 | 1. In the Google Cloud Console, go to **APIs & Services** > **Library**
 20 | 2. Search for "Google Apps Script API"
 21 | 3. Click on it and press **Enable**
 22 | 
 23 | ### 1.3 Create OAuth 2.0 Credentials
 24 | 1. Go to **APIs & Services** > **Credentials**
 25 | 2. Click **+ CREATE CREDENTIALS** > **OAuth 2.0 Client IDs**
 26 | 3. If prompted, configure the OAuth consent screen:
 27 |    - Choose **External** (unless you're in a Google Workspace organization)
 28 |    - Fill in the required fields:
 29 |      - App name: "Google Apps Script MCP Server"
 30 |      - User support email: Your email
 31 |      - Developer contact information: Your email
 32 |    - Add scopes (optional for testing):
 33 |      - `https://www.googleapis.com/auth/script.projects`
 34 |      - `https://www.googleapis.com/auth/script.projects.readonly`
 35 |      - `https://www.googleapis.com/auth/script.deployments.readonly`
 36 |      - `https://www.googleapis.com/auth/script.metrics`
 37 | 4. For Application Type, choose **Web application**
 38 | 5. Add authorized redirect URIs:
 39 |    - `http://localhost:3001/oauth/callback`
 40 | 6. Click **Create**
 41 | 7. Copy your **Client ID** and **Client Secret**
 42 | 
 43 | ## Step 2: Configure Environment Variables
 44 | 
 45 | ### 2.1 Update .env File
 46 | Edit the `.env` file in your project root and add your credentials:
 47 | 
 48 | ```env
 49 | # Google Apps Script API OAuth Configuration
 50 | GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here
 51 | GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here
 52 | GOOGLE_APP_SCRIPT_API_REFRESH_TOKEN=your_refresh_token_here
 53 | 
 54 | # OAuth Configuration
 55 | GOOGLE_APP_SCRIPT_API_REDIRECT_URI=http://localhost:3001/oauth/callback
 56 | ```
 57 | 
 58 | Replace:
 59 | - `your_client_id_here` with your actual Client ID
 60 | - `your_client_secret_here` with your actual Client Secret
 61 | - Keep `your_refresh_token_here` as is (we'll get this in the next step)
 62 | 
 63 | ## Step 3: Run OAuth Setup
 64 | 
 65 | ### 3.1 Install Dependencies
 66 | ```bash
 67 | npm install
 68 | ```
 69 | 
 70 | ### 3.2 Run OAuth Setup Script
 71 | ```bash
 72 | npm run setup-oauth
 73 | ```
 74 | 
 75 | This script will:
 76 | 1. Validate your credentials
 77 | 2. Open your browser for OAuth authorization
 78 | 3. Handle the OAuth callback
 79 | 4. Automatically update your `.env` file with the refresh token
 80 | 
 81 | ### 3.3 Authorize the Application
 82 | 1. Your browser will open automatically
 83 | 2. Sign in to your Google account
 84 | 3. Review and accept the permissions
 85 | 4. The browser will show a success message
 86 | 5. Return to your terminal - the setup should be complete
 87 | 
 88 | ## Step 4: Test Your Setup
 89 | 
 90 | ### 4.1 Run OAuth Test
 91 | ```bash
 92 | npm run test-oauth
 93 | ```
 94 | 
 95 | This will verify that your OAuth setup is working correctly.
 96 | 
 97 | ### 4.2 Test with MCP Client
 98 | You can now use your MCP tools with any MCP-compatible client like:
 99 | - Claude Desktop
100 | - Postman
101 | - Other MCP clients
102 | 
103 | ## Troubleshooting
104 | 
105 | ### Common Issues
106 | 
107 | #### 1. "redirect_uri_mismatch" Error
108 | - Make sure you added `http://localhost:3001/oauth/callback` to your authorized redirect URIs in Google Cloud Console
109 | - Check that the URI is exactly correct (no trailing slash, correct port)
110 | 
111 | #### 2. "access_denied" Error
112 | - Make sure you're signed in to the correct Google account
113 | - Check that the OAuth consent screen is properly configured
114 | - Verify that the Google Apps Script API is enabled
115 | 
116 | #### 3. "invalid_client" Error
117 | - Double-check your Client ID and Client Secret in the `.env` file
118 | - Make sure there are no extra spaces or quotes around the values
119 | 
120 | #### 4. Port Already in Use
121 | - Make sure port 3001 is not being used by another application
122 | - If needed, you can modify the port in `lib/oauth-helper.js` and update your redirect URI accordingly
123 | 
124 | #### 5. Refresh Token Not Saved
125 | - Make sure the OAuth setup script has write permissions to the `.env` file
126 | - Check that the `.env` file exists and is in the correct location
127 | 
128 | ### Advanced Configuration
129 | 
130 | #### Custom Port
131 | If you need to use a different port, update these files:
132 | 1. `lib/oauth-helper.js` - Change the `PORT` constant
133 | 2. `.env` - Update `GOOGLE_APP_SCRIPT_API_REDIRECT_URI`
134 | 3. Google Cloud Console - Update the authorized redirect URI
135 | 
136 | #### Custom Scopes
137 | To modify the required permissions, edit the `SCOPES` array in `lib/oauth-helper.js`.
138 | 
139 | Available scopes:
140 | - `https://www.googleapis.com/auth/script.projects` - Read/write access to Apps Script projects
141 | - `https://www.googleapis.com/auth/script.projects.readonly` - Read-only access to Apps Script projects
142 | - `https://www.googleapis.com/auth/script.deployments.readonly` - Read access to Apps Script deployments
143 | - `https://www.googleapis.com/auth/script.metrics` - Access to Apps Script execution metrics
144 | 
145 | ## Security Notes
146 | 
147 | 1. **Keep your credentials secure**: Never commit your `.env` file to version control
148 | 2. **Refresh token**: The refresh token allows long-term access - treat it like a password
149 | 3. **Client secret**: Keep your client secret confidential
150 | 4. **Production use**: For production, consider using more secure token storage than environment variables
151 | 
152 | ## Support
153 | 
154 | If you encounter issues:
155 | 1. Check the troubleshooting section above
156 | 2. Review the Google Apps Script API documentation
157 | 3. Check Google Cloud Console for any error messages
158 | 4. Ensure all prerequisites are met
159 | 
160 | For more information about Google Apps Script API:
161 | - [Google Apps Script API Documentation](https://developers.google.com/apps-script/api)
162 | - [OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server)
163 | 2. Google Apps Script API enabled
164 | 3. OAuth 2.0 credentials configured
165 | 
166 | ## Step 1: Create OAuth 2.0 Credentials
167 | 
168 | 1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
169 | 2. Select your project (or create a new one)
170 | 3. Navigate to **APIs & Services** > **Credentials**
171 | 4. Click **+ CREATE CREDENTIALS** > **OAuth client ID**
172 | 5. Select **Desktop application** as the application type
173 | 6. Give it a name (e.g., "Google Apps Script MCP Client")
174 | 7. Click **Create**
175 | 8. Download the JSON file containing your credentials
176 | 
177 | ## Step 2: Enable Required APIs
178 | 
179 | Make sure the following APIs are enabled in your GCP project:
180 | 
181 | 1. Google Apps Script API
182 | 2. Google Drive API (if accessing script files)
183 | 
184 | To enable APIs:
185 | 1. Go to **APIs & Services** > **Library**
186 | 2. Search for each API and click **Enable**
187 | 
188 | ## Step 3: Get a Refresh Token
189 | 
190 | You'll need to obtain a refresh token through the OAuth 2.0 flow. Here's a simple way to do it:
191 | 
192 | ### Option A: Using Google OAuth 2.0 Playground
193 | 
194 | 1. Go to [Google OAuth 2.0 Playground](https://developers.google.com/oauthplayground/)
195 | 2. Click the gear icon (⚙️) in the top right
196 | 3. Check **Use your own OAuth credentials**
197 | 4. Enter your **OAuth Client ID** and **OAuth Client secret**
198 | 5. In the left panel, find and select **Google Apps Script API v1**
199 | 6. Select the scope: `https://www.googleapis.com/auth/script.projects`
200 | 7. Click **Authorize APIs**
201 | 8. Complete the authorization flow
202 | 9. Click **Exchange authorization code for tokens**
203 | 10. Copy the **Refresh token** from the response
204 | 
205 | ### Option B: Using curl (Advanced)
206 | 
207 | ```bash
208 | # Step 1: Get authorization code (open this URL in browser)
209 | 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
210 | 
211 | # Step 2: Exchange code for tokens
212 | curl -X POST https://oauth2.googleapis.com/token \
213 |   -H "Content-Type: application/x-www-form-urlencoded" \
214 |   -d "client_id=YOUR_CLIENT_ID" \
215 |   -d "client_secret=YOUR_CLIENT_SECRET" \
216 |   -d "code=AUTHORIZATION_CODE_FROM_STEP_1" \
217 |   -d "grant_type=authorization_code" \
218 |   -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob"
219 | ```
220 | 
221 | ## Step 4: Configure Environment Variables
222 | 
223 | Update your `.env` file with the OAuth credentials:
224 | 
225 | ```env
226 | # Google Apps Script API OAuth Configuration
227 | GOOGLE_APP_SCRIPT_API_CLIENT_ID=your_client_id_here
228 | GOOGLE_APP_SCRIPT_API_CLIENT_SECRET=your_client_secret_here
229 | GOOGLE_APP_SCRIPT_API_REFRESH_TOKEN=your_refresh_token_here
230 | ```
231 | 
232 | Replace the placeholder values with:
233 | - `your_client_id_here`: Your OAuth 2.0 Client ID
234 | - `your_client_secret_here`: Your OAuth 2.0 Client Secret
235 | - `your_refresh_token_here`: The refresh token obtained in Step 3
236 | 
237 | ## Required Scopes
238 | 
239 | The following OAuth scopes are required for different operations:
240 | 
241 | - `https://www.googleapis.com/auth/script.projects` - Manage Google Apps Script projects
242 | - `https://www.googleapis.com/auth/script.processes` - View Google Apps Script processes
243 | - `https://www.googleapis.com/auth/script.deployments` - Manage deployments
244 | - `https://www.googleapis.com/auth/drive` - Access Drive files (if needed)
245 | 
246 | ## Security Notes
247 | 
248 | 1. **Keep credentials secure**: Never commit your `.env` file to version control
249 | 2. **Refresh token rotation**: Google may rotate refresh tokens periodically
250 | 3. **Access token expiry**: Access tokens typically expire after 1 hour
251 | 4. **Scope principle**: Only request the minimum scopes needed for your application
252 | 
253 | ## Troubleshooting
254 | 
255 | ### Common Issues
256 | 
257 | 1. **"Invalid credentials"**: Check that your client ID and secret are correct
258 | 2. **"Invalid scope"**: Ensure the required APIs are enabled in your GCP project
259 | 3. **"Refresh token expired"**: You may need to re-authorize and get a new refresh token
260 | 4. **"Project not found"**: Make sure the script project exists and you have access to it
261 | 
262 | ### Testing Authentication
263 | 
264 | You can test your OAuth setup by running a simple API call:
265 | 
266 | ```javascript
267 | import { getOAuthAccessToken } from './lib/oauth-helper.js';
268 | 
269 | async function testAuth() {
270 |   try {
271 |     const token = await getOAuthAccessToken();
272 |     console.log('OAuth authentication successful!');
273 |     console.log('Access token received:', token.substring(0, 20) + '...');
274 |   } catch (error) {
275 |     console.error('OAuth authentication failed:', error.message);
276 |   }
277 | }
278 | 
279 | testAuth();
280 | ```
281 | 
282 | ## Migration from API Key
283 | 
284 | If you were previously using an API key, the OAuth implementation provides:
285 | 
286 | 1. **Better security**: OAuth tokens are time-limited and can be revoked
287 | 2. **Fine-grained access**: Specific scopes control what the application can access
288 | 3. **User context**: Operations are performed in the context of the authenticated user
289 | 4. **Compliance**: Meets Google's authentication requirements for sensitive APIs
290 | 
291 | The OAuth helper automatically handles token refresh, so your application will continue working even after access tokens expire.
292 | 
```

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

```javascript
  1 | import { getOAuthAccessToken } from '../lib/oauth-helper.js';
  2 | 
  3 | const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';
  4 | 
  5 | async function updateScriptWithWebAppConfig() {
  6 |   try {
  7 |     const token = await getOAuthAccessToken();
  8 |     
  9 |     console.log('Updating script with web app configuration...');
 10 |     
 11 |     // Updated manifest with web app configuration
 12 |     const updatedManifest = {
 13 |       "timeZone": "America/New_York",
 14 |       "dependencies": {},
 15 |       "exceptionLogging": "STACKDRIVER", 
 16 |       "runtimeVersion": "V8",
 17 |       "webapp": {
 18 |         "access": "ANYONE",
 19 |         "executeAs": "USER_ACCESSING"
 20 |       }
 21 |     };
 22 | 
 23 |     // Prepare the updated script content
 24 |     const scriptContent = {
 25 |       files: [
 26 |         {
 27 |           name: "appsscript",
 28 |           type: "JSON",
 29 |           source: JSON.stringify(updatedManifest, null, 2)
 30 |         },
 31 |         {
 32 |           name: "code",
 33 |           type: "SERVER_JS",
 34 |           source: `/**
 35 |  * Serves the HTML page when the web app is accessed
 36 |  */
 37 | function doGet() {
 38 |   return HtmlService.createHtmlOutputFromFile('index')
 39 |     .setTitle('Hello World App')
 40 |     .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
 41 | }
 42 | 
 43 | /**
 44 |  * Server-side function that can be called from the client
 45 |  */
 46 | function getGreeting(name) {
 47 |   if (!name) {
 48 |     name = 'World';
 49 |   }
 50 |   return \`Hello, \${name}! This is a Google Apps Script web app.\`;
 51 | }
 52 | 
 53 | /**
 54 |  * Function to get current timestamp
 55 |  */
 56 | function getCurrentTime() {
 57 |   return new Date().toLocaleString();
 58 | }`
 59 |         },
 60 |         {
 61 |           name: "index",
 62 |           type: "HTML",
 63 |           source: `<!DOCTYPE html>
 64 | <html>
 65 | <head>
 66 |   <meta charset="utf-8">
 67 |   <meta name="viewport" content="width=device-width, initial-scale=1">
 68 |   <title>Hello World - Google Apps Script</title>
 69 |   <style>
 70 |     body {
 71 |       font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 72 |       max-width: 800px;
 73 |       margin: 0 auto;
 74 |       padding: 20px;
 75 |       background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 76 |       min-height: 100vh;
 77 |       color: white;
 78 |     }
 79 |     
 80 |     .container {
 81 |       background: rgba(255, 255, 255, 0.1);
 82 |       backdrop-filter: blur(10px);
 83 |       border-radius: 15px;
 84 |       padding: 30px;
 85 |       box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
 86 |       text-align: center;
 87 |     }
 88 |     
 89 |     h1 {
 90 |       font-size: 2.5em;
 91 |       margin-bottom: 20px;
 92 |       text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
 93 |     }
 94 |     
 95 |     .input-group {
 96 |       margin: 20px 0;
 97 |     }
 98 |     
 99 |     input[type="text"] {
100 |       padding: 12px 20px;
101 |       font-size: 16px;
102 |       border: none;
103 |       border-radius: 25px;
104 |       width: 250px;
105 |       text-align: center;
106 |       background: rgba(255, 255, 255, 0.9);
107 |       color: #333;
108 |     }
109 |     
110 |     button {
111 |       background: #ff6b6b;
112 |       color: white;
113 |       border: none;
114 |       padding: 12px 25px;
115 |       font-size: 16px;
116 |       border-radius: 25px;
117 |       cursor: pointer;
118 |       margin: 10px;
119 |       transition: all 0.3s ease;
120 |     }
121 |     
122 |     button:hover {
123 |       background: #ff5252;
124 |       transform: translateY(-2px);
125 |       box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
126 |     }
127 |     
128 |     .result {
129 |       margin: 20px 0;
130 |       padding: 20px;
131 |       background: rgba(255, 255, 255, 0.1);
132 |       border-radius: 10px;
133 |       font-size: 18px;
134 |       min-height: 50px;
135 |       display: flex;
136 |       align-items: center;
137 |       justify-content: center;
138 |     }
139 |     
140 |     .loading {
141 |       display: none;
142 |       color: #ffd700;
143 |     }
144 |     
145 |     .timestamp {
146 |       font-size: 14px;
147 |       color: rgba(255, 255, 255, 0.8);
148 |       margin-top: 20px;
149 |     }
150 |   </style>
151 | </head>
152 | <body>
153 |   <div class="container">
154 |     <h1>🌟 Hello World App 🌟</h1>
155 |     <p>Welcome to your Google Apps Script web application!</p>
156 |     
157 |     <div class="input-group">
158 |       <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
159 |     </div>
160 |     
161 |     <div>
162 |       <button onclick="sayHello()">Say Hello</button>
163 |       <button onclick="getTime()">Get Current Time</button>
164 |     </div>
165 |     
166 |     <div id="result" class="result">
167 |       Click a button to see the magic! ✨
168 |     </div>
169 |     
170 |     <div id="loading" class="loading">Loading...</div>
171 |     
172 |     <div id="timestamp" class="timestamp"></div>
173 |   </div>
174 | 
175 |   <script>
176 |     function sayHello() {
177 |       showLoading();
178 |       const name = document.getElementById('nameInput').value;
179 |       
180 |       google.script.run
181 |         .withSuccessHandler(onSuccess)
182 |         .withFailureHandler(onFailure)
183 |         .getGreeting(name);
184 |     }
185 |     
186 |     function getTime() {
187 |       showLoading();
188 |       
189 |       google.script.run
190 |         .withSuccessHandler(onTimeSuccess)
191 |         .withFailureHandler(onFailure)
192 |         .getCurrentTime();
193 |     }
194 |     
195 |     function onSuccess(result) {
196 |       hideLoading();
197 |       document.getElementById('result').innerHTML = \`
198 |         <div style="font-size: 20px; font-weight: bold;">
199 |           \${result}
200 |         </div>
201 |       \`;
202 |     }
203 |     
204 |     function onTimeSuccess(result) {
205 |       hideLoading();
206 |       document.getElementById('result').innerHTML = \`
207 |         <div style="font-size: 18px;">
208 |           🕐 Current server time: <br>
209 |           <strong>\${result}</strong>
210 |         </div>
211 |       \`;
212 |     }
213 |     
214 |     function onFailure(error) {
215 |       hideLoading();
216 |       document.getElementById('result').innerHTML = \`
217 |         <div style="color: #ff6b6b;">
218 |           ❌ Error: \${error.message}
219 |         </div>
220 |       \`;
221 |     }
222 |     
223 |     function showLoading() {
224 |       document.getElementById('loading').style.display = 'block';
225 |       document.getElementById('result').style.display = 'none';
226 |     }
227 |     
228 |     function hideLoading() {
229 |       document.getElementById('loading').style.display = 'none';
230 |       document.getElementById('result').style.display = 'flex';
231 |     }
232 |     
233 |     // Initialize the page
234 |     window.onload = function() {
235 |       document.getElementById('timestamp').innerHTML = 
236 |         \`Page loaded at: \${new Date().toLocaleString()}\`;
237 |     };
238 |   </script>
239 | </body>
240 | </html>`
241 |         }
242 |       ]
243 |     };
244 | 
245 |     console.log('Updated manifest:', JSON.stringify(updatedManifest, null, 2));
246 | 
247 |     // Update the script content
248 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/content`, {
249 |       method: 'PUT',
250 |       headers: {
251 |         'Authorization': `Bearer ${token}`,
252 |         'Content-Type': 'application/json',
253 |         'Accept': 'application/json'
254 |       },
255 |       body: JSON.stringify(scriptContent)
256 |     });
257 | 
258 |     if (!response.ok) {
259 |       const errorText = await response.text();
260 |       console.error('Error response:', errorText);
261 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
262 |     }
263 | 
264 |     const data = await response.json();
265 |     console.log('✅ Script updated successfully');
266 |     
267 |     return data;
268 |   } catch (error) {
269 |     console.error('Error updating script:', error);
270 |     return null;
271 |   }
272 | }
273 | 
274 | async function createNewVersion() {
275 |   try {
276 |     const token = await getOAuthAccessToken();
277 |     
278 |     console.log('Creating new version...');
279 |     
280 |     const versionData = {
281 |       description: "Version with web app configuration"
282 |     };
283 | 
284 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/versions`, {
285 |       method: 'POST',
286 |       headers: {
287 |         'Authorization': `Bearer ${token}`,
288 |         'Content-Type': 'application/json',
289 |         'Accept': 'application/json'
290 |       },
291 |       body: JSON.stringify(versionData)
292 |     });
293 | 
294 |     if (!response.ok) {
295 |       const errorText = await response.text();
296 |       console.error('Error response:', errorText);
297 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
298 |     }
299 | 
300 |     const data = await response.json();
301 |     console.log('✅ New version created:', data.versionNumber);
302 |     
303 |     return data;
304 |   } catch (error) {
305 |     console.error('Error creating version:', error);
306 |     return null;
307 |   }
308 | }
309 | 
310 | async function createWebAppDeployment(versionNumber) {
311 |   try {
312 |     const token = await getOAuthAccessToken();
313 |     
314 |     console.log('Creating web app deployment...');
315 |     
316 |     const deploymentConfig = {
317 |       description: "Hello World Web App - Public Access",
318 |       manifestFileName: "appsscript",
319 |       versionNumber: versionNumber
320 |     };
321 | 
322 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments`, {
323 |       method: 'POST',
324 |       headers: {
325 |         'Authorization': `Bearer ${token}`,
326 |         'Content-Type': 'application/json',
327 |         'Accept': 'application/json'
328 |       },
329 |       body: JSON.stringify(deploymentConfig)
330 |     });
331 | 
332 |     if (!response.ok) {
333 |       const errorText = await response.text();
334 |       console.error('Error response:', errorText);
335 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
336 |     }
337 | 
338 |     const data = await response.json();
339 |     console.log('✅ Deployment created:', JSON.stringify(data, null, 2));
340 |     
341 |     return data;
342 |   } catch (error) {
343 |     console.error('Error creating deployment:', error);
344 |     return null;
345 |   }
346 | }
347 | 
348 | async function getDeploymentDetails(deploymentId) {
349 |   try {
350 |     const token = await getOAuthAccessToken();
351 |       console.log(`Getting deployment details for: ${deploymentId}`);
352 |     
353 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments/${deploymentId}`, {
354 |       method: 'GET',
355 |       headers: {
356 |         'Authorization': `Bearer ${token}`,
357 |         'Accept': 'application/json'
358 |       }
359 |     });
360 | 
361 |     if (!response.ok) {
362 |       const errorText = await response.text();
363 |       console.error('Error response:', errorText);      throw new Error(`HTTP ${response.status}: ${errorText}`);
364 |     }
365 | 
366 |     const data = await response.json();
367 |     console.log('Deployment details:', JSON.stringify(data, null, 2));
368 |     
369 |     if (data.entryPoints && data.entryPoints[0] && data.entryPoints[0].webApp) {
370 |       console.log('\n🎉 SUCCESS! Your web app is deployed!');
371 |       console.log('Web App URL:', data.entryPoints[0].webApp.url);
372 |       console.log('Access Level:', data.entryPoints[0].webApp.access);
373 |       console.log('Execute As:', data.entryPoints[0].webApp.executeAs);
374 |       console.log('\n📱 You can now access your Hello World app at the URL above!');
375 |     } else {
376 |       console.log('\n⚠️  No web app entry point found.');
377 |     }
378 |     
379 |     return data;
380 |   } catch (error) {
381 |     console.error('Error getting deployment details:', error);
382 |     return null;
383 |   }
384 | }
385 | 
386 | async function main() {
387 |   console.log('=== Creating Google Apps Script Web App ===\n');
388 |   
389 |   // Step 1: Update script with web app configuration
390 |   console.log('Step 1: Updating script with web app configuration...');
391 |   const updateResult = await updateScriptWithWebAppConfig();
392 |   if (!updateResult) return;
393 |   
394 |   console.log('\n' + '='.repeat(50) + '\n');
395 |   
396 |   // Step 2: Create a new version
397 |   console.log('Step 2: Creating a new version...');
398 |   const versionResult = await createNewVersion();
399 |   if (!versionResult) return;
400 |   
401 |   console.log('\n' + '='.repeat(50) + '\n');
402 |   
403 |   // Step 3: Create deployment with the new version
404 |   console.log('Step 3: Creating deployment...');
405 |   const deploymentResult = await createWebAppDeployment(versionResult.versionNumber);
406 |   if (!deploymentResult) return;
407 |   
408 |   console.log('\n' + '='.repeat(50) + '\n');
409 |   
410 |   // Step 4: Get deployment details to see the web app URL
411 |   console.log('Step 4: Getting deployment details...');
412 |   await getDeploymentDetails(deploymentResult.deploymentId);
413 | }
414 | 
415 | main().catch(console.error);
416 | 
```
Page 2/3FirstPrevNextLast