google-calendar-workspace-mcp-server 0.0.1

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 (38) hide show
  1. package/README.md +262 -0
  2. package/build/index.d.ts +2 -0
  3. package/build/index.integration-with-mock.d.ts +6 -0
  4. package/build/index.integration-with-mock.js +195 -0
  5. package/build/index.js +35 -0
  6. package/package.json +49 -0
  7. package/shared/calendar-client/lib/api-errors.d.ts +4 -0
  8. package/shared/calendar-client/lib/api-errors.js +25 -0
  9. package/shared/calendar-client/lib/create-event.d.ts +2 -0
  10. package/shared/calendar-client/lib/create-event.js +13 -0
  11. package/shared/calendar-client/lib/get-event.d.ts +2 -0
  12. package/shared/calendar-client/lib/get-event.js +9 -0
  13. package/shared/calendar-client/lib/list-calendars.d.ts +5 -0
  14. package/shared/calendar-client/lib/list-calendars.js +14 -0
  15. package/shared/calendar-client/lib/list-events.d.ts +10 -0
  16. package/shared/calendar-client/lib/list-events.js +24 -0
  17. package/shared/calendar-client/lib/query-freebusy.d.ts +2 -0
  18. package/shared/calendar-client/lib/query-freebusy.js +13 -0
  19. package/shared/index.d.ts +2 -0
  20. package/shared/index.js +2 -0
  21. package/shared/logging.d.ts +10 -0
  22. package/shared/logging.js +23 -0
  23. package/shared/server.d.ts +130 -0
  24. package/shared/server.js +132 -0
  25. package/shared/tools/create-event.d.ts +110 -0
  26. package/shared/tools/create-event.js +195 -0
  27. package/shared/tools/get-event.d.ts +44 -0
  28. package/shared/tools/get-event.js +154 -0
  29. package/shared/tools/list-calendars.d.ts +36 -0
  30. package/shared/tools/list-calendars.js +88 -0
  31. package/shared/tools/list-events.d.ts +79 -0
  32. package/shared/tools/list-events.js +176 -0
  33. package/shared/tools/query-freebusy.d.ts +61 -0
  34. package/shared/tools/query-freebusy.js +110 -0
  35. package/shared/tools.d.ts +3 -0
  36. package/shared/tools.js +41 -0
  37. package/shared/types.d.ts +111 -0
  38. package/shared/types.js +4 -0
