# Directory Structure ``` ├── .env ├── .gitignore ├── docs │ └── Demo-on-Dive-Desktop.png ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── calendar-tools │ │ ├── _index.ts │ │ ├── calendarTools.ts │ │ ├── createEvent.ts │ │ ├── deleteEvent.ts │ │ ├── listEvents.ts │ │ └── updateEvent.ts │ ├── index.ts │ └── utils │ ├── auth.ts │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- ``` ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` dist/ node_modules/ credentials.json ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Calendar Tools MCP Server A powerful Model Context Protocol (MCP) server providing comprehensive calendar management capabilities. ## Features ### Calendar Management - Create calendar events - List calendar events - Update existing events - Delete events ## Demo on Dive Desktop  ## Installation ### Manual Installation ```bash npm install -g @cablate/mcp-google-calendar ``` ## Usage ### Cli ```bash mcp-google-calendar ``` ### With [Dive Desktop](https://github.com/OpenAgentPlatform/Dive) 1. Click "+ Add MCP Server" in Dive Desktop 2. Copy and paste this configuration: ```json { "mcpServers": { "calendar": { "command": "npx", "args": ["-y", "@cablate/mcp-google-calendar"], "env": { "GOOGLE_CALENDAR_ID": "your_calendar_id", "GOOGLE_TIME_ZONE": "your_time_zone", "GOOGLE_CREDENTIALS_PATH": "your_credentials_path" }, "enabled": true } } } ``` 3. Click "Save" to install the MCP server ## Google Service Account and Credentials Here is the simple steps to create a google service account and credentials: 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 2. Create a new project or select an existing project 3. Navigate to the "IAM & Admin" section 4. Click on "Service Accounts" 5. Click on "Create Service Account" 6. Enter a name for the service account (e.g., "MCP Google Calendar") 7. Click on "Create" 8. Click on "Create Key" 9. Select "JSON" as the key type 10. Click on "Create" 11. Download the JSON file and save it as `credentials.json` if still got any question, google and find the answer. ## License MIT ## Contributing Welcome community participation and contributions! Here are ways to contribute: - ⭐️ Star the project if you find it helpful - 🐛 Submit Issues: Report problems or provide suggestions - 🔧 Create Pull Requests: Submit code improvements ## Contact If you have any questions or suggestions, feel free to reach out: - 📧 Email: [[email protected]](mailto:[email protected]) - 📧 GitHub: [CabLate](https://github.com/cablate/) - 🤝 Collaboration: Welcome to discuss project cooperation - 📚 Technical Guidance: Sincere welcome for suggestions and guidance ``` -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- ```typescript export function convertToTimeZone(dateTime: string, timeZone: string): string { return new Date( new Date(dateTime).toLocaleString('en-US', { timeZone: timeZone }) ).toISOString(); } ``` -------------------------------------------------------------------------------- /src/calendar-tools/_index.ts: -------------------------------------------------------------------------------- ```typescript import { CREATE_EVENT_TOOL, DELETE_EVENT_TOOL, LIST_EVENTS_TOOL, UPDATE_EVENT_TOOL } from "./calendarTools.js"; export const tools = [CREATE_EVENT_TOOL, LIST_EVENTS_TOOL, UPDATE_EVENT_TOOL, DELETE_EVENT_TOOL]; export * from "./calendarTools.js"; export * from "./createEvent.js"; export * from "./deleteEvent.js"; export * from "./listEvents.js"; export * from "./updateEvent.js"; ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "outDir": "./dist", "rootDir": "./src", "moduleResolution": "NodeNext", "module": "NodeNext", "noImplicitAny": false }, "exclude": ["node_modules"], "include": ["src/**/*"] } ``` -------------------------------------------------------------------------------- /src/calendar-tools/deleteEvent.ts: -------------------------------------------------------------------------------- ```typescript import { google } from "googleapis"; import { getAuthClient } from "../utils/auth.js"; export async function deleteEvent(eventId: string) { try { const auth = await getAuthClient(); const calendar = google.calendar({ version: "v3", auth }); await calendar.events.delete({ calendarId: process.env.GOOGLE_CALENDAR_ID || "primary", eventId: eventId, sendUpdates: "all", }); return { success: true, data: "Event successfully deleted", }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Error deleting event", }; } } ``` -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- ```typescript import { promises as fs } from "fs"; import { JWT } from "google-auth-library"; import path from "path"; const SCOPES = ["https://www.googleapis.com/auth/calendar"]; const CREDENTIALS_PATH = process.env.GOOGLE_CREDENTIALS_PATH || path.join(process.cwd(), "credentials.json"); export async function getAuthClient(): Promise<JWT> { try { const content = await fs.readFile(CREDENTIALS_PATH); const credentials = JSON.parse(content.toString()); const client = new JWT({ email: credentials.client_email, key: credentials.private_key, scopes: SCOPES, }); return client; } catch (error) { console.error("Authentication error:", error); throw error; } } ``` -------------------------------------------------------------------------------- /src/calendar-tools/listEvents.ts: -------------------------------------------------------------------------------- ```typescript import { calendar_v3, google } from "googleapis"; import { getAuthClient } from "../utils/auth.js"; import { convertToTimeZone } from "../utils/index.js"; interface ListEventsParams { timeMin: string; maxResults: number; } interface ListEventsResult { success: boolean; error?: string; data?: calendar_v3.Schema$Event[]; } export async function listEvents(params: ListEventsParams): Promise<ListEventsResult> { try { const auth = await getAuthClient(); const calendar = google.calendar({ version: "v3", auth }); const result = await calendar.events.list({ calendarId: process.env.GOOGLE_CALENDAR_ID || "primary", timeMin: convertToTimeZone(params.timeMin, process.env.GOOGLE_TIME_ZONE || "Asia/Taipei"), maxResults: params.maxResults, singleEvents: true, orderBy: "startTime", }); return { success: true, data: result.data?.items || [], }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Error getting event list", }; } } ``` -------------------------------------------------------------------------------- /src/calendar-tools/createEvent.ts: -------------------------------------------------------------------------------- ```typescript import { calendar_v3, google } from "googleapis"; import { getAuthClient } from "../utils/auth.js"; interface CreateEventParams { summary: string; description?: string; startTime: string; endTime: string; attendees?: string[]; } interface CreateEventResult { success: boolean; error?: string; data?: calendar_v3.Schema$Event; } export async function createEvent(params: CreateEventParams): Promise<CreateEventResult> { try { const auth = await getAuthClient(); const calendar = google.calendar({ version: "v3", auth }); const event = { summary: params.summary, description: params.description, start: { dateTime: params.startTime, timeZone: process.env.GOOGLE_TIME_ZONE || "Etc/UTC", }, end: { dateTime: params.endTime, timeZone: process.env.GOOGLE_TIME_ZONE || "Etc/UTC", }, attendees: params.attendees?.map((email) => ({ email })), }; const result = await calendar.events.insert({ calendarId: process.env.GOOGLE_CALENDAR_ID || "primary", requestBody: event, sendUpdates: "all", }); return { success: true, data: result.data || undefined, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Error creating event", }; } } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "@cablate/mcp-google-calendar", "version": "0.0.3", "type": "module", "description": "MCP server that provides google calendar capabilities", "main": "dist/index.cjs", "license": "MIT", "scripts": { "build": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.cjs --external:pdfreader --external:jsdom --external:mammoth --external:csv-parse --external:libreoffice-convert && shx chmod +x dist/index.cjs", "dev": "ts-node src/index.ts", "start": "node dist/index.cjs" }, "dependencies": { "@google-cloud/local-auth": "^2.1.0", "@modelcontextprotocol/sdk": "^1.5.0", "esbuild": "^0.25.0", "googleapis": "^105.0.0", "shx": "^0.3.4", "typescript": "^5.3.3" }, "devDependencies": { "@types/node": "^20.11.16", "ts-node": "^10.9.2" }, "author": "CabLate", "files": [ "dist", "dist/**/*.map", "README.md" ], "bin": { "mcp-doc-forge": "./dist/index.cjs" }, "keywords": [ "mcp", "mcp-server", "google-calendar", "google-api", "calendar", "ai", "dive" ], "homepage": "https://github.com/cablate/mcp-google-calendar#readme", "repository": { "type": "git", "url": "git+https://github.com/cablate/mcp-google-calendar.git" }, "bugs": { "url": "https://github.com/cablate/mcp-google-calendar/issues" } } ``` -------------------------------------------------------------------------------- /src/calendar-tools/updateEvent.ts: -------------------------------------------------------------------------------- ```typescript import { google } from "googleapis"; import { getAuthClient } from "../utils/auth.js"; import { convertToTimeZone } from "../utils/index.js"; interface UpdateEventParams { summary?: string; description?: string; startTime?: string; endTime?: string; attendees?: string[]; } export async function updateEvent(eventId: string, updates: UpdateEventParams) { try { const auth = await getAuthClient(); const calendar = google.calendar({ version: "v3", auth }); // 先取得現有活動資料 const event = await calendar.events.get({ calendarId: process.env.GOOGLE_CALENDAR_ID || "primary", eventId: eventId, }); // 準備更新的資料 const updatedEvent = { ...event.data, summary: updates.summary || event.data.summary, description: updates.description || event.data.description, start: updates.startTime ? { dateTime: convertToTimeZone(updates.startTime, process.env.GOOGLE_TIME_ZONE || "Asia/Taipei"), timeZone: process.env.GOOGLE_TIME_ZONE || "Asia/Taipei", } : event.data.start, end: updates.endTime ? { dateTime: convertToTimeZone(updates.endTime, process.env.GOOGLE_TIME_ZONE || "Asia/Taipei"), timeZone: process.env.GOOGLE_TIME_ZONE || "Asia/Taipei", } : event.data.end, attendees: updates.attendees ? updates.attendees.map((email) => ({ email })) : event.data.attendees, }; const result = await calendar.events.update({ calendarId: process.env.GOOGLE_CALENDAR_ID || "primary", eventId: eventId, requestBody: updatedEvent, sendUpdates: "all", }); return { success: true, data: result.data, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Error updating event", }; } } ``` -------------------------------------------------------------------------------- /src/calendar-tools/calendarTools.ts: -------------------------------------------------------------------------------- ```typescript export const CREATE_EVENT_TOOL = { name: "create_event", description: "Create a new Google Calendar event", inputSchema: { type: "object", properties: { summary: { type: "string", description: "Event title", }, description: { type: "string", description: "Event description", }, startTime: { type: "string", description: "Event start time (ISO format)", }, endTime: { type: "string", description: "Event end time (ISO format)", }, attendees: { type: "array", items: { type: "string", }, description: "List of attendee email addresses", }, }, required: ["summary", "startTime", "endTime"], }, }; export const LIST_EVENTS_TOOL = { name: "list_events", description: "List Google Calendar events", inputSchema: { type: "object", properties: { timeMin: { type: "string", description: "Start time (ISO format)", }, maxResults: { type: "number", description: "Maximum number of results", }, }, }, }; export const UPDATE_EVENT_TOOL = { name: "update_event", description: "Update an existing Google Calendar event", inputSchema: { type: "object", properties: { eventId: { type: "string", description: "ID of the event to update", }, updates: { type: "object", properties: { summary: { type: "string", description: "New event title", }, description: { type: "string", description: "New event description", }, startTime: { type: "string", description: "New start time", }, endTime: { type: "string", description: "New end time", }, attendees: { type: "array", items: { type: "string", }, description: "New list of attendees", }, }, }, }, required: ["eventId", "updates"], }, }; export const DELETE_EVENT_TOOL = { name: "delete_event", description: "Delete a Google Calendar event", inputSchema: { type: "object", properties: { eventId: { type: "string", description: "ID of the event to delete", }, }, required: ["eventId"], }, }; ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { calendar_v3 } from "googleapis"; import { createEvent, deleteEvent, listEvents, tools, updateEvent } from "./calendar-tools/_index.js"; import { getAuthClient } from "./utils/auth.js"; const server = new Server( { name: "mcp-server/calendar_executor", version: "0.0.1", }, { capabilities: { description: "An MCP server providing Google Calendar integration!", tools: {}, }, } ); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools, })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new Error("No parameters provided"); } if (name === "create_event") { const { summary, description, startTime, endTime, attendees } = args as { summary: string; description: string; startTime: string; endTime: string; attendees?: string[]; }; const result = await createEvent({ summary, description, startTime, endTime, attendees, }); if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true, }; } return { content: [{ type: "text", text: `Successfully created event: ${result.data?.summary || ""}` }], isError: false, }; } if (name === "list_events") { const { timeMin, maxResults } = args as { timeMin?: string; maxResults?: number; }; const result = await listEvents({ timeMin: timeMin || new Date().toISOString(), maxResults: maxResults || 10, }); if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true, }; } return { content: [{ type: "text", text: formatEventsList(result.data || []) }], isError: false, }; } if (name === "update_event") { const { eventId, updates } = args as { eventId: string; updates: { summary?: string; description?: string; startTime?: string; endTime?: string; attendees?: string[]; }; }; const result = await updateEvent(eventId, updates); if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true, }; } return { content: [{ type: "text", text: `Successfully updated event: ${result.data?.summary || ""}` }], isError: false, }; } if (name === "delete_event") { const { eventId } = args as { eventId: string; }; const result = await deleteEvent(eventId); if (!result.success) { return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true, }; } return { content: [{ type: "text", text: "Successfully deleted event" }], isError: false, }; } return { content: [{ type: "text", text: `Error: ${name} is an unknown tool` }], isError: true, }; } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); async function runServer() { try { // 先初始化認證 await getAuthClient(); const transport = new StdioServerTransport(); await server.connect(transport); console.log("MCP Calendar Server started"); } catch (error) { console.error("Server startup failed:", error); process.exit(1); } } runServer().catch((error) => { console.error("Server encountered a critical error:", error); process.exit(1); }); function formatEventsList(events: calendar_v3.Schema$Event[]): string { return events .map((event) => { return ` ID: ${event.id} Event: ${event.summary} Description: ${event.description || "None"} Start Time: ${new Date(event.start?.dateTime || "").toLocaleString()} End Time: ${new Date(event.end?.dateTime || "").toLocaleString()} Attendees: ${event.attendees?.map((a: calendar_v3.Schema$EventAttendee) => a.email).join(", ") || "None"} `.trim(); }) .join("\n\n"); } ```