#
tokens: 6977/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log*
 4 | yarn-debug.log*
 5 | yarn-error.log*
 6 | 
 7 | # Build
 8 | build/
 9 | dist/
10 | *.tsbuildinfo
11 | 
12 | # Environment
13 | .env
14 | .env.local
15 | .env.*.local
16 | 
17 | # IDE
18 | .idea/
19 | .vscode/
20 | *.swp
21 | *.swo
22 | 
23 | # OS
24 | .DS_Store
25 | Thumbs.db
26 | 
27 | # Project specific
28 | gcp-oauth.keys.json
29 | .calendar-mcp/
30 | credentials.json
31 | .calendar-server-credentials.json 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Calendar AutoAuth MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server for Google Calendar integration in Cluade Desktop with auto authentication support. This server enables AI assistants to manage Google Calendar events through natural language interactions.
  4 | 
  5 | ![](https://badge.mcpx.dev?type=server 'MCP Server')
  6 | [![smithery badge](https://smithery.ai/badge/@gongrzhe/server-calendar-autoauth-mcp)](https://smithery.ai/server/@gongrzhe/server-calendar-autoauth-mcp)
  7 | [![npm version](https://badge.fury.io/js/%40gongrzhe%2Fserver-calendar-autoauth-mcp.svg)](https://www.npmjs.com/package/@gongrzhe/server-calendar-autoauth-mcp)
  8 | [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
  9 | 
 10 | ## Features
 11 | 
 12 | - Create calendar events with title, time, description, and location
 13 | - Retrieve event details by event ID
 14 | - Update existing events (title, time, description, location)
 15 | - Delete events
 16 | - List events within a specified time range
 17 | - Full integration with Google Calendar API
 18 | - Simple OAuth2 authentication flow with auto browser launch
 19 | - Support for both Desktop and Web application credentials
 20 | - Global credential storage for convenience
 21 | 
 22 | ## Installation & Authentication
 23 | 
 24 | ### Installing via Smithery
 25 | 
 26 | To install Calendar AutoAuth Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@gongrzhe/server-calendar-autoauth-mcp):
 27 | 
 28 | ```bash
 29 | npx -y @smithery/cli install @gongrzhe/server-calendar-autoauth-mcp --client claude
 30 | ```
 31 | 
 32 | 1. Create a Google Cloud Project and obtain credentials:
 33 | 
 34 |    a. Create a Google Cloud Project:
 35 |       - Go to [Google Cloud Console](https://console.cloud.google.com/)
 36 |       - Create a new project or select an existing one
 37 |       - Enable the Google Calendar API for your project
 38 | 
 39 |    b. Create OAuth 2.0 Credentials:
 40 |       - Go to "APIs & Services" > "Credentials"
 41 |       - Click "Create Credentials" > "OAuth client ID"
 42 |       - Choose either "Desktop app" or "Web application" as application type
 43 |       - Give it a name and click "Create"
 44 |       - For Web application, add `http://localhost:3000/oauth2callback` to the authorized redirect URIs
 45 |       - Download the JSON file of your client's OAuth keys
 46 |       - Rename the key file to `gcp-oauth.keys.json`
 47 | 
 48 | 2. Run Authentication:
 49 | 
 50 |    You can authenticate in two ways:
 51 | 
 52 |    a. Global Authentication (Recommended):
 53 |    ```bash
 54 |    # First time: Place gcp-oauth.keys.json in your home directory's .calendar-mcp folder
 55 |    mkdir -p ~/.calendar-mcp
 56 |    mv gcp-oauth.keys.json ~/.calendar-mcp/
 57 | 
 58 |    # Run authentication from anywhere
 59 |    npx @gongrzhe/server-calendar-autoauth-mcp auth
 60 |    ```
 61 | 
 62 |    b. Local Authentication:
 63 |    ```bash
 64 |    # Place gcp-oauth.keys.json in your current directory
 65 |    # The file will be automatically copied to global config
 66 |    npx @gongrzhe/server-calendar-autoauth-mcp auth
 67 |    ```
 68 | 
 69 |    The authentication process will:
 70 |    - Look for `gcp-oauth.keys.json` in the current directory or `~/.calendar-mcp/`
 71 |    - If found in current directory, copy it to `~/.calendar-mcp/`
 72 |    - Open your default browser for Google authentication
 73 |    - Save credentials as `~/.calendar-mcp/credentials.json`
 74 | 
 75 |    > **Note**: 
 76 |    > - After successful authentication, credentials are stored globally in `~/.calendar-mcp/` and can be used from any directory
 77 |    > - Both Desktop app and Web application credentials are supported
 78 |    > - For Web application credentials, make sure to add `http://localhost:3000/oauth2callback` to your authorized redirect URIs
 79 | 
 80 | 3. Configure in Claude Desktop:
 81 | 
 82 | ```json
 83 | {
 84 |   "mcpServers": {
 85 |     "calendar": {
 86 |       "command": "npx",
 87 |       "args": [
 88 |         "@gongrzhe/server-calendar-autoauth-mcp"
 89 |       ]
 90 |     }
 91 |   }
 92 | }
 93 | ```
 94 | 
 95 | ### Docker Support
 96 | 
 97 | If you prefer using Docker:
 98 | 
 99 | 1. Authentication:
100 | ```bash
101 | docker run -i --rm \
102 |   --mount type=bind,source=/path/to/gcp-oauth.keys.json,target=/gcp-oauth.keys.json \
103 |   -v mcp-calendar:/calendar-server \
104 |   -e CALENDAR_OAUTH_PATH=/gcp-oauth.keys.json \
105 |   -e "CALENDAR_CREDENTIALS_PATH=/calendar-server/credentials.json" \
106 |   -p 3000:3000 \
107 |   mcp/calendar auth
108 | ```
109 | 
110 | 2. Usage:
111 | ```json
112 | {
113 |   "mcpServers": {
114 |     "calendar": {
115 |       "command": "docker",
116 |       "args": [
117 |         "run",
118 |         "-i",
119 |         "--rm",
120 |         "-v",
121 |         "mcp-calendar:/calendar-server",
122 |         "-e",
123 |         "CALENDAR_CREDENTIALS_PATH=/calendar-server/credentials.json",
124 |         "mcp/calendar"
125 |       ]
126 |     }
127 |   }
128 | }
129 | ```
130 | 
131 | ## Usage Examples
132 | 
133 | The server provides several tools that can be used through the Claude Desktop:
134 | 
135 | ### Create Event
136 | ```json
137 | {
138 |   "summary": "Team Meeting",
139 |   "start": {
140 |     "dateTime": "2024-01-20T10:00:00Z"
141 |   },
142 |   "end": {
143 |     "dateTime": "2024-01-20T11:00:00Z"
144 |   },
145 |   "description": "Weekly team sync",
146 |   "location": "Conference Room A"
147 | }
148 | ```
149 | 
150 | ### List Events
151 | ```json
152 | {
153 |   "timeMin": "2024-01-01T00:00:00Z",
154 |   "timeMax": "2024-12-31T23:59:59Z",
155 |   "maxResults": 10,
156 |   "orderBy": "startTime"
157 | }
158 | ```
159 | 
160 | ### Update Event
161 | ```json
162 | {
163 |   "eventId": "event123",
164 |   "summary": "Updated Meeting Title",
165 |   "start": {
166 |     "dateTime": "2024-01-20T11:00:00Z"
167 |   },
168 |   "end": {
169 |     "dateTime": "2024-01-20T12:00:00Z"
170 |   }
171 | }
172 | ```
173 | 
174 | ### Delete Event
175 | ```json
176 | {
177 |   "eventId": "event123"
178 | }
179 | ```
180 | 
181 | ## Security Notes
182 | 
183 | - OAuth credentials are stored securely in your local environment (`~/.calendar-mcp/`)
184 | - The server uses offline access to maintain persistent authentication
185 | - Never share or commit your credentials to version control
186 | - Regularly review and revoke unused access in your Google Account settings
187 | - Credentials are stored globally but are only accessible by the current user
188 | 
189 | ## Troubleshooting
190 | 
191 | 1. **OAuth Keys Not Found**
192 |    - Make sure `gcp-oauth.keys.json` is in either your current directory or `~/.calendar-mcp/`
193 |    - Check file permissions
194 | 
195 | 2. **Invalid Credentials Format**
196 |    - Ensure your OAuth keys file contains either `web` or `installed` credentials
197 |    - For web applications, verify the redirect URI is correctly configured
198 | 
199 | 3. **Port Already in Use**
200 |    - If port 3000 is already in use, please free it up before running authentication
201 |    - You can find and stop the process using that port
202 | 
203 | ## Contributing
204 | 
205 | Contributions are welcome! Please feel free to submit a Pull Request.
206 | 
207 | ## License
208 | 
209 | This project is licensed under the ISC License.
210 | 
211 | ## Author
212 | 
213 | gongrzhe
214 | 
215 | ## Support
216 | 
217 | If you encounter any issues or have questions, please file an issue on the GitHub repository.
218 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: '3.8'
 2 | 
 3 | services:
 4 |   redis:
 5 |     image: redis:latest
 6 |     container_name: my-redis
 7 |     ports:
 8 |       - "6379:6379"
 9 |     volumes:
10 |       - redis_data:/data
11 |     command: redis-server --appendonly yes
12 |     restart: always
13 | 
14 | volumes:
15 |   redis_data: 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2020",
 4 |       "module": "ES2020",
 5 |       "moduleResolution": "node",
 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", "build"]
15 |   }
16 |   
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM node:20-alpine
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | # Copy package files
 6 | COPY package*.json ./
 7 | 
 8 | # Install dependencies
 9 | RUN npm install
10 | 
11 | # Copy source code
12 | COPY . .
13 | 
14 | # Build the application
15 | RUN npm run build
16 | 
17 | # Create data directory
18 | RUN mkdir -p /app/calendar-data
19 | 
20 | # Set permissions for the data directory
21 | RUN chown -R node:node /app/calendar-data
22 | 
23 | # Switch to non-root user
24 | USER node
25 | 
26 | # Start the server
27 | CMD ["node", "build/index.js"]
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - calendarOauthPath
10 |       - calendarCredentialsPath
11 |     properties:
12 |       calendarOauthPath:
13 |         type: string
14 |         default: ~/.calendar-mcp/gcp-oauth.keys.json
15 |         description: Path to the Google OAuth credentials file.
16 |       calendarCredentialsPath:
17 |         type: string
18 |         default: ~/.calendar-mcp/credentials.json
19 |         description: Path where the OAuth tokens will be stored.
20 |   commandFunction:
21 |     # A function that produces the CLI command to start the MCP on stdio.
22 |     |-
23 |     (config) => ({ command: 'node', args: ['build/index.js'], env: { CALENDAR_OAUTH_PATH: config.calendarOauthPath, CALENDAR_CREDENTIALS_PATH: config.calendarCredentialsPath } })
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@gongrzhe/server-calendar-autoauth-mcp",
 3 |   "version": "1.0.2",
 4 |   "description": "A Model Context Protocol server for Google Calendar integration with auto authentication",
 5 |   "main": "build/index.js",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "server-calendar-autoauth-mcp": "./build/index.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc",
12 |     "prepublishOnly": "npm run build",
13 |     "auth": "node ./build/index.js auth"
14 |   },
15 |   "files": [
16 |     "build",
17 |     "README.md"
18 |   ],
19 |   "keywords": [
20 |     "calendar",
21 |     "events",
22 |     "scheduling",
23 |     "mcp",
24 |     "model-context-protocol",
25 |     "google-calendar",
26 |     "claude",
27 |     "cursor",
28 |     "auto-auth"
29 |   ],
30 |   "author": "gongrzhe",
31 |   "license": "ISC",
32 |   "repository": {
33 |     "type": "git",
34 |     "url": "git+https://github.com/gongrzhe/server-calendar-autoauth-mcp.git"
35 |   },
36 |   "bugs": {
37 |     "url": "https://github.com/gongrzhe/server-calendar-autoauth-mcp/issues"
38 |   },
39 |   "homepage": "https://github.com/gongrzhe/server-calendar-autoauth-mcp#readme",
40 |   "publishConfig": {
41 |     "access": "public"
42 |   },
43 |   "devDependencies": {
44 |     "@types/node": "^22.10.2",
45 |     "@types/open": "^6.2.1",
46 |     "typescript": "^5.7.2"
47 |   },
48 |   "dependencies": {
49 |     "@modelcontextprotocol/sdk": "^0.4.0",
50 |     "googleapis": "^133.0.0",
51 |     "open": "^8.4.2",
52 |     "zod": "^3.24.1",
53 |     "zod-to-json-schema": "^3.22.4"
54 |   },
55 |   "engines": {
56 |     "node": ">=14.0.0"
57 |   }
58 | }
59 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |     CallToolRequestSchema,
  7 |     ListToolsRequestSchema,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | import { google } from 'googleapis';
 10 | import { z } from "zod";
 11 | import { zodToJsonSchema } from "zod-to-json-schema";
 12 | import { OAuth2Client } from 'google-auth-library';
 13 | import fs from 'fs';
 14 | import path from 'path';
 15 | import { fileURLToPath } from 'url';
 16 | import http from 'http';
 17 | import open from 'open';
 18 | import os from 'os';
 19 | 
 20 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 21 | 
 22 | // Configuration paths
 23 | const CONFIG_DIR = path.join(os.homedir(), '.calendar-mcp');
 24 | const OAUTH_PATH = process.env.CALENDAR_OAUTH_PATH || path.join(CONFIG_DIR, 'gcp-oauth.keys.json');
 25 | const CREDENTIALS_PATH = process.env.CALENDAR_CREDENTIALS_PATH || path.join(CONFIG_DIR, 'credentials.json');
 26 | 
 27 | // OAuth2 configuration
 28 | let oauth2Client: OAuth2Client;
 29 | 
 30 | async function loadCredentials() {
 31 |     try {
 32 |         // Create config directory if it doesn't exist
 33 |         if (!fs.existsSync(CONFIG_DIR)) {
 34 |             fs.mkdirSync(CONFIG_DIR, { recursive: true });
 35 |         }
 36 | 
 37 |         // Check for OAuth keys in current directory first, then in config directory
 38 |         const localOAuthPath = path.join(process.cwd(), 'gcp-oauth.keys.json');
 39 |         let oauthPath = OAUTH_PATH;
 40 |         
 41 |         if (fs.existsSync(localOAuthPath)) {
 42 |             // If found in current directory, copy to config directory
 43 |             fs.copyFileSync(localOAuthPath, OAUTH_PATH);
 44 |             console.log('OAuth keys found in current directory, copied to global config.');
 45 |         }
 46 | 
 47 |         if (!fs.existsSync(OAUTH_PATH)) {
 48 |             console.error('Error: OAuth keys file not found. Please place gcp-oauth.keys.json in current directory or', CONFIG_DIR);
 49 |             process.exit(1);
 50 |         }
 51 | 
 52 |         const keysContent = JSON.parse(fs.readFileSync(OAUTH_PATH, 'utf8'));
 53 |         const keys = keysContent.installed || keysContent.web;
 54 |         
 55 |         if (!keys) {
 56 |             console.error('Error: Invalid OAuth keys file format. File should contain either "installed" or "web" credentials.');
 57 |             process.exit(1);
 58 |         }
 59 | 
 60 |         oauth2Client = new OAuth2Client(
 61 |             keys.client_id,
 62 |             keys.client_secret,
 63 |             'http://localhost:3000/oauth2callback'
 64 |         );
 65 | 
 66 |         if (fs.existsSync(CREDENTIALS_PATH)) {
 67 |             const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
 68 |             oauth2Client.setCredentials(credentials);
 69 |         }
 70 |     } catch (error) {
 71 |         console.error('Error loading credentials:', error);
 72 |         process.exit(1);
 73 |     }
 74 | }
 75 | 
 76 | async function authenticate() {
 77 |     const server = http.createServer();
 78 |     server.listen(3000);
 79 | 
 80 |     return new Promise<void>((resolve, reject) => {
 81 |         const authUrl = oauth2Client.generateAuthUrl({
 82 |             access_type: 'offline',
 83 |             scope: ['https://www.googleapis.com/auth/calendar'],
 84 |         });
 85 | 
 86 |         console.log('Please visit this URL to authenticate:', authUrl);
 87 |         open(authUrl);
 88 | 
 89 |         server.on('request', async (req, res) => {
 90 |             if (!req.url?.startsWith('/oauth2callback')) return;
 91 | 
 92 |             const url = new URL(req.url, 'http://localhost:3000');
 93 |             const code = url.searchParams.get('code');
 94 | 
 95 |             if (!code) {
 96 |                 res.writeHead(400);
 97 |                 res.end('No code provided');
 98 |                 reject(new Error('No code provided'));
 99 |                 return;
100 |             }
101 | 
102 |             try {
103 |                 const { tokens } = await oauth2Client.getToken(code);
104 |                 oauth2Client.setCredentials(tokens);
105 |                 fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(tokens));
106 | 
107 |                 res.writeHead(200);
108 |                 res.end('Authentication successful! You can close this window.');
109 |                 server.close();
110 |                 resolve();
111 |             } catch (error) {
112 |                 res.writeHead(500);
113 |                 res.end('Authentication failed');
114 |                 reject(error);
115 |             }
116 |         });
117 |     });
118 | }
119 | 
120 | // Schema definitions
121 | const CreateEventSchema = z.object({
122 |     summary: z.string().describe("Event title"),
123 |     start: z.object({
124 |         dateTime: z.string().describe("Start time (ISO format)"),
125 |         timeZone: z.string().optional().describe("Time zone"),
126 |     }),
127 |     end: z.object({
128 |         dateTime: z.string().describe("End time (ISO format)"),
129 |         timeZone: z.string().optional().describe("Time zone"),
130 |     }),
131 |     description: z.string().optional().describe("Event description"),
132 |     location: z.string().optional().describe("Event location"),
133 | });
134 | 
135 | const GetEventSchema = z.object({
136 |     eventId: z.string().describe("ID of the event to retrieve"),
137 | });
138 | 
139 | const UpdateEventSchema = z.object({
140 |     eventId: z.string().describe("ID of the event to update"),
141 |     summary: z.string().optional().describe("New event title"),
142 |     start: z.object({
143 |         dateTime: z.string().describe("New start time (ISO format)"),
144 |         timeZone: z.string().optional().describe("Time zone"),
145 |     }).optional(),
146 |     end: z.object({
147 |         dateTime: z.string().describe("New end time (ISO format)"),
148 |         timeZone: z.string().optional().describe("Time zone"),
149 |     }).optional(),
150 |     description: z.string().optional().describe("New event description"),
151 |     location: z.string().optional().describe("New event location"),
152 | });
153 | 
154 | const DeleteEventSchema = z.object({
155 |     eventId: z.string().describe("ID of the event to delete"),
156 | });
157 | 
158 | const ListEventsSchema = z.object({
159 |     timeMin: z.string().describe("Start of time range (ISO format)"),
160 |     timeMax: z.string().describe("End of time range (ISO format)"),
161 |     maxResults: z.number().optional().describe("Maximum number of events to return"),
162 |     orderBy: z.enum(['startTime', 'updated']).optional().describe("Sort order"),
163 | });
164 | 
165 | // Main function
166 | async function main() {
167 |     await loadCredentials();
168 | 
169 |     if (process.argv[2] === 'auth') {
170 |         await authenticate();
171 |         console.log('Authentication completed successfully');
172 |         process.exit(0);
173 |     }
174 | 
175 |     // Initialize Google Calendar API
176 |     const calendar = google.calendar({ version: 'v3', auth: oauth2Client });
177 |     const calendarId = 'primary';
178 | 
179 |     // Server implementation
180 |     const server = new Server({
181 |         name: "google-calendar",
182 |         version: "1.0.0",
183 |         capabilities: {
184 |             tools: {},
185 |         },
186 |     });
187 | 
188 |     // Tool handlers
189 |     server.setRequestHandler(ListToolsRequestSchema, async () => ({
190 |         tools: [
191 |             {
192 |                 name: "create_event",
193 |                 description: "Creates a new event in Google Calendar",
194 |                 inputSchema: zodToJsonSchema(CreateEventSchema),
195 |             },
196 |             {
197 |                 name: "get_event",
198 |                 description: "Retrieves details of a specific event",
199 |                 inputSchema: zodToJsonSchema(GetEventSchema),
200 |             },
201 |             {
202 |                 name: "update_event",
203 |                 description: "Updates an existing event",
204 |                 inputSchema: zodToJsonSchema(UpdateEventSchema),
205 |             },
206 |             {
207 |                 name: "delete_event",
208 |                 description: "Deletes an event from the calendar",
209 |                 inputSchema: zodToJsonSchema(DeleteEventSchema),
210 |             },
211 |             {
212 |                 name: "list_events",
213 |                 description: "Lists events within a specified time range",
214 |                 inputSchema: zodToJsonSchema(ListEventsSchema),
215 |             },
216 |         ],
217 |     }));
218 | 
219 |     server.setRequestHandler(CallToolRequestSchema, async (request) => {
220 |         const { name, arguments: args } = request.params;
221 | 
222 |         try {
223 |             switch (name) {
224 |                 case "create_event": {
225 |                     const validatedArgs = CreateEventSchema.parse(args);
226 |                     const response = await calendar.events.insert({
227 |                         calendarId,
228 |                         requestBody: validatedArgs,
229 |                     });
230 |                     return {
231 |                         content: [
232 |                             {
233 |                                 type: "text",
234 |                                 text: `Event created with ID: ${response.data.id}\n` +
235 |                                       `Title: ${validatedArgs.summary}\n` +
236 |                                       `Start: ${validatedArgs.start.dateTime}\n` +
237 |                                       `End: ${validatedArgs.end.dateTime}`,
238 |                             },
239 |                         ],
240 |                     };
241 |                 }
242 | 
243 |                 case "get_event": {
244 |                     const validatedArgs = GetEventSchema.parse(args);
245 |                     const response = await calendar.events.get({
246 |                         calendarId,
247 |                         eventId: validatedArgs.eventId,
248 |                     });
249 |                     return {
250 |                         content: [
251 |                             {
252 |                                 type: "text",
253 |                                 text: JSON.stringify(response.data, null, 2),
254 |                             },
255 |                         ],
256 |                     };
257 |                 }
258 | 
259 |                 case "update_event": {
260 |                     const validatedArgs = UpdateEventSchema.parse(args);
261 |                     const { eventId, ...updates } = validatedArgs;
262 |                     const response = await calendar.events.patch({
263 |                         calendarId,
264 |                         eventId,
265 |                         requestBody: updates,
266 |                     });
267 |                     return {
268 |                         content: [
269 |                             {
270 |                                 type: "text",
271 |                                 text: `Event updated: ${eventId}\n` +
272 |                                       `New title: ${updates.summary || '(unchanged)'}\n` +
273 |                                       `New start: ${updates.start?.dateTime || '(unchanged)'}\n` +
274 |                                       `New end: ${updates.end?.dateTime || '(unchanged)'}`,
275 |                             },
276 |                         ],
277 |                     };
278 |                 }
279 | 
280 |                 case "delete_event": {
281 |                     const validatedArgs = DeleteEventSchema.parse(args);
282 |                     await calendar.events.delete({
283 |                         calendarId,
284 |                         eventId: validatedArgs.eventId,
285 |                     });
286 |                     return {
287 |                         content: [
288 |                             {
289 |                                 type: "text",
290 |                                 text: `Event deleted: ${validatedArgs.eventId}`,
291 |                             },
292 |                         ],
293 |                     };
294 |                 }
295 | 
296 |                 case "list_events": {
297 |                     const validatedArgs = ListEventsSchema.parse(args);
298 |                     const response = await calendar.events.list({
299 |                         calendarId,
300 |                         timeMin: validatedArgs.timeMin,
301 |                         timeMax: validatedArgs.timeMax,
302 |                         maxResults: validatedArgs.maxResults || 10,
303 |                         orderBy: validatedArgs.orderBy || 'startTime',
304 |                         singleEvents: true,
305 |                     });
306 |                     return {
307 |                         content: [
308 |                             {
309 |                                 type: "text",
310 |                                 text: `Found ${response.data.items?.length || 0} events:\n` +
311 |                                       JSON.stringify(response.data.items, null, 2),
312 |                             },
313 |                         ],
314 |                     };
315 |                 }
316 | 
317 |                 default:
318 |                     throw new Error(`Unknown tool: ${name}`);
319 |             }
320 |         } catch (error) {
321 |             return {
322 |                 content: [
323 |                     {
324 |                         type: "text",
325 |                         text: `Error: ${error instanceof Error ? error.message : String(error)}`,
326 |                     },
327 |                 ],
328 |                 isError: true,
329 |             };
330 |         }
331 |     });
332 | 
333 |     // Start the server
334 |     const transport = new StdioServerTransport();
335 |     server.connect(transport).catch((error) => {
336 |         console.error("Fatal error running server:", error);
337 |         process.exit(1);
338 |     });
339 |     console.error('Google Calendar MCP Server running on stdio');
340 | }
341 | 
342 | main().catch(console.error);
```