#
tokens: 4557/50000 14/14 files
lines: off (toggle) GitHub
raw markdown copy
# 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

![Calendar Tools Demo](docs/Demo-on-Dive-Desktop.png)

## 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");
}

```