@@ -0,0 +1,154 @@
1
+ import { z } from 'zod';
2
+ import { logError } from '../logging.js';
3
+ export const GetEventSchema = z.object({
4
+ calendar_id: z
5
+ .string()
6
+ .optional()
7
+ .default('primary')
8
+ .describe('Calendar ID containing the event. Defaults to "primary".'),
9
+ event_id: z.string().min(1).describe('The ID of the event to retrieve.'),
10
+ });
11
+ export function getEventTool(server, clientFactory) {
12
+ return {
13
+ name: 'gcal_get_event',
14
+ description: 'Retrieves detailed information about a specific calendar event by ID. ' +
15
+ 'Returns full event details including title, time, location, attendees, description, recurrence rules, and reminders. ' +
16
+ 'Use this when you need complete information about a particular event.',
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ calendar_id: {
21
+ type: 'string',
22
+ description: GetEventSchema.shape.calendar_id.description,
23
+ },
24
+ event_id: {
25
+ type: 'string',
26
+ description: GetEventSchema.shape.event_id.description,
27
+ },
28
+ },
29
+ required: ['event_id'],
30
+ },
31
+ handler: async (args) => {
32
+ try {
33
+ const parsed = GetEventSchema.parse(args);
34
+ const client = clientFactory();
35
+ const event = await client.getEvent(parsed.calendar_id, parsed.event_id);
36
+ let output = `# Event Details\n\n`;
37
+ output += `## ${event.summary || '(No title)'}\n\n`;
38
+ output += `**Event ID:** ${event.id}\n`;
39
+ // Status
40
+ if (event.status) {
41
+ output += `**Status:** ${event.status}\n`;
42
+ }
43
+ // Start time
44
+ if (event.start?.dateTime) {
45
+ output += `**Start:** ${new Date(event.start.dateTime).toLocaleString()}`;
46
+ if (event.start.timeZone) {
47
+ output += ` (${event.start.timeZone})`;
48
+ }
49
+ output += '\n';
50
+ }
51
+ else if (event.start?.date) {
52
+ output += `**Start:** ${event.start.date} (All day)\n`;
53
+ }
54
+ // End time
55
+ if (event.end?.dateTime) {
56
+ output += `**End:** ${new Date(event.end.dateTime).toLocaleString()}`;
57
+ if (event.end.timeZone) {
58
+ output += ` (${event.end.timeZone})`;
59
+ }
60
+ output += '\n';
61
+ }
62
+ else if (event.end?.date) {
63
+ output += `**End:** ${event.end.date} (All day)\n`;
64
+ }
65
+ // Location
66
+ if (event.location) {
67
+ output += `**Location:** ${event.location}\n`;
68
+ }
69
+ // Creator
70
+ if (event.creator) {
71
+ output += `**Created By:** ${event.creator.displayName || event.creator.email}\n`;
72
+ }
73
+ // Organizer
74
+ if (event.organizer) {
75
+ output += `**Organizer:** ${event.organizer.displayName || event.organizer.email}\n`;
76
+ }
77
+ // Attendees
78
+ if (event.attendees && event.attendees.length > 0) {
79
+ output += `\n### Attendees (${event.attendees.length})\n\n`;
80
+ for (const attendee of event.attendees) {
81
+ const name = attendee.displayName || attendee.email;
82
+ const status = attendee.responseStatus || 'needsAction';
83
+ const optional = attendee.organizer ? ' [Organizer]' : '';
84
+ output += `- ${name} - **${status}**${optional}\n`;
85
+ }
86
+ output += '\n';
87
+ }
88
+ // Description
89
+ if (event.description) {
90
+ output += `### Description\n\n${event.description}\n\n`;
91
+ }
92
+ // Recurrence
93
+ if (event.recurrence && event.recurrence.length > 0) {
94
+ output += `### Recurrence\n\n`;
95
+ for (const rule of event.recurrence) {
96
+ output += `- ${rule}\n`;
97
+ }
98
+ output += '\n';
99
+ }
100
+ // Reminders
101
+ if (event.reminders) {
102
+ output += `### Reminders\n\n`;
103
+ if (event.reminders.useDefault) {
104
+ output += `Using default reminders\n\n`;
105
+ }
106
+ else if (event.reminders.overrides && event.reminders.overrides.length > 0) {
107
+ for (const reminder of event.reminders.overrides) {
108
+ output += `- ${reminder.method}: ${reminder.minutes} minutes before\n`;
109
+ }
110
+ output += '\n';
111
+ }
112
+ }
113
+ // Visibility and transparency
114
+ if (event.visibility) {
115
+ output += `**Visibility:** ${event.visibility}\n`;
116
+ }
117
+ if (event.transparency) {
118
+ output += `**Show as:** ${event.transparency}\n`;
119
+ }
120
+ // Metadata
121
+ if (event.created) {
122
+ output += `**Created:** ${new Date(event.created).toLocaleString()}\n`;
123
+ }
124
+ if (event.updated) {
125
+ output += `**Updated:** ${new Date(event.updated).toLocaleString()}\n`;
126
+ }
127
+ // Link
128
+ if (event.htmlLink) {
129
+ output += `\n**Event Link:** ${event.htmlLink}\n`;
130
+ }
131
+ return {
132
+ content: [
133
+ {
134
+ type: 'text',
135
+ text: output,
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ catch (error) {
141
+ logError('get-event-tool', error);
142
+ return {
143
+ content: [
144
+ {
145
+ type: 'text',
146
+ text: `Error getting event: ${error instanceof Error ? error.message : String(error)}`,
147
+ },
148
+ ],
149
+ isError: true,
150
+ };
151
+ }
152
+ },
153
+ };
154
+ }
@@ -0,0 +1,36 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { z } from 'zod';
3
+ import type { ClientFactory } from '../server.js';
4
+ export declare const ListCalendarsSchema: z.ZodObject<{
5
+ max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ max_results: number;
8
+ }, {
9
+ max_results?: number | undefined;
10
+ }>;
11
+ export declare function listCalendarsTool(server: Server, clientFactory: ClientFactory): {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: {
15
+ type: "object";
16
+ properties: {
17
+ max_results: {
18
+ type: string;
19
+ description: string | undefined;
20
+ };
21
+ };
22
+ };
23
+ handler: (args: unknown) => Promise<{
24
+ content: {
25
+ type: string;
26
+ text: string;
27
+ }[];
28
+ isError?: undefined;
29
+ } | {
30
+ content: {
31
+ type: string;
32
+ text: string;
33
+ }[];
34
+ isError: boolean;
35
+ }>;
36
+ };
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+ import { logError } from '../logging.js';
3
+ export const ListCalendarsSchema = z.object({
4
+ max_results: z
5
+ .number()
6
+ .positive()
7
+ .max(250)
8
+ .optional()
9
+ .default(50)
10
+ .describe('Maximum number of calendars to return. Defaults to 50, maximum is 250.'),
11
+ });
12
+ export function listCalendarsTool(server, clientFactory) {
13
+ return {
14
+ name: 'gcal_list_calendars',
15
+ description: 'Lists all calendars available to the authenticated user. ' +
16
+ 'Returns calendar details including ID, name, description, time zone, and access role. ' +
17
+ 'Use this to discover available calendars before querying events, or to see which calendars the user has access to.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ max_results: {
22
+ type: 'number',
23
+ description: ListCalendarsSchema.shape.max_results.description,
24
+ },
25
+ },
26
+ },
27
+ handler: async (args) => {
28
+ try {
29
+ const parsed = ListCalendarsSchema.parse(args);
30
+ const client = clientFactory();
31
+ const result = await client.listCalendars({
32
+ maxResults: parsed.max_results,
33
+ });
34
+ const calendars = result.items || [];
35
+ if (calendars.length === 0) {
36
+ return {
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text: 'No calendars found.',
41
+ },
42
+ ],
43
+ };
44
+ }
45
+ let output = `# Available Calendars (${calendars.length} found)\n\n`;
46
+ for (const calendar of calendars) {
47
+ output += `## ${calendar.summary}\n\n`;
48
+ output += `**Calendar ID:** ${calendar.id}\n`;
49
+ if (calendar.description) {
50
+ output += `**Description:** ${calendar.description}\n`;
51
+ }
52
+ output += `**Time Zone:** ${calendar.timeZone}\n`;
53
+ output += `**Access Role:** ${calendar.accessRole}\n`;
54
+ if (calendar.primary) {
55
+ output += `**Primary:** Yes\n`;
56
+ }
57
+ if (calendar.selected !== undefined) {
58
+ output += `**Selected:** ${calendar.selected ? 'Yes' : 'No'}\n`;
59
+ }
60
+ if (calendar.backgroundColor) {
61
+ output += `**Background Color:** ${calendar.backgroundColor}\n`;
62
+ }
63
+ output += '\n---\n\n';
64
+ }
65
+ return {
66
+ content: [
67
+ {
68
+ type: 'text',
69
+ text: output,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ catch (error) {
75
+ logError('list-calendars-tool', error);
76
+ return {
77
+ content: [
78
+ {
79
+ type: 'text',
80
+ text: `Error listing calendars: ${error instanceof Error ? error.message : String(error)}`,
81
+ },
82
+ ],
83
+ isError: true,
84
+ };
85
+ }
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,79 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { z } from 'zod';
3
+ import type { ClientFactory } from '../server.js';
4
+ export declare const ListEventsSchema: z.ZodObject<{
5
+ calendar_id: z.ZodDefault<z.ZodOptional<z.ZodString>>;
6
+ time_min: z.ZodOptional<z.ZodString>;
7
+ time_max: z.ZodOptional<z.ZodString>;
8
+ max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
9
+ query: z.ZodOptional<z.ZodString>;
10
+ single_events: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
11
+ order_by: z.ZodOptional<z.ZodEnum<["startTime", "updated"]>>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ calendar_id: string;
14
+ max_results: number;
15
+ single_events: boolean;
16
+ time_min?: string | undefined;
17
+ time_max?: string | undefined;
18
+ query?: string | undefined;
19
+ order_by?: "startTime" | "updated" | undefined;
20
+ }, {
21
+ calendar_id?: string | undefined;
22
+ time_min?: string | undefined;
23
+ time_max?: string | undefined;
24
+ max_results?: number | undefined;
25
+ query?: string | undefined;
26
+ single_events?: boolean | undefined;
27
+ order_by?: "startTime" | "updated" | undefined;
28
+ }>;
29
+ export declare function listEventsTool(server: Server, clientFactory: ClientFactory): {
30
+ name: string;
31
+ description: string;
32
+ inputSchema: {
33
+ type: "object";
34
+ properties: {
35
+ calendar_id: {
36
+ type: string;
37
+ description: string | undefined;
38
+ };
39
+ time_min: {
40
+ type: string;
41
+ description: string | undefined;
42
+ };
43
+ time_max: {
44
+ type: string;
45
+ description: string | undefined;
46
+ };
47
+ max_results: {
48
+ type: string;
49
+ description: string | undefined;
50
+ };
51
+ query: {
52
+ type: string;
53
+ description: string | undefined;
54
+ };
55
+ single_events: {
56
+ type: string;
57
+ description: string | undefined;
58
+ };
59
+ order_by: {
60
+ type: string;
61
+ enum: string[];
62
+ description: string | undefined;
63
+ };
64
+ };
65
+ };
66
+ handler: (args: unknown) => Promise<{
67
+ content: {
68
+ type: string;
69
+ text: string;
70
+ }[];
71
+ isError?: undefined;
72
+ } | {
73
+ content: {
74
+ type: string;
75
+ text: string;
76
+ }[];
77
+ isError: boolean;
78
+ }>;
79
+ };
@@ -0,0 +1,176 @@
1
+ import { z } from 'zod';
2
+ import { logError } from '../logging.js';
3
+ export const ListEventsSchema = z.object({
4
+ calendar_id: z
5
+ .string()
6
+ .optional()
7
+ .default('primary')
8
+ .describe('Calendar ID to list events from. Defaults to "primary" (user\'s primary calendar).'),
9
+ time_min: z
10
+ .string()
11
+ .optional()
12
+ .describe('Lower bound (inclusive) for event start time in RFC3339 format (e.g., "2024-01-01T00:00:00Z"). Defaults to current time if not specified.'),
13
+ time_max: z
14
+ .string()
15
+ .optional()
16
+ .describe('Upper bound (exclusive) for event start time in RFC3339 format (e.g., "2024-12-31T23:59:59Z").'),
17
+ max_results: z
18
+ .number()
19
+ .positive()
20
+ .max(250)
21
+ .optional()
22
+ .default(10)
23
+ .describe('Maximum number of events to return. Defaults to 10, maximum is 250.'),
24
+ query: z.string().optional().describe('Free text search query to filter events.'),
25
+ single_events: z
26
+ .boolean()
27
+ .optional()
28
+ .default(true)
29
+ .describe('Whether to expand recurring events into instances. Defaults to true to show individual occurrences.'),
30
+ order_by: z
31
+ .enum(['startTime', 'updated'])
32
+ .optional()
33
+ .describe('Order of events. "startTime" orders by start time (requires single_events=true), "updated" orders by last modification time.'),
34
+ });
35
+ export function listEventsTool(server, clientFactory) {
36
+ return {
37
+ name: 'gcal_list_events',
38
+ description: 'Lists events from a Google Calendar within an optional time range. ' +
39
+ 'Returns event details including title, time, location, attendees, and description. ' +
40
+ 'Useful for checking upcoming meetings, finding events by search query, or reviewing a specific time period. ' +
41
+ 'By default, shows the next 10 events from the primary calendar.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ calendar_id: {
46
+ type: 'string',
47
+ description: ListEventsSchema.shape.calendar_id.description,
48
+ },
49
+ time_min: {
50
+ type: 'string',
51
+ description: ListEventsSchema.shape.time_min.description,
52
+ },
53
+ time_max: {
54
+ type: 'string',
55
+ description: ListEventsSchema.shape.time_max.description,
56
+ },
57
+ max_results: {
58
+ type: 'number',
59
+ description: ListEventsSchema.shape.max_results.description,
60
+ },
61
+ query: {
62
+ type: 'string',
63
+ description: ListEventsSchema.shape.query.description,
64
+ },
65
+ single_events: {
66
+ type: 'boolean',
67
+ description: ListEventsSchema.shape.single_events.description,
68
+ },
69
+ order_by: {
70
+ type: 'string',
71
+ enum: ['startTime', 'updated'],
72
+ description: ListEventsSchema.shape.order_by.description,
73
+ },
74
+ },
75
+ },
76
+ handler: async (args) => {
77
+ try {
78
+ const parsed = ListEventsSchema.parse(args);
79
+ const client = clientFactory();
80
+ const result = await client.listEvents(parsed.calendar_id, {
81
+ timeMin: parsed.time_min,
82
+ timeMax: parsed.time_max,
83
+ maxResults: parsed.max_results,
84
+ q: parsed.query,
85
+ singleEvents: parsed.single_events,
86
+ orderBy: parsed.order_by,
87
+ });
88
+ const events = result.items || [];
89
+ if (events.length === 0) {
90
+ return {
91
+ content: [
92
+ {
93
+ type: 'text',
94
+ text: 'No events found matching the criteria.',
95
+ },
96
+ ],
97
+ };
98
+ }
99
+ let output = `# Calendar Events (${events.length} found)\n\n`;
100
+ output += `**Calendar:** ${result.summary}\n`;
101
+ output += `**Time Zone:** ${result.timeZone}\n\n`;
102
+ for (const event of events) {
103
+ output += `## ${event.summary || '(No title)'}\n\n`;
104
+ output += `**Event ID:** ${event.id}\n`;
105
+ // Start time
106
+ if (event.start?.dateTime) {
107
+ output += `**Start:** ${new Date(event.start.dateTime).toLocaleString()}\n`;
108
+ }
109
+ else if (event.start?.date) {
110
+ output += `**Start:** ${event.start.date} (All day)\n`;
111
+ }
112
+ // End time
113
+ if (event.end?.dateTime) {
114
+ output += `**End:** ${new Date(event.end.dateTime).toLocaleString()}\n`;
115
+ }
116
+ else if (event.end?.date) {
117
+ output += `**End:** ${event.end.date} (All day)\n`;
118
+ }
119
+ // Location
120
+ if (event.location) {
121
+ output += `**Location:** ${event.location}\n`;
122
+ }
123
+ // Status
124
+ if (event.status) {
125
+ output += `**Status:** ${event.status}\n`;
126
+ }
127
+ // Organizer
128
+ if (event.organizer) {
129
+ output += `**Organizer:** ${event.organizer.displayName || event.organizer.email}\n`;
130
+ }
131
+ // Attendees
132
+ if (event.attendees && event.attendees.length > 0) {
133
+ output += `**Attendees:** ${event.attendees.length}\n`;
134
+ for (const attendee of event.attendees) {
135
+ const name = attendee.displayName || attendee.email;
136
+ const status = attendee.responseStatus || 'needsAction';
137
+ output += ` - ${name} (${status})\n`;
138
+ }
139
+ }
140
+ // Description
141
+ if (event.description) {
142
+ const truncated = event.description.length > 200
143
+ ? event.description.substring(0, 200) + '...'
144
+ : event.description;
145
+ output += `**Description:** ${truncated}\n`;
146
+ }
147
+ // Link
148
+ if (event.htmlLink) {
149
+ output += `**Link:** ${event.htmlLink}\n`;
150
+ }
151
+ output += '\n---\n\n';
152
+ }
153
+ return {
154
+ content: [
155
+ {
156
+ type: 'text',
157
+ text: output,
158
+ },
159
+ ],
160
+ };
161
+ }
162
+ catch (error) {
163
+ logError('list-events-tool', error);
164
+ return {
165
+ content: [
166
+ {
167
+ type: 'text',
168
+ text: `Error listing events: ${error instanceof Error ? error.message : String(error)}`,
169
+ },
170
+ ],
171
+ isError: true,
172
+ };
173
+ }
174
+ },
175
+ };
176
+ }
@@ -0,0 +1,61 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { z } from 'zod';
3
+ import type { ClientFactory } from '../server.js';
4
+ export declare const QueryFreebusySchema: z.ZodObject<{
5
+ time_min: z.ZodString;
6
+ time_max: z.ZodString;
7
+ calendar_ids: z.ZodArray<z.ZodString, "many">;
8
+ timezone: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ time_min: string;
11
+ time_max: string;
12
+ calendar_ids: string[];
13
+ timezone?: string | undefined;
14
+ }, {
15
+ time_min: string;
16
+ time_max: string;
17
+ calendar_ids: string[];
18
+ timezone?: string | undefined;
19
+ }>;
20
+ export declare function queryFreebusyTool(server: Server, clientFactory: ClientFactory): {
21
+ name: string;
22
+ description: string;
23
+ inputSchema: {
24
+ type: "object";
25
+ properties: {
26
+ time_min: {
27
+ type: string;
28
+ description: string | undefined;
29
+ };
30
+ time_max: {
31
+ type: string;
32
+ description: string | undefined;
33
+ };
34
+ calendar_ids: {
35
+ type: string;
36
+ items: {
37
+ type: string;
38
+ };
39
+ description: string | undefined;
40
+ };
41
+ timezone: {
42
+ type: string;
43
+ description: string | undefined;
44
+ };
45
+ };
46
+ required: string[];
47
+ };
48
+ handler: (args: unknown) => Promise<{
49
+ content: {
50
+ type: string;
51
+ text: string;
52
+ }[];
53
+ isError?: undefined;
54
+ } | {
55
+ content: {
56
+ type: string;
57
+ text: string;
58
+ }[];
59
+ isError: boolean;
60
+ }>;
61
+ };