├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Environment variables 5 | .env 6 | 7 | # Build outputs 8 | dist/ 9 | build/ 10 | 11 | # Logs 12 | *.log 13 | 14 | # IDE specific files 15 | .vscode/ 16 | .idea/ 17 | 18 | # OS specific files 19 | .DS_Store 20 | Thumbs.db -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Google Calendar MCP Server 2 | 3 | Thank you for your interest in contributing to the Google Calendar MCP Server! This document provides guidelines and instructions for contributing to this project. 4 | 5 | ## Code of Conduct 6 | 7 | By participating in this project, you agree to abide by our Code of Conduct: 8 | 9 | - Be respectful and inclusive 10 | - Use welcoming and inclusive language 11 | - Be collaborative 12 | - Accept constructive criticism 13 | 14 | 15 | ## How to Contribute 16 | 17 | ### Reporting Bugs 18 | 19 | 1. Check the issue tracker to avoid duplicate reports 20 | 2. If no existing issue covers your bug, create a new issue 21 | 3. Include in your report: 22 | - Clear description of the bug 23 | - Steps to reproduce 24 | - Expected behavior 25 | - Actual behavior 26 | - Environment details (OS, Node.js version, etc.) 27 | - Relevant logs 28 | - Screenshots if applicable 29 | 30 | ### Suggesting Enhancements 31 | 32 | 1. Check existing issues and pull requests 33 | 2. Create a new issue with: 34 | - Clear description of the enhancement 35 | - Use cases 36 | - Benefits 37 | - Potential implementation approach 38 | - Any potential impacts on existing functionality 39 | 40 | ### Pull Requests 41 | 42 | 1. Fork the repository 43 | 2. Create a new branch: 44 | ```bash 45 | git checkout -b feature/your-feature-name 46 | # or 47 | git checkout -b fix/your-fix-name 48 | ``` 49 | 50 | 3. Make your changes following our coding standards 51 | 4. Test your changes thoroughly 52 | 5. Commit your changes with clear messages: 53 | ```bash 54 | git commit -m "feat: add new calendar sync feature" 55 | # or 56 | git commit -m "fix: resolve authentication timeout issue" 57 | ``` 58 | 59 | 6. Push to your fork: 60 | ```bash 61 | git push origin feature/your-feature-name 62 | ``` 63 | 64 | 7. Create a Pull Request with: 65 | - Clear description of changes 66 | - Link to related issues 67 | - Screenshots/videos for UI changes 68 | - Updated documentation if needed 69 | 70 | ## Development Setup 71 | 72 | 1. Clone the repository: 73 | ```bash 74 | git clone https://github.com/your-username/google-calendar-mcp.git 75 | cd google-calendar-mcp 76 | ``` 77 | 78 | 2. Install dependencies: 79 | ```bash 80 | npm install 81 | ``` 82 | 83 | 3. Create a test Google Cloud project for development 84 | 4. Set up your environment variables in `.env` 85 | 5. Build the project: 86 | ```bash 87 | npm run build 88 | ``` 89 | 90 | ## Coding Standards 91 | 92 | ### TypeScript Guidelines 93 | 94 | - Use TypeScript strict mode 95 | - Provide clear type definitions 96 | - Avoid `any` types when possible 97 | - Use interfaces for object shapes 98 | - Document complex types 99 | 100 | ### Style Guide 101 | 102 | - Use 2 spaces for indentation 103 | - Use semicolons 104 | - Use single quotes for strings 105 | - Use template literals for string interpolation 106 | - Add trailing commas in multi-line objects/arrays 107 | - Keep lines under 100 characters 108 | 109 | Example: 110 | ```typescript 111 | interface CalendarEvent { 112 | id: string; 113 | summary: string; 114 | startTime: string; 115 | endTime: string; 116 | attendees?: string[]; 117 | } 118 | 119 | function formatEvent(event: CalendarEvent): string { 120 | return `Event: ${event.summary} 121 | Start: ${event.startTime} 122 | End: ${event.endTime}`; 123 | } 124 | ``` 125 | 126 | ### Documentation 127 | 128 | - Add JSDoc comments for functions and classes 129 | - Keep comments current with code changes 130 | - Document non-obvious code behavior 131 | - Include examples for complex functionality 132 | 133 | Example: 134 | ```typescript 135 | /** 136 | * Creates a new calendar event with specified parameters. 137 | * 138 | * @param {string} summary - Event title 139 | * @param {string} startTime - ISO string for event start time 140 | * @param {string} endTime - ISO string for event end time 141 | * @param {string[]} [attendees] - Optional list of attendee emails 142 | * @returns {Promise} Created event object 143 | * @throws {Error} If authentication fails or invalid parameters 144 | */ 145 | async function createEvent( 146 | summary: string, 147 | startTime: string, 148 | endTime: string, 149 | attendees?: string[] 150 | ): Promise { 151 | // Implementation 152 | } 153 | ``` 154 | 155 | ### Testing 156 | 157 | - Write unit tests for new functionality 158 | - Include integration tests for API interactions 159 | - Test error conditions 160 | - Maintain test coverage 161 | - Use meaningful test descriptions 162 | 163 | Example: 164 | ```typescript 165 | describe('Calendar Event Creation', () => { 166 | it('should create an event with valid parameters', async () => { 167 | // Test implementation 168 | }); 169 | 170 | it('should handle invalid date formats', async () => { 171 | // Test implementation 172 | }); 173 | 174 | it('should retry on API timeout', async () => { 175 | // Test implementation 176 | }); 177 | }); 178 | ``` 179 | 180 | ## Review Process 181 | 182 | 1. Automated checks must pass: 183 | - TypeScript compilation 184 | - Linting 185 | - Tests 186 | - Code coverage 187 | 188 | 2. Code Review Requirements: 189 | - One approved review required 190 | - All review comments addressed 191 | - Documentation updated 192 | - Tests included 193 | - No merge conflicts 194 | 195 | 3. Merge Process: 196 | - Squash and merge preferred 197 | - Clear commit message 198 | - Delete branch after merge 199 | 200 | ## Release Process 201 | 202 | 1. Version numbers follow [Semantic Versioning](https://semver.org/) 203 | 2. Update CHANGELOG.md with changes 204 | 3. Create a new release in GitHub 205 | 4. Update documentation if needed 206 | 5. Notify users of breaking changes 207 | 208 | ## Getting Help 209 | 210 | - Check existing documentation 211 | - Ask in GitHub Discussions 212 | - Join our community chat 213 | - Contact maintainers 214 | - Attend community meetings 215 | 216 | ## License 217 | 218 | By contributing, you agree that your contributions will be licensed under the same license as the project (see LICENSE file). 219 | 220 | ## Questions? 221 | 222 | Feel free to open an issue or contact the maintainers if you have any questions about contributing. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 v-3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Calendar MCP Server 2 | 3 | This MCP server allows Claude to interact with your Google Calendar, enabling capabilities like listing events, creating meetings, and finding free time slots. 4 | 5 | ## Prerequisites 6 | 7 | - Node.js (v16 or higher) 8 | - Claude Desktop App 9 | - A Google Cloud Project 10 | - Google Calendar API enabled 11 | - OAuth 2.0 credentials 12 | 13 | ## Setup Instructions 14 | 15 | ### 1. Create a Google Cloud Project 16 | 17 | 1. Go to the [Google Cloud Console](https://console.cloud.google.com/) 18 | 2. Create a new project or select an existing one 19 | 3. Enable the Google Calendar API: 20 | - Go to "APIs & Services" > "Library" 21 | - Search for "Google Calendar API" 22 | - Click "Enable" 23 | 24 | ### 2. Configure OAuth Consent Screen 25 | 26 | 1. Go to "APIs & Services" > "OAuth consent screen" 27 | 2. Select "External" user type (unless you have a Google Workspace organization) 28 | 3. Fill in the required information: 29 | - App name 30 | - User support email 31 | - Developer contact information 32 | 4. Add the following scopes: 33 | - `https://www.googleapis.com/auth/calendar` 34 | - `https://www.googleapis.com/auth/calendar.events` 35 | 5. Add your email address as a test user 36 | 37 | ### 3. Create OAuth 2.0 Credentials 38 | 39 | 1. Go to "APIs & Services" > "Credentials" 40 | 2. Click "Create Credentials" > "OAuth client ID" 41 | 3. Select "Desktop app" as the application type 42 | 4. Name your client (e.g., "MCP Calendar Client") 43 | 5. Click "Create" 44 | 6. Download the client configuration file (you'll need the client ID and client secret) 45 | 46 | ### 4. Get Refresh Token 47 | 48 | 1. Create a new file named `getToken.js`: 49 | 50 | ```javascript 51 | const { google } = require('googleapis'); 52 | const http = require('http'); 53 | const url = require('url'); 54 | 55 | // Replace these with your OAuth 2.0 credentials 56 | const CLIENT_ID = 'your-client-id'; 57 | const CLIENT_SECRET = 'your-client-secret'; 58 | const REDIRECT_URI = 'http://localhost:3000/oauth2callback'; 59 | 60 | // Configure OAuth2 client 61 | const oauth2Client = new google.auth.OAuth2( 62 | CLIENT_ID, 63 | CLIENT_SECRET, 64 | REDIRECT_URI 65 | ); 66 | 67 | // Define scopes 68 | const scopes = [ 69 | 'https://www.googleapis.com/auth/calendar', 70 | 'https://www.googleapis.com/auth/calendar.events' 71 | ]; 72 | 73 | async function getRefreshToken() { 74 | return new Promise((resolve, reject) => { 75 | try { 76 | // Create server to handle OAuth callback 77 | const server = http.createServer(async (req, res) => { 78 | try { 79 | const queryParams = url.parse(req.url, true).query; 80 | 81 | if (queryParams.code) { 82 | // Get tokens from code 83 | const { tokens } = await oauth2Client.getToken(queryParams.code); 84 | console.log('\n================='); 85 | console.log('Refresh Token:', tokens.refresh_token); 86 | console.log('=================\n'); 87 | console.log('Save this refresh token in your configuration!'); 88 | 89 | // Send success response 90 | res.end('Authentication successful! You can close this window.'); 91 | 92 | // Close server 93 | server.close(); 94 | resolve(tokens); 95 | } 96 | } catch (error) { 97 | console.error('Error getting tokens:', error); 98 | res.end('Authentication failed! Please check console for errors.'); 99 | reject(error); 100 | } 101 | }).listen(3000, () => { 102 | // Generate auth url 103 | const authUrl = oauth2Client.generateAuthUrl({ 104 | access_type: 'offline', 105 | scope: scopes, 106 | prompt: 'consent' // Force consent screen to ensure refresh token 107 | }); 108 | 109 | console.log('1. Copy this URL and paste it in your browser:'); 110 | console.log('\n', authUrl, '\n'); 111 | console.log('2. Follow the Google authentication process'); 112 | console.log('3. Wait for the refresh token to appear here'); 113 | }); 114 | 115 | } catch (error) { 116 | console.error('Server creation error:', error); 117 | reject(error); 118 | } 119 | }); 120 | } 121 | 122 | // Run the token retrieval 123 | getRefreshToken().catch(console.error); 124 | ``` 125 | 126 | 2. Install required dependency: 127 | ```bash 128 | npm install googleapis 129 | ``` 130 | 131 | 3. Update the script with your OAuth credentials: 132 | - Replace `your-client-id` with your actual client ID 133 | - Replace `your-client-secret` with your actual client secret 134 | 135 | 4. Run the script: 136 | ```bash 137 | node getToken.js 138 | ``` 139 | 140 | 5. Follow the instructions in the console: 141 | - Copy the provided URL 142 | - Paste it into your browser 143 | - Complete the Google authentication process 144 | - Copy the refresh token that appears in the console 145 | 146 | ### 5. Configure Claude Desktop 147 | 148 | 1. Open your Claude Desktop configuration file: 149 | 150 | **For MacOS:** 151 | ```bash 152 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json 153 | ``` 154 | 155 | **For Windows:** 156 | ```bash 157 | code %AppData%\Claude\claude_desktop_config.json 158 | ``` 159 | 160 | 2. Add or update the configuration: 161 | ```json 162 | { 163 | "mcpServers": { 164 | "google-calendar": { 165 | "command": "node", 166 | "args": [ 167 | "/ABSOLUTE/PATH/TO/YOUR/build/index.js" 168 | ], 169 | "env": { 170 | "GOOGLE_CLIENT_ID": "your_client_id_here", 171 | "GOOGLE_CLIENT_SECRET": "your_client_secret_here", 172 | "GOOGLE_REDIRECT_URI": "http://localhost", 173 | "GOOGLE_REFRESH_TOKEN": "your_refresh_token_here" 174 | } 175 | } 176 | } 177 | } 178 | ``` 179 | 180 | 3. Save the file and restart Claude Desktop 181 | 182 | ## Initial Project Setup 183 | 184 | 1. Create a new directory for your project: 185 | ```bash 186 | mkdir google-calendar-mcp 187 | cd google-calendar-mcp 188 | ``` 189 | 190 | 2. Initialize a new npm project: 191 | ```bash 192 | npm init -y 193 | ``` 194 | 195 | 3. Install dependencies: 196 | ```bash 197 | npm install @modelcontextprotocol/sdk googleapis google-auth-library zod 198 | npm install -D @types/node typescript 199 | ``` 200 | 201 | 4. Create a tsconfig.json file: 202 | ```json 203 | { 204 | "compilerOptions": { 205 | "target": "ES2022", 206 | "module": "Node16", 207 | "moduleResolution": "Node16", 208 | "outDir": "./build", 209 | "rootDir": "./src", 210 | "strict": true, 211 | "esModuleInterop": true, 212 | "skipLibCheck": true, 213 | "forceConsistentCasingInFileNames": true 214 | }, 215 | "include": ["src/**/*"], 216 | "exclude": ["node_modules"] 217 | } 218 | ``` 219 | 220 | 5. Update package.json: 221 | ```json 222 | { 223 | "type": "module", 224 | "scripts": { 225 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"" 226 | } 227 | } 228 | ``` 229 | 230 | 6. Create your source directory: 231 | ```bash 232 | mkdir src 233 | ``` 234 | 235 | 7. Create a .env file for local development (don't commit this file): 236 | ```bash 237 | GOOGLE_CLIENT_ID=your_client_id_here 238 | GOOGLE_CLIENT_SECRET=your_client_secret_here 239 | GOOGLE_REDIRECT_URI=http://localhost 240 | GOOGLE_REFRESH_TOKEN=your_refresh_token_here 241 | ``` 242 | 243 | ## Building and Running 244 | 245 | 1. Build the server: 246 | ```bash 247 | npm run build 248 | ``` 249 | 250 | 2. The server will automatically start when you open Claude Desktop 251 | 252 | ## Available Tools 253 | 254 | The server provides the following tools: 255 | 256 | 1. `list_events`: List calendar events within a specified time range 257 | 2. `create_event`: Create a new calendar event 258 | 3. `update_event`: Update an existing calendar event 259 | 4. `delete_event`: Delete a calendar event 260 | 5. `find_free_time`: Find available time slots in the calendar 261 | 262 | ## Example Usage in Claude 263 | 264 | After setup, you can use commands like: 265 | 266 | - "Show me my calendar events for next week" 267 | - "Schedule a meeting with [email_id] tomorrow at 2 PM for 1 hour" 268 | - "Find a free 30-minute slot this afternoon" 269 | - "Update my 3 PM meeting to 4 PM" 270 | - "Cancel my meeting with ID [event_id]" 271 | 272 | ## Troubleshooting 273 | 274 | ### Common Issues 275 | 276 | 1. **Tools not appearing in Claude:** 277 | - Check Claude Desktop logs: `tail -f ~/Library/Logs/Claude/mcp*.log` 278 | - Verify all environment variables are set correctly 279 | - Ensure the path to index.js is absolute and correct 280 | 281 | 2. **Authentication Errors:** 282 | - Verify your OAuth credentials are correct 283 | - Check if refresh token is valid 284 | - Ensure required scopes are enabled 285 | 286 | 3. **Server Connection Issues:** 287 | - Check if the server built successfully 288 | - Verify file permissions on build/index.js (should be 755) 289 | - Try running the server directly: `node /path/to/build/index.js` 290 | 291 | ### Viewing Logs 292 | 293 | To view server logs: 294 | ```bash 295 | # For MacOS/Linux: 296 | tail -n 20 -f ~/Library/Logs/Claude/mcp*.log 297 | 298 | # For Windows: 299 | Get-Content -Path "$env:AppData\Claude\Logs\mcp*.log" -Wait -Tail 20 300 | ``` 301 | 302 | ### Environment Variables 303 | 304 | If you're getting environment variable errors, verify each one: 305 | 306 | 1. GOOGLE_CLIENT_ID: Should start with something like "123456789-..." 307 | 2. GOOGLE_CLIENT_SECRET: Usually ends in ".apps.googleusercontent.com" 308 | 3. GOOGLE_REDIRECT_URI: Should be "http://localhost" 309 | 4. GOOGLE_REFRESH_TOKEN: A long string that doesn't expire 310 | 311 | ## Security Considerations 312 | 313 | - Keep your OAuth credentials secure 314 | - Don't commit credentials to version control 315 | - Use environment variables for sensitive data 316 | - Regularly rotate refresh tokens 317 | - Monitor API usage in Google Cloud Console 318 | 319 | ## License 320 | 321 | MIT License - See LICENSE file for details. 322 | 323 | ## Support 324 | 325 | If you encounter any issues: 326 | 1. Check the troubleshooting section above 327 | 2. Review Claude Desktop logs 328 | 3. Open an issue on GitHub 329 | 4. Contact the maintainer -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "googlecalendar", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "googlecalendar", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.0.4", 13 | "google-auth-library": "^9.15.0", 14 | "googleapis": "^144.0.0", 15 | "open": "^10.1.0", 16 | "zod": "^3.24.1" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.10.2", 20 | "typescript": "^5.7.2" 21 | } 22 | }, 23 | "node_modules/@modelcontextprotocol/sdk": { 24 | "version": "1.0.4", 25 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.4.tgz", 26 | "integrity": "sha512-C+jw1lF6HSGzs7EZpzHbXfzz9rj9him4BaoumlTciW/IDDgIpweF/qiCWKlP02QKg5PPcgY6xY2WCt5y2tpYow==", 27 | "license": "MIT", 28 | "dependencies": { 29 | "content-type": "^1.0.5", 30 | "raw-body": "^3.0.0", 31 | "zod": "^3.23.8" 32 | } 33 | }, 34 | "node_modules/@types/node": { 35 | "version": "22.10.2", 36 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", 37 | "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", 38 | "dev": true, 39 | "license": "MIT", 40 | "dependencies": { 41 | "undici-types": "~6.20.0" 42 | } 43 | }, 44 | "node_modules/agent-base": { 45 | "version": "7.1.3", 46 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", 47 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", 48 | "license": "MIT", 49 | "engines": { 50 | "node": ">= 14" 51 | } 52 | }, 53 | "node_modules/base64-js": { 54 | "version": "1.5.1", 55 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 56 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 57 | "funding": [ 58 | { 59 | "type": "github", 60 | "url": "https://github.com/sponsors/feross" 61 | }, 62 | { 63 | "type": "patreon", 64 | "url": "https://www.patreon.com/feross" 65 | }, 66 | { 67 | "type": "consulting", 68 | "url": "https://feross.org/support" 69 | } 70 | ], 71 | "license": "MIT" 72 | }, 73 | "node_modules/bignumber.js": { 74 | "version": "9.1.2", 75 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", 76 | "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", 77 | "license": "MIT", 78 | "engines": { 79 | "node": "*" 80 | } 81 | }, 82 | "node_modules/buffer-equal-constant-time": { 83 | "version": "1.0.1", 84 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 85 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", 86 | "license": "BSD-3-Clause" 87 | }, 88 | "node_modules/bundle-name": { 89 | "version": "4.1.0", 90 | "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", 91 | "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", 92 | "license": "MIT", 93 | "dependencies": { 94 | "run-applescript": "^7.0.0" 95 | }, 96 | "engines": { 97 | "node": ">=18" 98 | }, 99 | "funding": { 100 | "url": "https://github.com/sponsors/sindresorhus" 101 | } 102 | }, 103 | "node_modules/bytes": { 104 | "version": "3.1.2", 105 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 106 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 107 | "license": "MIT", 108 | "engines": { 109 | "node": ">= 0.8" 110 | } 111 | }, 112 | "node_modules/call-bind-apply-helpers": { 113 | "version": "1.0.1", 114 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 115 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 116 | "license": "MIT", 117 | "dependencies": { 118 | "es-errors": "^1.3.0", 119 | "function-bind": "^1.1.2" 120 | }, 121 | "engines": { 122 | "node": ">= 0.4" 123 | } 124 | }, 125 | "node_modules/call-bound": { 126 | "version": "1.0.3", 127 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", 128 | "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", 129 | "license": "MIT", 130 | "dependencies": { 131 | "call-bind-apply-helpers": "^1.0.1", 132 | "get-intrinsic": "^1.2.6" 133 | }, 134 | "engines": { 135 | "node": ">= 0.4" 136 | }, 137 | "funding": { 138 | "url": "https://github.com/sponsors/ljharb" 139 | } 140 | }, 141 | "node_modules/content-type": { 142 | "version": "1.0.5", 143 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 144 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 145 | "license": "MIT", 146 | "engines": { 147 | "node": ">= 0.6" 148 | } 149 | }, 150 | "node_modules/debug": { 151 | "version": "4.4.0", 152 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 153 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 154 | "license": "MIT", 155 | "dependencies": { 156 | "ms": "^2.1.3" 157 | }, 158 | "engines": { 159 | "node": ">=6.0" 160 | }, 161 | "peerDependenciesMeta": { 162 | "supports-color": { 163 | "optional": true 164 | } 165 | } 166 | }, 167 | "node_modules/default-browser": { 168 | "version": "5.2.1", 169 | "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", 170 | "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", 171 | "license": "MIT", 172 | "dependencies": { 173 | "bundle-name": "^4.1.0", 174 | "default-browser-id": "^5.0.0" 175 | }, 176 | "engines": { 177 | "node": ">=18" 178 | }, 179 | "funding": { 180 | "url": "https://github.com/sponsors/sindresorhus" 181 | } 182 | }, 183 | "node_modules/default-browser-id": { 184 | "version": "5.0.0", 185 | "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", 186 | "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", 187 | "license": "MIT", 188 | "engines": { 189 | "node": ">=18" 190 | }, 191 | "funding": { 192 | "url": "https://github.com/sponsors/sindresorhus" 193 | } 194 | }, 195 | "node_modules/define-lazy-prop": { 196 | "version": "3.0.0", 197 | "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", 198 | "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", 199 | "license": "MIT", 200 | "engines": { 201 | "node": ">=12" 202 | }, 203 | "funding": { 204 | "url": "https://github.com/sponsors/sindresorhus" 205 | } 206 | }, 207 | "node_modules/depd": { 208 | "version": "2.0.0", 209 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 210 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 211 | "license": "MIT", 212 | "engines": { 213 | "node": ">= 0.8" 214 | } 215 | }, 216 | "node_modules/dunder-proto": { 217 | "version": "1.0.1", 218 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 219 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 220 | "license": "MIT", 221 | "dependencies": { 222 | "call-bind-apply-helpers": "^1.0.1", 223 | "es-errors": "^1.3.0", 224 | "gopd": "^1.2.0" 225 | }, 226 | "engines": { 227 | "node": ">= 0.4" 228 | } 229 | }, 230 | "node_modules/ecdsa-sig-formatter": { 231 | "version": "1.0.11", 232 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 233 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 234 | "license": "Apache-2.0", 235 | "dependencies": { 236 | "safe-buffer": "^5.0.1" 237 | } 238 | }, 239 | "node_modules/es-define-property": { 240 | "version": "1.0.1", 241 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 242 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 243 | "license": "MIT", 244 | "engines": { 245 | "node": ">= 0.4" 246 | } 247 | }, 248 | "node_modules/es-errors": { 249 | "version": "1.3.0", 250 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 251 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 252 | "license": "MIT", 253 | "engines": { 254 | "node": ">= 0.4" 255 | } 256 | }, 257 | "node_modules/es-object-atoms": { 258 | "version": "1.0.0", 259 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", 260 | "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", 261 | "license": "MIT", 262 | "dependencies": { 263 | "es-errors": "^1.3.0" 264 | }, 265 | "engines": { 266 | "node": ">= 0.4" 267 | } 268 | }, 269 | "node_modules/extend": { 270 | "version": "3.0.2", 271 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 272 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 273 | "license": "MIT" 274 | }, 275 | "node_modules/function-bind": { 276 | "version": "1.1.2", 277 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 278 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 279 | "license": "MIT", 280 | "funding": { 281 | "url": "https://github.com/sponsors/ljharb" 282 | } 283 | }, 284 | "node_modules/gaxios": { 285 | "version": "6.7.1", 286 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", 287 | "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", 288 | "license": "Apache-2.0", 289 | "dependencies": { 290 | "extend": "^3.0.2", 291 | "https-proxy-agent": "^7.0.1", 292 | "is-stream": "^2.0.0", 293 | "node-fetch": "^2.6.9", 294 | "uuid": "^9.0.1" 295 | }, 296 | "engines": { 297 | "node": ">=14" 298 | } 299 | }, 300 | "node_modules/gcp-metadata": { 301 | "version": "6.1.0", 302 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", 303 | "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", 304 | "license": "Apache-2.0", 305 | "dependencies": { 306 | "gaxios": "^6.0.0", 307 | "json-bigint": "^1.0.0" 308 | }, 309 | "engines": { 310 | "node": ">=14" 311 | } 312 | }, 313 | "node_modules/get-intrinsic": { 314 | "version": "1.2.6", 315 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", 316 | "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", 317 | "license": "MIT", 318 | "dependencies": { 319 | "call-bind-apply-helpers": "^1.0.1", 320 | "dunder-proto": "^1.0.0", 321 | "es-define-property": "^1.0.1", 322 | "es-errors": "^1.3.0", 323 | "es-object-atoms": "^1.0.0", 324 | "function-bind": "^1.1.2", 325 | "gopd": "^1.2.0", 326 | "has-symbols": "^1.1.0", 327 | "hasown": "^2.0.2", 328 | "math-intrinsics": "^1.0.0" 329 | }, 330 | "engines": { 331 | "node": ">= 0.4" 332 | }, 333 | "funding": { 334 | "url": "https://github.com/sponsors/ljharb" 335 | } 336 | }, 337 | "node_modules/google-auth-library": { 338 | "version": "9.15.0", 339 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", 340 | "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", 341 | "license": "Apache-2.0", 342 | "dependencies": { 343 | "base64-js": "^1.3.0", 344 | "ecdsa-sig-formatter": "^1.0.11", 345 | "gaxios": "^6.1.1", 346 | "gcp-metadata": "^6.1.0", 347 | "gtoken": "^7.0.0", 348 | "jws": "^4.0.0" 349 | }, 350 | "engines": { 351 | "node": ">=14" 352 | } 353 | }, 354 | "node_modules/googleapis": { 355 | "version": "144.0.0", 356 | "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", 357 | "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", 358 | "license": "Apache-2.0", 359 | "dependencies": { 360 | "google-auth-library": "^9.0.0", 361 | "googleapis-common": "^7.0.0" 362 | }, 363 | "engines": { 364 | "node": ">=14.0.0" 365 | } 366 | }, 367 | "node_modules/googleapis-common": { 368 | "version": "7.2.0", 369 | "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", 370 | "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", 371 | "license": "Apache-2.0", 372 | "dependencies": { 373 | "extend": "^3.0.2", 374 | "gaxios": "^6.0.3", 375 | "google-auth-library": "^9.7.0", 376 | "qs": "^6.7.0", 377 | "url-template": "^2.0.8", 378 | "uuid": "^9.0.0" 379 | }, 380 | "engines": { 381 | "node": ">=14.0.0" 382 | } 383 | }, 384 | "node_modules/gopd": { 385 | "version": "1.2.0", 386 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 387 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 388 | "license": "MIT", 389 | "engines": { 390 | "node": ">= 0.4" 391 | }, 392 | "funding": { 393 | "url": "https://github.com/sponsors/ljharb" 394 | } 395 | }, 396 | "node_modules/gtoken": { 397 | "version": "7.1.0", 398 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", 399 | "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", 400 | "license": "MIT", 401 | "dependencies": { 402 | "gaxios": "^6.0.0", 403 | "jws": "^4.0.0" 404 | }, 405 | "engines": { 406 | "node": ">=14.0.0" 407 | } 408 | }, 409 | "node_modules/has-symbols": { 410 | "version": "1.1.0", 411 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 412 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 413 | "license": "MIT", 414 | "engines": { 415 | "node": ">= 0.4" 416 | }, 417 | "funding": { 418 | "url": "https://github.com/sponsors/ljharb" 419 | } 420 | }, 421 | "node_modules/hasown": { 422 | "version": "2.0.2", 423 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 424 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 425 | "license": "MIT", 426 | "dependencies": { 427 | "function-bind": "^1.1.2" 428 | }, 429 | "engines": { 430 | "node": ">= 0.4" 431 | } 432 | }, 433 | "node_modules/http-errors": { 434 | "version": "2.0.0", 435 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 436 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 437 | "license": "MIT", 438 | "dependencies": { 439 | "depd": "2.0.0", 440 | "inherits": "2.0.4", 441 | "setprototypeof": "1.2.0", 442 | "statuses": "2.0.1", 443 | "toidentifier": "1.0.1" 444 | }, 445 | "engines": { 446 | "node": ">= 0.8" 447 | } 448 | }, 449 | "node_modules/https-proxy-agent": { 450 | "version": "7.0.6", 451 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 452 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 453 | "license": "MIT", 454 | "dependencies": { 455 | "agent-base": "^7.1.2", 456 | "debug": "4" 457 | }, 458 | "engines": { 459 | "node": ">= 14" 460 | } 461 | }, 462 | "node_modules/iconv-lite": { 463 | "version": "0.6.3", 464 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 465 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 466 | "license": "MIT", 467 | "dependencies": { 468 | "safer-buffer": ">= 2.1.2 < 3.0.0" 469 | }, 470 | "engines": { 471 | "node": ">=0.10.0" 472 | } 473 | }, 474 | "node_modules/inherits": { 475 | "version": "2.0.4", 476 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 477 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 478 | "license": "ISC" 479 | }, 480 | "node_modules/is-docker": { 481 | "version": "3.0.0", 482 | "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", 483 | "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", 484 | "license": "MIT", 485 | "bin": { 486 | "is-docker": "cli.js" 487 | }, 488 | "engines": { 489 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 490 | }, 491 | "funding": { 492 | "url": "https://github.com/sponsors/sindresorhus" 493 | } 494 | }, 495 | "node_modules/is-inside-container": { 496 | "version": "1.0.0", 497 | "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", 498 | "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", 499 | "license": "MIT", 500 | "dependencies": { 501 | "is-docker": "^3.0.0" 502 | }, 503 | "bin": { 504 | "is-inside-container": "cli.js" 505 | }, 506 | "engines": { 507 | "node": ">=14.16" 508 | }, 509 | "funding": { 510 | "url": "https://github.com/sponsors/sindresorhus" 511 | } 512 | }, 513 | "node_modules/is-stream": { 514 | "version": "2.0.1", 515 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 516 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 517 | "license": "MIT", 518 | "engines": { 519 | "node": ">=8" 520 | }, 521 | "funding": { 522 | "url": "https://github.com/sponsors/sindresorhus" 523 | } 524 | }, 525 | "node_modules/is-wsl": { 526 | "version": "3.1.0", 527 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", 528 | "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", 529 | "license": "MIT", 530 | "dependencies": { 531 | "is-inside-container": "^1.0.0" 532 | }, 533 | "engines": { 534 | "node": ">=16" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/sponsors/sindresorhus" 538 | } 539 | }, 540 | "node_modules/json-bigint": { 541 | "version": "1.0.0", 542 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", 543 | "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", 544 | "license": "MIT", 545 | "dependencies": { 546 | "bignumber.js": "^9.0.0" 547 | } 548 | }, 549 | "node_modules/jwa": { 550 | "version": "2.0.0", 551 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 552 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 553 | "license": "MIT", 554 | "dependencies": { 555 | "buffer-equal-constant-time": "1.0.1", 556 | "ecdsa-sig-formatter": "1.0.11", 557 | "safe-buffer": "^5.0.1" 558 | } 559 | }, 560 | "node_modules/jws": { 561 | "version": "4.0.0", 562 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 563 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 564 | "license": "MIT", 565 | "dependencies": { 566 | "jwa": "^2.0.0", 567 | "safe-buffer": "^5.0.1" 568 | } 569 | }, 570 | "node_modules/math-intrinsics": { 571 | "version": "1.1.0", 572 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 573 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 574 | "license": "MIT", 575 | "engines": { 576 | "node": ">= 0.4" 577 | } 578 | }, 579 | "node_modules/ms": { 580 | "version": "2.1.3", 581 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 582 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 583 | "license": "MIT" 584 | }, 585 | "node_modules/node-fetch": { 586 | "version": "2.7.0", 587 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 588 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 589 | "license": "MIT", 590 | "dependencies": { 591 | "whatwg-url": "^5.0.0" 592 | }, 593 | "engines": { 594 | "node": "4.x || >=6.0.0" 595 | }, 596 | "peerDependencies": { 597 | "encoding": "^0.1.0" 598 | }, 599 | "peerDependenciesMeta": { 600 | "encoding": { 601 | "optional": true 602 | } 603 | } 604 | }, 605 | "node_modules/object-inspect": { 606 | "version": "1.13.3", 607 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 608 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 609 | "license": "MIT", 610 | "engines": { 611 | "node": ">= 0.4" 612 | }, 613 | "funding": { 614 | "url": "https://github.com/sponsors/ljharb" 615 | } 616 | }, 617 | "node_modules/open": { 618 | "version": "10.1.0", 619 | "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", 620 | "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", 621 | "license": "MIT", 622 | "dependencies": { 623 | "default-browser": "^5.2.1", 624 | "define-lazy-prop": "^3.0.0", 625 | "is-inside-container": "^1.0.0", 626 | "is-wsl": "^3.1.0" 627 | }, 628 | "engines": { 629 | "node": ">=18" 630 | }, 631 | "funding": { 632 | "url": "https://github.com/sponsors/sindresorhus" 633 | } 634 | }, 635 | "node_modules/qs": { 636 | "version": "6.13.1", 637 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", 638 | "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", 639 | "license": "BSD-3-Clause", 640 | "dependencies": { 641 | "side-channel": "^1.0.6" 642 | }, 643 | "engines": { 644 | "node": ">=0.6" 645 | }, 646 | "funding": { 647 | "url": "https://github.com/sponsors/ljharb" 648 | } 649 | }, 650 | "node_modules/raw-body": { 651 | "version": "3.0.0", 652 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 653 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 654 | "license": "MIT", 655 | "dependencies": { 656 | "bytes": "3.1.2", 657 | "http-errors": "2.0.0", 658 | "iconv-lite": "0.6.3", 659 | "unpipe": "1.0.0" 660 | }, 661 | "engines": { 662 | "node": ">= 0.8" 663 | } 664 | }, 665 | "node_modules/run-applescript": { 666 | "version": "7.0.0", 667 | "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", 668 | "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", 669 | "license": "MIT", 670 | "engines": { 671 | "node": ">=18" 672 | }, 673 | "funding": { 674 | "url": "https://github.com/sponsors/sindresorhus" 675 | } 676 | }, 677 | "node_modules/safe-buffer": { 678 | "version": "5.2.1", 679 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 680 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 681 | "funding": [ 682 | { 683 | "type": "github", 684 | "url": "https://github.com/sponsors/feross" 685 | }, 686 | { 687 | "type": "patreon", 688 | "url": "https://www.patreon.com/feross" 689 | }, 690 | { 691 | "type": "consulting", 692 | "url": "https://feross.org/support" 693 | } 694 | ], 695 | "license": "MIT" 696 | }, 697 | "node_modules/safer-buffer": { 698 | "version": "2.1.2", 699 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 700 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 701 | "license": "MIT" 702 | }, 703 | "node_modules/setprototypeof": { 704 | "version": "1.2.0", 705 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 706 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 707 | "license": "ISC" 708 | }, 709 | "node_modules/side-channel": { 710 | "version": "1.1.0", 711 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 712 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 713 | "license": "MIT", 714 | "dependencies": { 715 | "es-errors": "^1.3.0", 716 | "object-inspect": "^1.13.3", 717 | "side-channel-list": "^1.0.0", 718 | "side-channel-map": "^1.0.1", 719 | "side-channel-weakmap": "^1.0.2" 720 | }, 721 | "engines": { 722 | "node": ">= 0.4" 723 | }, 724 | "funding": { 725 | "url": "https://github.com/sponsors/ljharb" 726 | } 727 | }, 728 | "node_modules/side-channel-list": { 729 | "version": "1.0.0", 730 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 731 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 732 | "license": "MIT", 733 | "dependencies": { 734 | "es-errors": "^1.3.0", 735 | "object-inspect": "^1.13.3" 736 | }, 737 | "engines": { 738 | "node": ">= 0.4" 739 | }, 740 | "funding": { 741 | "url": "https://github.com/sponsors/ljharb" 742 | } 743 | }, 744 | "node_modules/side-channel-map": { 745 | "version": "1.0.1", 746 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 747 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 748 | "license": "MIT", 749 | "dependencies": { 750 | "call-bound": "^1.0.2", 751 | "es-errors": "^1.3.0", 752 | "get-intrinsic": "^1.2.5", 753 | "object-inspect": "^1.13.3" 754 | }, 755 | "engines": { 756 | "node": ">= 0.4" 757 | }, 758 | "funding": { 759 | "url": "https://github.com/sponsors/ljharb" 760 | } 761 | }, 762 | "node_modules/side-channel-weakmap": { 763 | "version": "1.0.2", 764 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 765 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 766 | "license": "MIT", 767 | "dependencies": { 768 | "call-bound": "^1.0.2", 769 | "es-errors": "^1.3.0", 770 | "get-intrinsic": "^1.2.5", 771 | "object-inspect": "^1.13.3", 772 | "side-channel-map": "^1.0.1" 773 | }, 774 | "engines": { 775 | "node": ">= 0.4" 776 | }, 777 | "funding": { 778 | "url": "https://github.com/sponsors/ljharb" 779 | } 780 | }, 781 | "node_modules/statuses": { 782 | "version": "2.0.1", 783 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 784 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 785 | "license": "MIT", 786 | "engines": { 787 | "node": ">= 0.8" 788 | } 789 | }, 790 | "node_modules/toidentifier": { 791 | "version": "1.0.1", 792 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 793 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 794 | "license": "MIT", 795 | "engines": { 796 | "node": ">=0.6" 797 | } 798 | }, 799 | "node_modules/tr46": { 800 | "version": "0.0.3", 801 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 802 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 803 | "license": "MIT" 804 | }, 805 | "node_modules/typescript": { 806 | "version": "5.7.2", 807 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 808 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 809 | "dev": true, 810 | "license": "Apache-2.0", 811 | "bin": { 812 | "tsc": "bin/tsc", 813 | "tsserver": "bin/tsserver" 814 | }, 815 | "engines": { 816 | "node": ">=14.17" 817 | } 818 | }, 819 | "node_modules/undici-types": { 820 | "version": "6.20.0", 821 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 822 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 823 | "dev": true, 824 | "license": "MIT" 825 | }, 826 | "node_modules/unpipe": { 827 | "version": "1.0.0", 828 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 829 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 830 | "license": "MIT", 831 | "engines": { 832 | "node": ">= 0.8" 833 | } 834 | }, 835 | "node_modules/url-template": { 836 | "version": "2.0.8", 837 | "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", 838 | "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", 839 | "license": "BSD" 840 | }, 841 | "node_modules/uuid": { 842 | "version": "9.0.1", 843 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", 844 | "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", 845 | "funding": [ 846 | "https://github.com/sponsors/broofa", 847 | "https://github.com/sponsors/ctavan" 848 | ], 849 | "license": "MIT", 850 | "bin": { 851 | "uuid": "dist/bin/uuid" 852 | } 853 | }, 854 | "node_modules/webidl-conversions": { 855 | "version": "3.0.1", 856 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 857 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 858 | "license": "BSD-2-Clause" 859 | }, 860 | "node_modules/whatwg-url": { 861 | "version": "5.0.0", 862 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 863 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 864 | "license": "MIT", 865 | "dependencies": { 866 | "tr46": "~0.0.3", 867 | "webidl-conversions": "^3.0.0" 868 | } 869 | }, 870 | "node_modules/zod": { 871 | "version": "3.24.1", 872 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 873 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 874 | "license": "MIT", 875 | "funding": { 876 | "url": "https://github.com/sponsors/colinhacks" 877 | } 878 | } 879 | } 880 | } 881 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "googlecalendar", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "@modelcontextprotocol/sdk": "^1.0.4", 16 | "google-auth-library": "^9.15.0", 17 | "googleapis": "^144.0.0", 18 | "open": "^10.1.0", 19 | "zod": "^3.24.1" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^22.10.2", 23 | "typescript": "^5.7.2" 24 | }, 25 | "files": [ 26 | "build" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { 4 | CallToolRequestSchema, 5 | ListToolsRequestSchema, 6 | } from "@modelcontextprotocol/sdk/types.js"; 7 | import { z } from "zod"; 8 | import { google } from 'googleapis'; 9 | import { OAuth2Client } from 'google-auth-library'; 10 | 11 | // Initialize Google Calendar client 12 | const oauth2Client = new OAuth2Client({ 13 | clientId: process.env.GOOGLE_CLIENT_ID, 14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 15 | redirectUri: process.env.GOOGLE_REDIRECT_URI, 16 | }); 17 | 18 | // Set credentials from environment variables 19 | oauth2Client.setCredentials({ 20 | refresh_token: process.env.GOOGLE_REFRESH_TOKEN, 21 | }); 22 | 23 | const calendar = google.calendar({ version: 'v3', auth: oauth2Client }); 24 | 25 | // Validation schemas 26 | const schemas = { 27 | toolInputs: { 28 | listEvents: z.object({ 29 | timeMin: z.string().optional(), 30 | timeMax: z.string().optional(), 31 | maxResults: z.number().optional(), 32 | }), 33 | createEvent: z.object({ 34 | summary: z.string(), 35 | description: z.string().optional(), 36 | startTime: z.string(), 37 | endTime: z.string(), 38 | attendees: z.array(z.string()).optional(), 39 | }), 40 | updateEvent: z.object({ 41 | eventId: z.string(), 42 | summary: z.string().optional(), 43 | description: z.string().optional(), 44 | startTime: z.string().optional(), 45 | endTime: z.string().optional(), 46 | }), 47 | deleteEvent: z.object({ 48 | eventId: z.string(), 49 | }), 50 | findFreeTime: z.object({ 51 | timeMin: z.string(), 52 | timeMax: z.string(), 53 | duration: z.number(), // duration in minutes 54 | }) 55 | } 56 | }; 57 | 58 | // Tool definitions 59 | const TOOL_DEFINITIONS = [ 60 | { 61 | name: "list_events", 62 | description: "List calendar events within a specified time range", 63 | inputSchema: { 64 | type: "object", 65 | properties: { 66 | timeMin: { 67 | type: "string", 68 | description: "Start time (ISO string)", 69 | }, 70 | timeMax: { 71 | type: "string", 72 | description: "End time (ISO string)", 73 | }, 74 | maxResults: { 75 | type: "number", 76 | description: "Maximum number of events to return", 77 | }, 78 | }, 79 | }, 80 | }, 81 | { 82 | name: "create_event", 83 | description: "Create a new calendar event", 84 | inputSchema: { 85 | type: "object", 86 | properties: { 87 | summary: { 88 | type: "string", 89 | description: "Event title", 90 | }, 91 | description: { 92 | type: "string", 93 | description: "Event description", 94 | }, 95 | startTime: { 96 | type: "string", 97 | description: "Event start time (ISO string)", 98 | }, 99 | endTime: { 100 | type: "string", 101 | description: "Event end time (ISO string)", 102 | }, 103 | attendees: { 104 | type: "array", 105 | items: { 106 | type: "string", 107 | }, 108 | description: "List of attendee email addresses", 109 | }, 110 | }, 111 | required: ["summary", "startTime", "endTime"], 112 | }, 113 | }, 114 | { 115 | name: "update_event", 116 | description: "Update an existing calendar event", 117 | inputSchema: { 118 | type: "object", 119 | properties: { 120 | eventId: { 121 | type: "string", 122 | description: "ID of the event to update", 123 | }, 124 | summary: { 125 | type: "string", 126 | description: "New event title", 127 | }, 128 | description: { 129 | type: "string", 130 | description: "New event description", 131 | }, 132 | startTime: { 133 | type: "string", 134 | description: "New start time (ISO string)", 135 | }, 136 | endTime: { 137 | type: "string", 138 | description: "New end time (ISO string)", 139 | }, 140 | }, 141 | required: ["eventId"], 142 | }, 143 | }, 144 | { 145 | name: "delete_event", 146 | description: "Delete a calendar event", 147 | inputSchema: { 148 | type: "object", 149 | properties: { 150 | eventId: { 151 | type: "string", 152 | description: "ID of the event to delete", 153 | }, 154 | }, 155 | required: ["eventId"], 156 | }, 157 | }, 158 | { 159 | name: "find_free_time", 160 | description: "Find available time slots in the calendar", 161 | inputSchema: { 162 | type: "object", 163 | properties: { 164 | timeMin: { 165 | type: "string", 166 | description: "Start of time range (ISO string)", 167 | }, 168 | timeMax: { 169 | type: "string", 170 | description: "End of time range (ISO string)", 171 | }, 172 | duration: { 173 | type: "number", 174 | description: "Desired duration in minutes", 175 | }, 176 | }, 177 | required: ["timeMin", "timeMax", "duration"], 178 | }, 179 | }, 180 | ]; 181 | 182 | // Tool implementation handlers 183 | const toolHandlers = { 184 | async list_events(args: unknown) { 185 | const { timeMin, timeMax, maxResults = 10 } = schemas.toolInputs.listEvents.parse(args); 186 | 187 | const response = await calendar.events.list({ 188 | calendarId: 'primary', 189 | timeMin: timeMin || new Date().toISOString(), 190 | timeMax, 191 | maxResults, 192 | singleEvents: true, 193 | orderBy: 'startTime', 194 | }); 195 | 196 | const events = response.data.items || []; 197 | const formattedEvents = events.map(event => { 198 | return `• ${event.summary}\n Start: ${event.start?.dateTime || event.start?.date}\n End: ${event.end?.dateTime || event.end?.date}\n ID: ${event.id}`; 199 | }).join('\n\n'); 200 | 201 | return { 202 | content: [{ 203 | type: "text" as const, 204 | text: events.length ? 205 | `Found ${events.length} events:\n\n${formattedEvents}` : 206 | "No events found in the specified time range." 207 | }] 208 | }; 209 | }, 210 | 211 | async create_event(args: unknown) { 212 | const { summary, description, startTime, endTime, attendees } = schemas.toolInputs.createEvent.parse(args); 213 | 214 | const event = await calendar.events.insert({ 215 | calendarId: 'primary', 216 | requestBody: { 217 | summary, 218 | description, 219 | start: { 220 | dateTime: startTime, 221 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 222 | }, 223 | end: { 224 | dateTime: endTime, 225 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 226 | }, 227 | attendees: attendees?.map(email => ({ email })), 228 | }, 229 | }); 230 | 231 | return { 232 | content: [{ 233 | type: "text" as const, 234 | text: `Event created successfully!\nID: ${event.data.id}\nLink: ${event.data.htmlLink}` 235 | }] 236 | }; 237 | }, 238 | 239 | async update_event(args: unknown) { 240 | const { eventId, summary, description, startTime, endTime } = schemas.toolInputs.updateEvent.parse(args); 241 | 242 | // Get existing event 243 | const existingEvent = await calendar.events.get({ 244 | calendarId: 'primary', 245 | eventId, 246 | }); 247 | 248 | // Prepare update payload 249 | const updatePayload: any = { 250 | summary: summary || existingEvent.data.summary, 251 | description: description || existingEvent.data.description, 252 | }; 253 | 254 | if (startTime) { 255 | updatePayload.start = { 256 | dateTime: startTime, 257 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 258 | }; 259 | } 260 | 261 | if (endTime) { 262 | updatePayload.end = { 263 | dateTime: endTime, 264 | timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, 265 | }; 266 | } 267 | 268 | await calendar.events.update({ 269 | calendarId: 'primary', 270 | eventId, 271 | requestBody: updatePayload, 272 | }); 273 | 274 | return { 275 | content: [{ 276 | type: "text" as const, 277 | text: `Event ${eventId} updated successfully!` 278 | }] 279 | }; 280 | }, 281 | 282 | async delete_event(args: unknown) { 283 | const { eventId } = schemas.toolInputs.deleteEvent.parse(args); 284 | 285 | await calendar.events.delete({ 286 | calendarId: 'primary', 287 | eventId, 288 | }); 289 | 290 | return { 291 | content: [{ 292 | type: "text" as const, 293 | text: `Event ${eventId} deleted successfully!` 294 | }] 295 | }; 296 | }, 297 | 298 | async find_free_time(args: unknown) { 299 | const { timeMin, timeMax, duration } = schemas.toolInputs.findFreeTime.parse(args); 300 | 301 | // Get existing events in the time range 302 | const response = await calendar.events.list({ 303 | calendarId: 'primary', 304 | timeMin, 305 | timeMax, 306 | singleEvents: true, 307 | orderBy: 'startTime', 308 | }); 309 | 310 | const events = response.data.items || []; 311 | const freeTimes: { start: string; end: string }[] = []; 312 | 313 | let currentTime = new Date(timeMin); 314 | const endTime = new Date(timeMax); 315 | const durationMs = duration * 60000; // Convert minutes to milliseconds 316 | 317 | // Find free time slots 318 | for (const event of events) { 319 | const eventStart = new Date(event.start?.dateTime || event.start?.date || ''); 320 | 321 | // Check if there's enough time before the event 322 | if (eventStart.getTime() - currentTime.getTime() >= durationMs) { 323 | freeTimes.push({ 324 | start: currentTime.toISOString(), 325 | end: new Date(eventStart.getTime() - 1).toISOString(), 326 | }); 327 | } 328 | 329 | currentTime = new Date(event.end?.dateTime || event.end?.date || ''); 330 | } 331 | 332 | // Check for free time after the last event 333 | if (endTime.getTime() - currentTime.getTime() >= durationMs) { 334 | freeTimes.push({ 335 | start: currentTime.toISOString(), 336 | end: endTime.toISOString(), 337 | }); 338 | } 339 | 340 | const formattedTimes = freeTimes.map(slot => 341 | `• ${new Date(slot.start).toLocaleString()} - ${new Date(slot.end).toLocaleString()}` 342 | ).join('\n'); 343 | 344 | return { 345 | content: [{ 346 | type: "text" as const, 347 | text: freeTimes.length ? 348 | `Found ${freeTimes.length} available time slots:\n\n${formattedTimes}` : 349 | `No available time slots found for duration of ${duration} minutes in the specified range.` 350 | }] 351 | }; 352 | }, 353 | }; 354 | 355 | // Initialize MCP server 356 | const server = new Server( 357 | { 358 | name: "google-calendar-server", 359 | version: "1.0.0", 360 | }, 361 | { 362 | capabilities: { 363 | tools: {}, 364 | }, 365 | } 366 | ); 367 | 368 | // Register tool handlers 369 | server.setRequestHandler(ListToolsRequestSchema, async () => { 370 | console.error("Tools requested by client"); 371 | return { tools: TOOL_DEFINITIONS }; 372 | }); 373 | 374 | server.setRequestHandler(ListToolsRequestSchema, async () => { 375 | console.error("Tools requested by client"); 376 | console.error("Returning tools:", JSON.stringify(TOOL_DEFINITIONS, null, 2)); 377 | return { tools: TOOL_DEFINITIONS }; 378 | }); 379 | 380 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 381 | const { name, arguments: args } = request.params; 382 | 383 | try { 384 | const handler = toolHandlers[name as keyof typeof toolHandlers]; 385 | if (!handler) { 386 | throw new Error(`Unknown tool: ${name}`); 387 | } 388 | 389 | return await handler(args); 390 | } catch (error) { 391 | console.error(`Error executing tool ${name}:`, error); 392 | throw error; 393 | } 394 | }); 395 | 396 | // Start the server 397 | async function main() { 398 | try { 399 | // Check for required environment variables 400 | const requiredEnvVars = [ 401 | 'GOOGLE_CLIENT_ID', 402 | 'GOOGLE_CLIENT_SECRET', 403 | 'GOOGLE_REDIRECT_URI', 404 | 'GOOGLE_REFRESH_TOKEN' 405 | ]; 406 | 407 | const missingVars = requiredEnvVars.filter(varName => !process.env[varName]); 408 | if (missingVars.length > 0) { 409 | console.error(`Missing required environment variables: ${missingVars.join(', ')}`); 410 | process.exit(1); 411 | } 412 | 413 | console.error("Starting server with env vars:", { 414 | clientId: process.env.GOOGLE_CLIENT_ID?.substring(0, 5) + '...', 415 | clientSecret: process.env.GOOGLE_CLIENT_SECRET?.substring(0, 5) + '...', 416 | redirectUri: process.env.GOOGLE_REDIRECT_URI, 417 | hasRefreshToken: !!process.env.GOOGLE_REFRESH_TOKEN 418 | }); 419 | 420 | const transport = new StdioServerTransport(); 421 | console.error("Created transport"); 422 | 423 | await server.connect(transport); 424 | console.error("Connected to transport"); 425 | 426 | console.error("Google Calendar MCP Server running on stdio"); 427 | } catch (error) { 428 | console.error("Startup error:", error); 429 | process.exit(1); 430 | } 431 | } 432 | 433 | main().catch((error) => { 434 | console.error("Fatal error:", error); 435 | process.exit(1); 436 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } --------------------------------------------------------------------------------