@iflow-mcp/v-3-google-calendar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/build/index.js +392 -0
  4. package/package.json +31 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
package/README.md ADDED
@@ -0,0 +1,329 @@
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/build/index.js ADDED
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { z } from "zod";
6
+ import { google } from 'googleapis';
7
+ import { OAuth2Client } from 'google-auth-library';
8
+ // Initialize Google Calendar client
9
+ const oauth2Client = new OAuth2Client({
10
+ clientId: process.env.GOOGLE_CLIENT_ID,
11
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
12
+ redirectUri: process.env.GOOGLE_REDIRECT_URI,
13
+ });
14
+ // Set credentials from environment variables
15
+ oauth2Client.setCredentials({
16
+ refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
17
+ });
18
+ const calendar = google.calendar({ version: 'v3', auth: oauth2Client });
19
+ // Validation schemas
20
+ const schemas = {
21
+ toolInputs: {
22
+ listEvents: z.object({
23
+ timeMin: z.string().optional(),
24
+ timeMax: z.string().optional(),
25
+ maxResults: z.number().optional(),
26
+ }),
27
+ createEvent: z.object({
28
+ summary: z.string(),
29
+ description: z.string().optional(),
30
+ startTime: z.string(),
31
+ endTime: z.string(),
32
+ attendees: z.array(z.string()).optional(),
33
+ }),
34
+ updateEvent: z.object({
35
+ eventId: z.string(),
36
+ summary: z.string().optional(),
37
+ description: z.string().optional(),
38
+ startTime: z.string().optional(),
39
+ endTime: z.string().optional(),
40
+ }),
41
+ deleteEvent: z.object({
42
+ eventId: z.string(),
43
+ }),
44
+ findFreeTime: z.object({
45
+ timeMin: z.string(),
46
+ timeMax: z.string(),
47
+ duration: z.number(), // duration in minutes
48
+ })
49
+ }
50
+ };
51
+ // Tool definitions
52
+ const TOOL_DEFINITIONS = [
53
+ {
54
+ name: "list_events",
55
+ description: "List calendar events within a specified time range",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: {
59
+ timeMin: {
60
+ type: "string",
61
+ description: "Start time (ISO string)",
62
+ },
63
+ timeMax: {
64
+ type: "string",
65
+ description: "End time (ISO string)",
66
+ },
67
+ maxResults: {
68
+ type: "number",
69
+ description: "Maximum number of events to return",
70
+ },
71
+ },
72
+ },
73
+ },
74
+ {
75
+ name: "create_event",
76
+ description: "Create a new calendar event",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ summary: {
81
+ type: "string",
82
+ description: "Event title",
83
+ },
84
+ description: {
85
+ type: "string",
86
+ description: "Event description",
87
+ },
88
+ startTime: {
89
+ type: "string",
90
+ description: "Event start time (ISO string)",
91
+ },
92
+ endTime: {
93
+ type: "string",
94
+ description: "Event end time (ISO string)",
95
+ },
96
+ attendees: {
97
+ type: "array",
98
+ items: {
99
+ type: "string",
100
+ },
101
+ description: "List of attendee email addresses",
102
+ },
103
+ },
104
+ required: ["summary", "startTime", "endTime"],
105
+ },
106
+ },
107
+ {
108
+ name: "update_event",
109
+ description: "Update an existing calendar event",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ eventId: {
114
+ type: "string",
115
+ description: "ID of the event to update",
116
+ },
117
+ summary: {
118
+ type: "string",
119
+ description: "New event title",
120
+ },
121
+ description: {
122
+ type: "string",
123
+ description: "New event description",
124
+ },
125
+ startTime: {
126
+ type: "string",
127
+ description: "New start time (ISO string)",
128
+ },
129
+ endTime: {
130
+ type: "string",
131
+ description: "New end time (ISO string)",
132
+ },
133
+ },
134
+ required: ["eventId"],
135
+ },
136
+ },
137
+ {
138
+ name: "delete_event",
139
+ description: "Delete a calendar event",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ eventId: {
144
+ type: "string",
145
+ description: "ID of the event to delete",
146
+ },
147
+ },
148
+ required: ["eventId"],
149
+ },
150
+ },
151
+ {
152
+ name: "find_free_time",
153
+ description: "Find available time slots in the calendar",
154
+ inputSchema: {
155
+ type: "object",
156
+ properties: {
157
+ timeMin: {
158
+ type: "string",
159
+ description: "Start of time range (ISO string)",
160
+ },
161
+ timeMax: {
162
+ type: "string",
163
+ description: "End of time range (ISO string)",
164
+ },
165
+ duration: {
166
+ type: "number",
167
+ description: "Desired duration in minutes",
168
+ },
169
+ },
170
+ required: ["timeMin", "timeMax", "duration"],
171
+ },
172
+ },
173
+ ];
174
+ // Tool implementation handlers
175
+ const toolHandlers = {
176
+ async list_events(args) {
177
+ const { timeMin, timeMax, maxResults = 10 } = schemas.toolInputs.listEvents.parse(args);
178
+ const response = await calendar.events.list({
179
+ calendarId: 'primary',
180
+ timeMin: timeMin || new Date().toISOString(),
181
+ timeMax,
182
+ maxResults,
183
+ singleEvents: true,
184
+ orderBy: 'startTime',
185
+ });
186
+ const events = response.data.items || [];
187
+ const formattedEvents = events.map(event => {
188
+ return `• ${event.summary}\n Start: ${event.start?.dateTime || event.start?.date}\n End: ${event.end?.dateTime || event.end?.date}\n ID: ${event.id}`;
189
+ }).join('\n\n');
190
+ return {
191
+ content: [{
192
+ type: "text",
193
+ text: events.length ?
194
+ `Found ${events.length} events:\n\n${formattedEvents}` :
195
+ "No events found in the specified time range."
196
+ }]
197
+ };
198
+ },
199
+ async create_event(args) {
200
+ const { summary, description, startTime, endTime, attendees } = schemas.toolInputs.createEvent.parse(args);
201
+ const event = await calendar.events.insert({
202
+ calendarId: 'primary',
203
+ requestBody: {
204
+ summary,
205
+ description,
206
+ start: {
207
+ dateTime: startTime,
208
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
209
+ },
210
+ end: {
211
+ dateTime: endTime,
212
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
213
+ },
214
+ attendees: attendees?.map(email => ({ email })),
215
+ },
216
+ });
217
+ return {
218
+ content: [{
219
+ type: "text",
220
+ text: `Event created successfully!\nID: ${event.data.id}\nLink: ${event.data.htmlLink}`
221
+ }]
222
+ };
223
+ },
224
+ async update_event(args) {
225
+ const { eventId, summary, description, startTime, endTime } = schemas.toolInputs.updateEvent.parse(args);
226
+ // Get existing event
227
+ const existingEvent = await calendar.events.get({
228
+ calendarId: 'primary',
229
+ eventId,
230
+ });
231
+ // Prepare update payload
232
+ const updatePayload = {
233
+ summary: summary || existingEvent.data.summary,
234
+ description: description || existingEvent.data.description,
235
+ };
236
+ if (startTime) {
237
+ updatePayload.start = {
238
+ dateTime: startTime,
239
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
240
+ };
241
+ }
242
+ if (endTime) {
243
+ updatePayload.end = {
244
+ dateTime: endTime,
245
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
246
+ };
247
+ }
248
+ await calendar.events.update({
249
+ calendarId: 'primary',
250
+ eventId,
251
+ requestBody: updatePayload,
252
+ });
253
+ return {
254
+ content: [{
255
+ type: "text",
256
+ text: `Event ${eventId} updated successfully!`
257
+ }]
258
+ };
259
+ },
260
+ async delete_event(args) {
261
+ const { eventId } = schemas.toolInputs.deleteEvent.parse(args);
262
+ await calendar.events.delete({
263
+ calendarId: 'primary',
264
+ eventId,
265
+ });
266
+ return {
267
+ content: [{
268
+ type: "text",
269
+ text: `Event ${eventId} deleted successfully!`
270
+ }]
271
+ };
272
+ },
273
+ async find_free_time(args) {
274
+ const { timeMin, timeMax, duration } = schemas.toolInputs.findFreeTime.parse(args);
275
+ // Get existing events in the time range
276
+ const response = await calendar.events.list({
277
+ calendarId: 'primary',
278
+ timeMin,
279
+ timeMax,
280
+ singleEvents: true,
281
+ orderBy: 'startTime',
282
+ });
283
+ const events = response.data.items || [];
284
+ const freeTimes = [];
285
+ let currentTime = new Date(timeMin);
286
+ const endTime = new Date(timeMax);
287
+ const durationMs = duration * 60000; // Convert minutes to milliseconds
288
+ // Find free time slots
289
+ for (const event of events) {
290
+ const eventStart = new Date(event.start?.dateTime || event.start?.date || '');
291
+ // Check if there's enough time before the event
292
+ if (eventStart.getTime() - currentTime.getTime() >= durationMs) {
293
+ freeTimes.push({
294
+ start: currentTime.toISOString(),
295
+ end: new Date(eventStart.getTime() - 1).toISOString(),
296
+ });
297
+ }
298
+ currentTime = new Date(event.end?.dateTime || event.end?.date || '');
299
+ }
300
+ // Check for free time after the last event
301
+ if (endTime.getTime() - currentTime.getTime() >= durationMs) {
302
+ freeTimes.push({
303
+ start: currentTime.toISOString(),
304
+ end: endTime.toISOString(),
305
+ });
306
+ }
307
+ const formattedTimes = freeTimes.map(slot => `• ${new Date(slot.start).toLocaleString()} - ${new Date(slot.end).toLocaleString()}`).join('\n');
308
+ return {
309
+ content: [{
310
+ type: "text",
311
+ text: freeTimes.length ?
312
+ `Found ${freeTimes.length} available time slots:\n\n${formattedTimes}` :
313
+ `No available time slots found for duration of ${duration} minutes in the specified range.`
314
+ }]
315
+ };
316
+ },
317
+ };
318
+ // Initialize MCP server
319
+ const server = new Server({
320
+ name: "google-calendar-server",
321
+ version: "1.0.0",
322
+ }, {
323
+ capabilities: {
324
+ tools: {},
325
+ },
326
+ });
327
+ // Register tool handlers
328
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
329
+ console.error("Tools requested by client");
330
+ return { tools: TOOL_DEFINITIONS };
331
+ });
332
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
333
+ console.error("Tools requested by client");
334
+ console.error("Returning tools:", JSON.stringify(TOOL_DEFINITIONS, null, 2));
335
+ return { tools: TOOL_DEFINITIONS };
336
+ });
337
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
338
+ const { name, arguments: args } = request.params;
339
+ try {
340
+ const handler = toolHandlers[name];
341
+ if (!handler) {
342
+ throw new Error(`Unknown tool: ${name}`);
343
+ }
344
+ return await handler(args);
345
+ }
346
+ catch (error) {
347
+ console.error(`Error executing tool ${name}:`, error);
348
+ throw error;
349
+ }
350
+ });
351
+ // Start the server
352
+ async function main() {
353
+ try {
354
+ // Check for required environment variables (skip in test mode)
355
+ const isTestMode = process.env.MCP_TEST_MODE === 'true';
356
+ const requiredEnvVars = [
357
+ 'GOOGLE_CLIENT_ID',
358
+ 'GOOGLE_CLIENT_SECRET',
359
+ 'GOOGLE_REDIRECT_URI',
360
+ 'GOOGLE_REFRESH_TOKEN'
361
+ ];
362
+ const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
363
+ if (missingVars.length > 0 && !isTestMode) {
364
+ console.error(`Missing required environment variables: ${missingVars.join(', ')}`);
365
+ process.exit(1);
366
+ }
367
+ if (isTestMode) {
368
+ console.error("Running in test mode - skipping environment variable validation");
369
+ }
370
+ else {
371
+ console.error("Starting server with env vars:", {
372
+ clientId: process.env.GOOGLE_CLIENT_ID?.substring(0, 5) + '...',
373
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET?.substring(0, 5) + '...',
374
+ redirectUri: process.env.GOOGLE_REDIRECT_URI,
375
+ hasRefreshToken: !!process.env.GOOGLE_REFRESH_TOKEN
376
+ });
377
+ }
378
+ const transport = new StdioServerTransport();
379
+ console.error("Created transport");
380
+ await server.connect(transport);
381
+ console.error("Connected to transport");
382
+ console.error("Google Calendar MCP Server running on stdio");
383
+ }
384
+ catch (error) {
385
+ console.error("Startup error:", error);
386
+ process.exit(1);
387
+ }
388
+ }
389
+ main().catch((error) => {
390
+ console.error("Fatal error:", error);
391
+ process.exit(1);
392
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@iflow-mcp/v-3-google-calendar",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "bin": {
7
+ "iflow-mcp_v-3-google-calendar": "./build/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "description": "",
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.0.4",
19
+ "google-auth-library": "^9.15.0",
20
+ "googleapis": "^144.0.0",
21
+ "open": "^10.1.0",
22
+ "zod": "^3.24.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.10.2",
26
+ "typescript": "^5.7.2"
27
+ },
28
+ "files": [
29
+ "build"
30
+ ]
31
+ }