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.
- package/README.md +262 -0
- package/build/index.d.ts +2 -0
- package/build/index.integration-with-mock.d.ts +6 -0
- package/build/index.integration-with-mock.js +195 -0
- package/build/index.js +35 -0
- package/package.json +49 -0
- package/shared/calendar-client/lib/api-errors.d.ts +4 -0
- package/shared/calendar-client/lib/api-errors.js +25 -0
- package/shared/calendar-client/lib/create-event.d.ts +2 -0
- package/shared/calendar-client/lib/create-event.js +13 -0
- package/shared/calendar-client/lib/get-event.d.ts +2 -0
- package/shared/calendar-client/lib/get-event.js +9 -0
- package/shared/calendar-client/lib/list-calendars.d.ts +5 -0
- package/shared/calendar-client/lib/list-calendars.js +14 -0
- package/shared/calendar-client/lib/list-events.d.ts +10 -0
- package/shared/calendar-client/lib/list-events.js +24 -0
- package/shared/calendar-client/lib/query-freebusy.d.ts +2 -0
- package/shared/calendar-client/lib/query-freebusy.js +13 -0
- package/shared/index.d.ts +2 -0
- package/shared/index.js +2 -0
- package/shared/logging.d.ts +10 -0
- package/shared/logging.js +23 -0
- package/shared/server.d.ts +130 -0
- package/shared/server.js +132 -0
- package/shared/tools/create-event.d.ts +110 -0
- package/shared/tools/create-event.js +195 -0
- package/shared/tools/get-event.d.ts +44 -0
- package/shared/tools/get-event.js +154 -0
- package/shared/tools/list-calendars.d.ts +36 -0
- package/shared/tools/list-calendars.js +88 -0
- package/shared/tools/list-events.d.ts +79 -0
- package/shared/tools/list-events.js +176 -0
- package/shared/tools/query-freebusy.d.ts +61 -0
- package/shared/tools/query-freebusy.js +110 -0
- package/shared/tools.d.ts +3 -0
- package/shared/tools.js +41 -0
- package/shared/types.d.ts +111 -0
- package/shared/types.js +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { handleApiError } from './api-errors.js';
|
|
2
|
+
export async function queryFreebusy(baseUrl, headers, request) {
|
|
3
|
+
const url = `${baseUrl}/freeBusy`;
|
|
4
|
+
const response = await fetch(url, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers,
|
|
7
|
+
body: JSON.stringify(request),
|
|
8
|
+
});
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
handleApiError(response.status, 'querying freebusy information');
|
|
11
|
+
}
|
|
12
|
+
return (await response.json());
|
|
13
|
+
}
|
package/shared/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utilities
|
|
3
|
+
* IMPORTANT: All logging uses console.error() to write to stderr.
|
|
4
|
+
* The MCP protocol requires stdout to contain only JSON messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare function logServerStart(serverName: string, transport?: string): void;
|
|
7
|
+
export declare function logError(context: string, error: unknown): void;
|
|
8
|
+
export declare function logWarning(context: string, message: string): void;
|
|
9
|
+
export declare function logDebug(context: string, message: string): void;
|
|
10
|
+
export declare function logInfo(context: string, message: string): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utilities
|
|
3
|
+
* IMPORTANT: All logging uses console.error() to write to stderr.
|
|
4
|
+
* The MCP protocol requires stdout to contain only JSON messages.
|
|
5
|
+
*/
|
|
6
|
+
export function logServerStart(serverName, transport = 'stdio') {
|
|
7
|
+
console.error(`MCP server ${serverName} running on ${transport}`);
|
|
8
|
+
}
|
|
9
|
+
export function logError(context, error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11
|
+
console.error(`[ERROR] ${context}: ${message}`);
|
|
12
|
+
}
|
|
13
|
+
export function logWarning(context, message) {
|
|
14
|
+
console.error(`[WARN] ${context}: ${message}`);
|
|
15
|
+
}
|
|
16
|
+
export function logDebug(context, message) {
|
|
17
|
+
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
|
18
|
+
console.error(`[DEBUG] ${context}: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function logInfo(context, message) {
|
|
22
|
+
console.error(`[INFO] ${context}: ${message}`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { CalendarEvent, CalendarEventList, CalendarList, FreeBusyRequest, FreeBusyResponse } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Google Calendar API client interface
|
|
5
|
+
* Defines all methods for interacting with the Google Calendar API
|
|
6
|
+
*/
|
|
7
|
+
export interface ICalendarClient {
|
|
8
|
+
/**
|
|
9
|
+
* List events from a calendar
|
|
10
|
+
*/
|
|
11
|
+
listEvents(calendarId: string, options?: {
|
|
12
|
+
timeMin?: string;
|
|
13
|
+
timeMax?: string;
|
|
14
|
+
maxResults?: number;
|
|
15
|
+
pageToken?: string;
|
|
16
|
+
q?: string;
|
|
17
|
+
singleEvents?: boolean;
|
|
18
|
+
orderBy?: string;
|
|
19
|
+
}): Promise<CalendarEventList>;
|
|
20
|
+
/**
|
|
21
|
+
* Get a specific event by ID
|
|
22
|
+
*/
|
|
23
|
+
getEvent(calendarId: string, eventId: string): Promise<CalendarEvent>;
|
|
24
|
+
/**
|
|
25
|
+
* Create a new event
|
|
26
|
+
*/
|
|
27
|
+
createEvent(calendarId: string, event: Partial<CalendarEvent>): Promise<CalendarEvent>;
|
|
28
|
+
/**
|
|
29
|
+
* List calendars available to the user
|
|
30
|
+
*/
|
|
31
|
+
listCalendars(options?: {
|
|
32
|
+
maxResults?: number;
|
|
33
|
+
pageToken?: string;
|
|
34
|
+
}): Promise<CalendarList>;
|
|
35
|
+
/**
|
|
36
|
+
* Query free/busy information
|
|
37
|
+
*/
|
|
38
|
+
queryFreebusy(request: FreeBusyRequest): Promise<FreeBusyResponse>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Service account credentials structure
|
|
42
|
+
*/
|
|
43
|
+
export interface ServiceAccountCredentials {
|
|
44
|
+
type: string;
|
|
45
|
+
project_id: string;
|
|
46
|
+
private_key_id: string;
|
|
47
|
+
private_key: string;
|
|
48
|
+
client_email: string;
|
|
49
|
+
client_id: string;
|
|
50
|
+
auth_uri: string;
|
|
51
|
+
token_uri: string;
|
|
52
|
+
auth_provider_x509_cert_url: string;
|
|
53
|
+
client_x509_cert_url: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Google Calendar API client implementation using service account with domain-wide delegation
|
|
57
|
+
*/
|
|
58
|
+
export declare class ServiceAccountCalendarClient implements ICalendarClient {
|
|
59
|
+
private impersonateEmail;
|
|
60
|
+
private baseUrl;
|
|
61
|
+
private jwtClient;
|
|
62
|
+
private cachedToken;
|
|
63
|
+
private tokenExpiry;
|
|
64
|
+
private refreshPromise;
|
|
65
|
+
constructor(credentials: ServiceAccountCredentials, impersonateEmail: string);
|
|
66
|
+
private refreshToken;
|
|
67
|
+
private getHeaders;
|
|
68
|
+
listEvents(calendarId: string, options?: {
|
|
69
|
+
timeMin?: string;
|
|
70
|
+
timeMax?: string;
|
|
71
|
+
maxResults?: number;
|
|
72
|
+
pageToken?: string;
|
|
73
|
+
q?: string;
|
|
74
|
+
singleEvents?: boolean;
|
|
75
|
+
orderBy?: string;
|
|
76
|
+
}): Promise<CalendarEventList>;
|
|
77
|
+
getEvent(calendarId: string, eventId: string): Promise<CalendarEvent>;
|
|
78
|
+
createEvent(calendarId: string, event: Partial<CalendarEvent>): Promise<CalendarEvent>;
|
|
79
|
+
listCalendars(options?: {
|
|
80
|
+
maxResults?: number;
|
|
81
|
+
pageToken?: string;
|
|
82
|
+
}): Promise<CalendarList>;
|
|
83
|
+
queryFreebusy(request: FreeBusyRequest): Promise<FreeBusyResponse>;
|
|
84
|
+
}
|
|
85
|
+
export type ClientFactory = () => ICalendarClient;
|
|
86
|
+
/**
|
|
87
|
+
* Creates the default Google Calendar client based on environment variables.
|
|
88
|
+
* Uses service account with domain-wide delegation:
|
|
89
|
+
* - GCAL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
|
|
90
|
+
* - GCAL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
|
|
91
|
+
* - GCAL_IMPERSONATE_EMAIL: Email address to impersonate
|
|
92
|
+
*/
|
|
93
|
+
export declare function createDefaultClient(): ICalendarClient;
|
|
94
|
+
export declare function createMCPServer(): {
|
|
95
|
+
server: Server<{
|
|
96
|
+
method: string;
|
|
97
|
+
params?: {
|
|
98
|
+
[x: string]: unknown;
|
|
99
|
+
_meta?: {
|
|
100
|
+
[x: string]: unknown;
|
|
101
|
+
progressToken?: string | number | undefined;
|
|
102
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
103
|
+
taskId: string;
|
|
104
|
+
} | undefined;
|
|
105
|
+
} | undefined;
|
|
106
|
+
} | undefined;
|
|
107
|
+
}, {
|
|
108
|
+
method: string;
|
|
109
|
+
params?: {
|
|
110
|
+
[x: string]: unknown;
|
|
111
|
+
_meta?: {
|
|
112
|
+
[x: string]: unknown;
|
|
113
|
+
progressToken?: string | number | undefined;
|
|
114
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
115
|
+
taskId: string;
|
|
116
|
+
} | undefined;
|
|
117
|
+
} | undefined;
|
|
118
|
+
} | undefined;
|
|
119
|
+
}, {
|
|
120
|
+
[x: string]: unknown;
|
|
121
|
+
_meta?: {
|
|
122
|
+
[x: string]: unknown;
|
|
123
|
+
progressToken?: string | number | undefined;
|
|
124
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
125
|
+
taskId: string;
|
|
126
|
+
} | undefined;
|
|
127
|
+
} | undefined;
|
|
128
|
+
}>;
|
|
129
|
+
registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
|
|
130
|
+
};
|
package/shared/server.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { JWT } from 'google-auth-library';
|
|
3
|
+
import { createRegisterTools } from './tools.js';
|
|
4
|
+
/**
|
|
5
|
+
* Google Calendar API client implementation using service account with domain-wide delegation
|
|
6
|
+
*/
|
|
7
|
+
export class ServiceAccountCalendarClient {
|
|
8
|
+
impersonateEmail;
|
|
9
|
+
baseUrl = 'https://www.googleapis.com/calendar/v3';
|
|
10
|
+
jwtClient;
|
|
11
|
+
cachedToken = null;
|
|
12
|
+
tokenExpiry = 0;
|
|
13
|
+
refreshPromise = null;
|
|
14
|
+
constructor(credentials, impersonateEmail) {
|
|
15
|
+
this.impersonateEmail = impersonateEmail;
|
|
16
|
+
this.jwtClient = new JWT({
|
|
17
|
+
email: credentials.client_email,
|
|
18
|
+
key: credentials.private_key,
|
|
19
|
+
scopes: ['https://www.googleapis.com/auth/calendar'],
|
|
20
|
+
subject: impersonateEmail,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async refreshToken() {
|
|
24
|
+
const tokenResponse = await this.jwtClient.authorize();
|
|
25
|
+
if (!tokenResponse.access_token) {
|
|
26
|
+
throw new Error('Failed to obtain access token from service account');
|
|
27
|
+
}
|
|
28
|
+
this.cachedToken = tokenResponse.access_token;
|
|
29
|
+
// Token typically expires in 1 hour, but use the actual expiry if provided
|
|
30
|
+
this.tokenExpiry = tokenResponse.expiry_date || Date.now() + 3600000;
|
|
31
|
+
}
|
|
32
|
+
async getHeaders() {
|
|
33
|
+
// Check if we have a valid cached token (with 60 second buffer)
|
|
34
|
+
if (this.cachedToken && Date.now() < this.tokenExpiry - 60000) {
|
|
35
|
+
return {
|
|
36
|
+
Authorization: `Bearer ${this.cachedToken}`,
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Use mutex pattern to prevent concurrent token refresh
|
|
41
|
+
if (!this.refreshPromise) {
|
|
42
|
+
this.refreshPromise = this.refreshToken().finally(() => {
|
|
43
|
+
this.refreshPromise = null;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
await this.refreshPromise;
|
|
47
|
+
return {
|
|
48
|
+
Authorization: `Bearer ${this.cachedToken}`,
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async listEvents(calendarId, options) {
|
|
53
|
+
const headers = await this.getHeaders();
|
|
54
|
+
const { listEvents } = await import('./calendar-client/lib/list-events.js');
|
|
55
|
+
return listEvents(this.baseUrl, headers, calendarId, options);
|
|
56
|
+
}
|
|
57
|
+
async getEvent(calendarId, eventId) {
|
|
58
|
+
const headers = await this.getHeaders();
|
|
59
|
+
const { getEvent } = await import('./calendar-client/lib/get-event.js');
|
|
60
|
+
return getEvent(this.baseUrl, headers, calendarId, eventId);
|
|
61
|
+
}
|
|
62
|
+
async createEvent(calendarId, event) {
|
|
63
|
+
const headers = await this.getHeaders();
|
|
64
|
+
const { createEvent } = await import('./calendar-client/lib/create-event.js');
|
|
65
|
+
return createEvent(this.baseUrl, headers, calendarId, event);
|
|
66
|
+
}
|
|
67
|
+
async listCalendars(options) {
|
|
68
|
+
const headers = await this.getHeaders();
|
|
69
|
+
const { listCalendars } = await import('./calendar-client/lib/list-calendars.js');
|
|
70
|
+
return listCalendars(this.baseUrl, headers, options);
|
|
71
|
+
}
|
|
72
|
+
async queryFreebusy(request) {
|
|
73
|
+
const headers = await this.getHeaders();
|
|
74
|
+
const { queryFreebusy } = await import('./calendar-client/lib/query-freebusy.js');
|
|
75
|
+
return queryFreebusy(this.baseUrl, headers, request);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates the default Google Calendar client based on environment variables.
|
|
80
|
+
* Uses service account with domain-wide delegation:
|
|
81
|
+
* - GCAL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
|
|
82
|
+
* - GCAL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
|
|
83
|
+
* - GCAL_IMPERSONATE_EMAIL: Email address to impersonate
|
|
84
|
+
*/
|
|
85
|
+
export function createDefaultClient() {
|
|
86
|
+
const clientEmail = process.env.GCAL_SERVICE_ACCOUNT_CLIENT_EMAIL;
|
|
87
|
+
// Handle both literal \n in JSON configs and actual newlines
|
|
88
|
+
const privateKey = process.env.GCAL_SERVICE_ACCOUNT_PRIVATE_KEY?.replace(/\\n/g, '\n');
|
|
89
|
+
const impersonateEmail = process.env.GCAL_IMPERSONATE_EMAIL;
|
|
90
|
+
if (!clientEmail) {
|
|
91
|
+
throw new Error('GCAL_SERVICE_ACCOUNT_CLIENT_EMAIL environment variable must be set. ' +
|
|
92
|
+
'This is the email address from your Google Cloud service account.');
|
|
93
|
+
}
|
|
94
|
+
if (!privateKey) {
|
|
95
|
+
throw new Error('GCAL_SERVICE_ACCOUNT_PRIVATE_KEY environment variable must be set. ' +
|
|
96
|
+
'This is the private key from your Google Cloud service account (PEM format).');
|
|
97
|
+
}
|
|
98
|
+
if (!impersonateEmail) {
|
|
99
|
+
throw new Error('GCAL_IMPERSONATE_EMAIL environment variable must be set. ' +
|
|
100
|
+
'This is the email address of the user to access Google Calendar as.');
|
|
101
|
+
}
|
|
102
|
+
const credentials = {
|
|
103
|
+
type: 'service_account',
|
|
104
|
+
project_id: '',
|
|
105
|
+
private_key_id: '',
|
|
106
|
+
private_key: privateKey,
|
|
107
|
+
client_email: clientEmail,
|
|
108
|
+
client_id: '',
|
|
109
|
+
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
|
|
110
|
+
token_uri: 'https://oauth2.googleapis.com/token',
|
|
111
|
+
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
|
|
112
|
+
client_x509_cert_url: '',
|
|
113
|
+
};
|
|
114
|
+
return new ServiceAccountCalendarClient(credentials, impersonateEmail);
|
|
115
|
+
}
|
|
116
|
+
export function createMCPServer() {
|
|
117
|
+
const server = new Server({
|
|
118
|
+
name: 'google-calendar-workspace-mcp-server',
|
|
119
|
+
version: '0.0.1',
|
|
120
|
+
}, {
|
|
121
|
+
capabilities: {
|
|
122
|
+
tools: {},
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const registerHandlers = async (server, clientFactory) => {
|
|
126
|
+
// Use provided factory or create default client
|
|
127
|
+
const factory = clientFactory || createDefaultClient;
|
|
128
|
+
const registerTools = createRegisterTools(factory);
|
|
129
|
+
registerTools(server);
|
|
130
|
+
};
|
|
131
|
+
return { server, registerHandlers };
|
|
132
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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 CreateEventSchema: z.ZodObject<{
|
|
5
|
+
calendar_id: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
6
|
+
summary: z.ZodString;
|
|
7
|
+
description: z.ZodOptional<z.ZodString>;
|
|
8
|
+
location: z.ZodOptional<z.ZodString>;
|
|
9
|
+
start_datetime: z.ZodOptional<z.ZodString>;
|
|
10
|
+
start_date: z.ZodOptional<z.ZodString>;
|
|
11
|
+
start_timezone: z.ZodOptional<z.ZodString>;
|
|
12
|
+
end_datetime: z.ZodOptional<z.ZodString>;
|
|
13
|
+
end_date: z.ZodOptional<z.ZodString>;
|
|
14
|
+
end_timezone: z.ZodOptional<z.ZodString>;
|
|
15
|
+
attendees: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
calendar_id: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
description?: string | undefined;
|
|
20
|
+
location?: string | undefined;
|
|
21
|
+
start_datetime?: string | undefined;
|
|
22
|
+
start_date?: string | undefined;
|
|
23
|
+
start_timezone?: string | undefined;
|
|
24
|
+
end_datetime?: string | undefined;
|
|
25
|
+
end_date?: string | undefined;
|
|
26
|
+
end_timezone?: string | undefined;
|
|
27
|
+
attendees?: string[] | undefined;
|
|
28
|
+
}, {
|
|
29
|
+
summary: string;
|
|
30
|
+
calendar_id?: string | undefined;
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
location?: string | undefined;
|
|
33
|
+
start_datetime?: string | undefined;
|
|
34
|
+
start_date?: string | undefined;
|
|
35
|
+
start_timezone?: string | undefined;
|
|
36
|
+
end_datetime?: string | undefined;
|
|
37
|
+
end_date?: string | undefined;
|
|
38
|
+
end_timezone?: string | undefined;
|
|
39
|
+
attendees?: string[] | undefined;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function createEventTool(server: Server, clientFactory: ClientFactory): {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object";
|
|
46
|
+
properties: {
|
|
47
|
+
calendar_id: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string | undefined;
|
|
50
|
+
};
|
|
51
|
+
summary: {
|
|
52
|
+
type: string;
|
|
53
|
+
description: string | undefined;
|
|
54
|
+
};
|
|
55
|
+
description: {
|
|
56
|
+
type: string;
|
|
57
|
+
description: string | undefined;
|
|
58
|
+
};
|
|
59
|
+
location: {
|
|
60
|
+
type: string;
|
|
61
|
+
description: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
start_datetime: {
|
|
64
|
+
type: string;
|
|
65
|
+
description: string | undefined;
|
|
66
|
+
};
|
|
67
|
+
start_date: {
|
|
68
|
+
type: string;
|
|
69
|
+
description: string | undefined;
|
|
70
|
+
};
|
|
71
|
+
start_timezone: {
|
|
72
|
+
type: string;
|
|
73
|
+
description: string | undefined;
|
|
74
|
+
};
|
|
75
|
+
end_datetime: {
|
|
76
|
+
type: string;
|
|
77
|
+
description: string | undefined;
|
|
78
|
+
};
|
|
79
|
+
end_date: {
|
|
80
|
+
type: string;
|
|
81
|
+
description: string | undefined;
|
|
82
|
+
};
|
|
83
|
+
end_timezone: {
|
|
84
|
+
type: string;
|
|
85
|
+
description: string | undefined;
|
|
86
|
+
};
|
|
87
|
+
attendees: {
|
|
88
|
+
type: string;
|
|
89
|
+
items: {
|
|
90
|
+
type: string;
|
|
91
|
+
};
|
|
92
|
+
description: string | undefined;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
required: string[];
|
|
96
|
+
};
|
|
97
|
+
handler: (args: unknown) => Promise<{
|
|
98
|
+
content: {
|
|
99
|
+
type: string;
|
|
100
|
+
text: string;
|
|
101
|
+
}[];
|
|
102
|
+
isError?: undefined;
|
|
103
|
+
} | {
|
|
104
|
+
content: {
|
|
105
|
+
type: string;
|
|
106
|
+
text: string;
|
|
107
|
+
}[];
|
|
108
|
+
isError: boolean;
|
|
109
|
+
}>;
|
|
110
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { logError } from '../logging.js';
|
|
3
|
+
export const CreateEventSchema = z.object({
|
|
4
|
+
calendar_id: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.default('primary')
|
|
8
|
+
.describe('Calendar ID to create the event in. Defaults to "primary".'),
|
|
9
|
+
summary: z.string().min(1).describe('Event title/summary.'),
|
|
10
|
+
description: z.string().optional().describe('Event description.'),
|
|
11
|
+
location: z.string().optional().describe('Event location.'),
|
|
12
|
+
start_datetime: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Start date-time in RFC3339 format (e.g., "2024-01-01T10:00:00-05:00"). Use this for timed events.'),
|
|
16
|
+
start_date: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Start date for all-day events (e.g., "2024-01-01"). Use this for all-day events.'),
|
|
20
|
+
start_timezone: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Time zone for start time (e.g., "America/New_York"). Optional.'),
|
|
24
|
+
end_datetime: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('End date-time in RFC3339 format (e.g., "2024-01-01T11:00:00-05:00"). Use this for timed events.'),
|
|
28
|
+
end_date: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('End date for all-day events (e.g., "2024-01-02"). Use this for all-day events.'),
|
|
32
|
+
end_timezone: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Time zone for end time (e.g., "America/New_York"). Optional.'),
|
|
36
|
+
attendees: z.array(z.string()).optional().describe('List of attendee email addresses.'),
|
|
37
|
+
});
|
|
38
|
+
export function createEventTool(server, clientFactory) {
|
|
39
|
+
return {
|
|
40
|
+
name: 'gcal_create_event',
|
|
41
|
+
description: 'Creates a new event in Google Calendar. ' +
|
|
42
|
+
'Supports both timed events (using start_datetime/end_datetime) and all-day events (using start_date/end_date). ' +
|
|
43
|
+
'Can include title, description, location, and attendees. ' +
|
|
44
|
+
'Returns the created event details including the event ID and link.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
calendar_id: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: CreateEventSchema.shape.calendar_id.description,
|
|
51
|
+
},
|
|
52
|
+
summary: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: CreateEventSchema.shape.summary.description,
|
|
55
|
+
},
|
|
56
|
+
description: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: CreateEventSchema.shape.description.description,
|
|
59
|
+
},
|
|
60
|
+
location: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: CreateEventSchema.shape.location.description,
|
|
63
|
+
},
|
|
64
|
+
start_datetime: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: CreateEventSchema.shape.start_datetime.description,
|
|
67
|
+
},
|
|
68
|
+
start_date: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: CreateEventSchema.shape.start_date.description,
|
|
71
|
+
},
|
|
72
|
+
start_timezone: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: CreateEventSchema.shape.start_timezone.description,
|
|
75
|
+
},
|
|
76
|
+
end_datetime: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: CreateEventSchema.shape.end_datetime.description,
|
|
79
|
+
},
|
|
80
|
+
end_date: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: CreateEventSchema.shape.end_date.description,
|
|
83
|
+
},
|
|
84
|
+
end_timezone: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: CreateEventSchema.shape.end_timezone.description,
|
|
87
|
+
},
|
|
88
|
+
attendees: {
|
|
89
|
+
type: 'array',
|
|
90
|
+
items: { type: 'string' },
|
|
91
|
+
description: CreateEventSchema.shape.attendees.description,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['summary'],
|
|
95
|
+
},
|
|
96
|
+
handler: async (args) => {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = CreateEventSchema.parse(args);
|
|
99
|
+
const client = clientFactory();
|
|
100
|
+
// Validate that we have either datetime or date fields
|
|
101
|
+
if (!parsed.start_datetime && !parsed.start_date) {
|
|
102
|
+
throw new Error('Must provide either start_datetime or start_date');
|
|
103
|
+
}
|
|
104
|
+
if (!parsed.end_datetime && !parsed.end_date) {
|
|
105
|
+
throw new Error('Must provide either end_datetime or end_date');
|
|
106
|
+
}
|
|
107
|
+
// Build event object
|
|
108
|
+
const event = {
|
|
109
|
+
summary: parsed.summary,
|
|
110
|
+
description: parsed.description,
|
|
111
|
+
location: parsed.location,
|
|
112
|
+
start: {},
|
|
113
|
+
end: {},
|
|
114
|
+
};
|
|
115
|
+
// Set start time
|
|
116
|
+
if (parsed.start_datetime) {
|
|
117
|
+
event.start.dateTime = parsed.start_datetime;
|
|
118
|
+
if (parsed.start_timezone) {
|
|
119
|
+
event.start.timeZone = parsed.start_timezone;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (parsed.start_date) {
|
|
123
|
+
event.start.date = parsed.start_date;
|
|
124
|
+
}
|
|
125
|
+
// Set end time
|
|
126
|
+
if (parsed.end_datetime) {
|
|
127
|
+
event.end.dateTime = parsed.end_datetime;
|
|
128
|
+
if (parsed.end_timezone) {
|
|
129
|
+
event.end.timeZone = parsed.end_timezone;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (parsed.end_date) {
|
|
133
|
+
event.end.date = parsed.end_date;
|
|
134
|
+
}
|
|
135
|
+
// Add attendees
|
|
136
|
+
if (parsed.attendees && parsed.attendees.length > 0) {
|
|
137
|
+
event.attendees = parsed.attendees.map((email) => ({ email }));
|
|
138
|
+
}
|
|
139
|
+
const result = await client.createEvent(parsed.calendar_id, event);
|
|
140
|
+
let output = `# Event Created Successfully\n\n`;
|
|
141
|
+
output += `## ${result.summary || '(No title)'}\n\n`;
|
|
142
|
+
output += `**Event ID:** ${result.id}\n`;
|
|
143
|
+
// Start time
|
|
144
|
+
if (result.start?.dateTime) {
|
|
145
|
+
output += `**Start:** ${new Date(result.start.dateTime).toLocaleString()}\n`;
|
|
146
|
+
}
|
|
147
|
+
else if (result.start?.date) {
|
|
148
|
+
output += `**Start:** ${result.start.date} (All day)\n`;
|
|
149
|
+
}
|
|
150
|
+
// End time
|
|
151
|
+
if (result.end?.dateTime) {
|
|
152
|
+
output += `**End:** ${new Date(result.end.dateTime).toLocaleString()}\n`;
|
|
153
|
+
}
|
|
154
|
+
else if (result.end?.date) {
|
|
155
|
+
output += `**End:** ${result.end.date} (All day)\n`;
|
|
156
|
+
}
|
|
157
|
+
// Location
|
|
158
|
+
if (result.location) {
|
|
159
|
+
output += `**Location:** ${result.location}\n`;
|
|
160
|
+
}
|
|
161
|
+
// Attendees
|
|
162
|
+
if (result.attendees && result.attendees.length > 0) {
|
|
163
|
+
output += `**Attendees:** ${result.attendees.length}\n`;
|
|
164
|
+
for (const attendee of result.attendees) {
|
|
165
|
+
output += ` - ${attendee.email}\n`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Link
|
|
169
|
+
if (result.htmlLink) {
|
|
170
|
+
output += `\n**Event Link:** ${result.htmlLink}\n`;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: output,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
logError('create-event-tool', error);
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: `Error creating event: ${error instanceof Error ? error.message : String(error)}`,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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 GetEventSchema: z.ZodObject<{
|
|
5
|
+
calendar_id: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
6
|
+
event_id: z.ZodString;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
calendar_id: string;
|
|
9
|
+
event_id: string;
|
|
10
|
+
}, {
|
|
11
|
+
event_id: string;
|
|
12
|
+
calendar_id?: string | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function getEventTool(server: Server, clientFactory: ClientFactory): {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object";
|
|
19
|
+
properties: {
|
|
20
|
+
calendar_id: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
event_id: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string | undefined;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
required: string[];
|
|
30
|
+
};
|
|
31
|
+
handler: (args: unknown) => Promise<{
|
|
32
|
+
content: {
|
|
33
|
+
type: string;
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
isError?: undefined;
|
|
37
|
+
} | {
|
|
38
|
+
content: {
|
|
39
|
+
type: string;
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
isError: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
};
|