@alanse/mcp-server-google-workspace 1.0.0 → 1.0.2
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 +146 -17
- package/dist/auth.js +5 -0
- package/dist/lib/calendar-helpers.js +197 -0
- package/dist/lib/drive-helpers.js +263 -0
- package/dist/lib/gmail-helpers.js +204 -0
- package/dist/tools/calendar/acl/calendar_acl_insert.js +80 -0
- package/dist/tools/calendar/acl/calendar_acl_list.js +82 -0
- package/dist/tools/calendar/basic/calendar_create_event.js +113 -0
- package/dist/tools/calendar/basic/calendar_delete_event.js +52 -0
- package/dist/tools/calendar/basic/calendar_get_event.js +52 -0
- package/dist/tools/calendar/basic/calendar_list_events.js +86 -0
- package/dist/tools/calendar/basic/calendar_update_event.js +116 -0
- package/dist/tools/calendar/calendarlist/calendar_calendarlist_get.js +73 -0
- package/dist/tools/calendar/calendarlist/calendar_calendarlist_list.js +87 -0
- package/dist/tools/calendar/calendars/calendar_calendars_get.js +52 -0
- package/dist/tools/calendar/calendars/calendar_calendars_insert.js +66 -0
- package/dist/tools/calendar/calendars/calendar_calendars_update.js +85 -0
- package/dist/tools/calendar/colors/calendar_colors_get.js +46 -0
- package/dist/tools/calendar/events_advanced/calendar_events_instances.js +81 -0
- package/dist/tools/calendar/events_advanced/calendar_events_move.js +63 -0
- package/dist/tools/calendar/events_advanced/calendar_events_quickadd.js +52 -0
- package/dist/tools/calendar/freebusy/calendar_freebusy_query.js +69 -0
- package/dist/tools/calendar/settings/calendar_settings_list.js +81 -0
- package/dist/tools/drive/advanced/drive_empty_trash.js +56 -0
- package/dist/tools/drive/advanced/drive_export_file.js +158 -0
- package/dist/tools/drive/advanced/drive_list_revisions.js +80 -0
- package/dist/tools/drive/basic/drive_get_metadata.js +49 -0
- package/dist/tools/drive/basic/drive_list_files.js +76 -0
- package/dist/tools/drive/file/drive_copy_file.js +79 -0
- package/dist/tools/drive/file/drive_create_file.js +72 -0
- package/dist/tools/drive/file/drive_delete_file.js +48 -0
- package/dist/tools/drive/file/drive_move_file.js +79 -0
- package/dist/tools/drive/file/drive_rename_file.js +58 -0
- package/dist/tools/drive/file/drive_update_file.js +106 -0
- package/dist/tools/drive/file/drive_upload_file.js +80 -0
- package/dist/tools/drive/folder/drive_create_folder.js +67 -0
- package/dist/tools/drive/folder/drive_list_folder_contents.js +68 -0
- package/dist/tools/drive/folder/drive_move_to_folder.js +59 -0
- package/dist/tools/drive/permissions/drive_list_permissions.js +115 -0
- package/dist/tools/drive/permissions/drive_remove_permission.js +71 -0
- package/dist/tools/drive/permissions/drive_share_file.js +116 -0
- package/dist/tools/drive/permissions/drive_update_permission.js +79 -0
- package/dist/tools/gmail/basic/gmail_get_message.js +95 -0
- package/dist/tools/gmail/basic/gmail_get_thread.js +46 -0
- package/dist/tools/gmail/basic/gmail_list_labels.js +54 -0
- package/dist/tools/gmail/basic/gmail_search_messages.js +59 -0
- package/dist/tools/gmail/batch/gmail_batch_modify_labels.js +74 -0
- package/dist/tools/gmail/batch/gmail_get_messages_batch.js +120 -0
- package/dist/tools/gmail/batch/gmail_get_threads_batch.js +102 -0
- package/dist/tools/gmail/labels/gmail_manage_label.js +131 -0
- package/dist/tools/gmail/labels/gmail_modify_labels.js +65 -0
- package/dist/tools/gmail/send/gmail_draft_message.js +117 -0
- package/dist/tools/gmail/send/gmail_send_message.js +109 -0
- package/dist/tools/index.js +267 -3
- package/package.json +8 -3
- package/dist/tools/basic/gsheets_add_sheet.js +0 -65
- package/dist/tools/basic/gsheets_copy_sheet.js +0 -56
- package/dist/tools/basic/gsheets_copy_to.js +0 -113
- package/dist/tools/basic/gsheets_create_spreadsheet.js +0 -88
- package/dist/tools/basic/gsheets_delete_columns.js +0 -69
- package/dist/tools/basic/gsheets_delete_rows.js +0 -69
- package/dist/tools/basic/gsheets_delete_sheet.js +0 -56
- package/dist/tools/basic/gsheets_duplicate_sheet.js +0 -72
- package/dist/tools/basic/gsheets_insert_columns.js +0 -69
- package/dist/tools/basic/gsheets_insert_rows.js +0 -69
- package/dist/tools/basic/gsheets_list_sheets.js +0 -53
- package/dist/tools/basic/gsheets_read.js +0 -120
- package/dist/tools/basic/gsheets_rename_sheet.js +0 -64
- package/dist/tools/charts/gsheets_add_bubble.js +0 -176
- package/dist/tools/charts/gsheets_add_candlestick.js +0 -192
- package/dist/tools/charts/gsheets_add_chart.js +0 -162
- package/dist/tools/charts/gsheets_add_combo.js +0 -169
- package/dist/tools/charts/gsheets_add_histogram.js +0 -143
- package/dist/tools/charts/gsheets_add_org_chart.js +0 -160
- package/dist/tools/charts/gsheets_add_treemap.js +0 -177
- package/dist/tools/charts/gsheets_add_waterfall.js +0 -155
- package/dist/tools/charts/gsheets_delete_chart.js +0 -56
- package/dist/tools/charts/gsheets_update_chart.js +0 -118
- package/dist/tools/data/gsheets_append_data.js +0 -68
- package/dist/tools/data/gsheets_batch_clear.js +0 -53
- package/dist/tools/data/gsheets_batch_update.js +0 -81
- package/dist/tools/data/gsheets_clear_data.js +0 -53
- package/dist/tools/data/gsheets_create_filter.js +0 -81
- package/dist/tools/data/gsheets_find_replace.js +0 -124
- package/dist/tools/data/gsheets_set_data_validation.js +0 -153
- package/dist/tools/data/gsheets_sort_range.js +0 -102
- package/dist/tools/data/gsheets_update_cell.js +0 -44
- package/dist/tools/formatting/gsheets_auto_resize.js +0 -75
- package/dist/tools/formatting/gsheets_format_cells.js +0 -161
- package/dist/tools/formatting/gsheets_freeze_columns.js +0 -67
- package/dist/tools/formatting/gsheets_freeze_rows.js +0 -67
- package/dist/tools/formatting/gsheets_merge_cells.js +0 -85
- package/dist/tools/formatting/gsheets_set_number_format.js +0 -116
- package/dist/tools/formatting/gsheets_unmerge_cells.js +0 -79
- package/dist/tools/formatting/gsheets_update_borders.js +0 -212
- package/dist/tools/gdrive/gdrive_read_file.js +0 -77
- package/dist/tools/gdrive/gdrive_search.js +0 -71
- package/dist/tools/gdrive_read_file.js +0 -77
- package/dist/tools/gdrive_search.js +0 -71
- package/dist/tools/gsheets_add_bubble.js +0 -176
- package/dist/tools/gsheets_add_candlestick.js +0 -192
- package/dist/tools/gsheets_add_chart.js +0 -162
- package/dist/tools/gsheets_add_combo.js +0 -169
- package/dist/tools/gsheets_add_conditional_format.js +0 -175
- package/dist/tools/gsheets_add_histogram.js +0 -143
- package/dist/tools/gsheets_add_named_range.js +0 -87
- package/dist/tools/gsheets_add_org_chart.js +0 -160
- package/dist/tools/gsheets_add_protected_range.js +0 -127
- package/dist/tools/gsheets_add_sheet.js +0 -65
- package/dist/tools/gsheets_add_treemap.js +0 -177
- package/dist/tools/gsheets_add_waterfall.js +0 -155
- package/dist/tools/gsheets_append_data.js +0 -68
- package/dist/tools/gsheets_auto_resize.js +0 -75
- package/dist/tools/gsheets_batch_clear.js +0 -53
- package/dist/tools/gsheets_batch_update.js +0 -81
- package/dist/tools/gsheets_clear_data.js +0 -53
- package/dist/tools/gsheets_copy_sheet.js +0 -56
- package/dist/tools/gsheets_copy_to.js +0 -113
- package/dist/tools/gsheets_create_filter.js +0 -81
- package/dist/tools/gsheets_create_spreadsheet.js +0 -88
- package/dist/tools/gsheets_delete_chart.js +0 -56
- package/dist/tools/gsheets_delete_columns.js +0 -69
- package/dist/tools/gsheets_delete_named_range.js +0 -56
- package/dist/tools/gsheets_delete_protected_range.js +0 -56
- package/dist/tools/gsheets_delete_rows.js +0 -69
- package/dist/tools/gsheets_delete_sheet.js +0 -56
- package/dist/tools/gsheets_duplicate_sheet.js +0 -72
- package/dist/tools/gsheets_find_replace.js +0 -124
- package/dist/tools/gsheets_format_cells.js +0 -161
- package/dist/tools/gsheets_freeze_columns.js +0 -67
- package/dist/tools/gsheets_freeze_rows.js +0 -67
- package/dist/tools/gsheets_insert_columns.js +0 -69
- package/dist/tools/gsheets_insert_rows.js +0 -69
- package/dist/tools/gsheets_list_sheets.js +0 -53
- package/dist/tools/gsheets_merge_cells.js +0 -85
- package/dist/tools/gsheets_read.js +0 -120
- package/dist/tools/gsheets_rename_sheet.js +0 -64
- package/dist/tools/gsheets_set_data_validation.js +0 -153
- package/dist/tools/gsheets_set_number_format.js +0 -116
- package/dist/tools/gsheets_sort_range.js +0 -102
- package/dist/tools/gsheets_unmerge_cells.js +0 -79
- package/dist/tools/gsheets_update_borders.js +0 -212
- package/dist/tools/gsheets_update_cell.js +0 -44
- package/dist/tools/gsheets_update_chart.js +0 -118
- package/dist/tools/gsheets_update_named_range.js +0 -112
- package/dist/tools/gsheets_update_protected_range.js +0 -110
- package/dist/tools/protection/gsheets_add_conditional_format.js +0 -175
- package/dist/tools/protection/gsheets_add_named_range.js +0 -87
- package/dist/tools/protection/gsheets_add_protected_range.js +0 -127
- package/dist/tools/protection/gsheets_delete_named_range.js +0 -56
- package/dist/tools/protection/gsheets_delete_protected_range.js +0 -56
- package/dist/tools/protection/gsheets_update_named_range.js +0 -112
- package/dist/tools/protection/gsheets_update_protected_range.js +0 -110
- /package/dist/tools/drive/{drive_read_file.js → basic/drive_read_file.js} +0 -0
- /package/dist/tools/drive/{drive_search.js → basic/drive_search.js} +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Utilities for Gmail API operations including:
|
|
5
|
+
* - Message body extraction and formatting
|
|
6
|
+
* - HTML to text conversion
|
|
7
|
+
* - MIME message preparation
|
|
8
|
+
* - Attachment handling
|
|
9
|
+
* - URL generation
|
|
10
|
+
*/
|
|
11
|
+
import nodemailer from "nodemailer";
|
|
12
|
+
import { convert } from "html-to-text";
|
|
13
|
+
/**
|
|
14
|
+
* Extract text and HTML bodies from Gmail message payload
|
|
15
|
+
*/
|
|
16
|
+
export function extractMessageBodies(payload) {
|
|
17
|
+
let textBody = "";
|
|
18
|
+
let htmlBody = "";
|
|
19
|
+
function extractParts(part) {
|
|
20
|
+
if (part.mimeType === "text/plain" && part.body?.data) {
|
|
21
|
+
textBody = Buffer.from(part.body.data, "base64").toString("utf-8");
|
|
22
|
+
}
|
|
23
|
+
else if (part.mimeType === "text/html" && part.body?.data) {
|
|
24
|
+
htmlBody = Buffer.from(part.body.data, "base64").toString("utf-8");
|
|
25
|
+
}
|
|
26
|
+
// Recursively process multipart messages
|
|
27
|
+
if (part.parts) {
|
|
28
|
+
for (const subPart of part.parts) {
|
|
29
|
+
extractParts(subPart);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
extractParts(payload);
|
|
34
|
+
return { textBody, htmlBody };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format body content, preferring plain text but converting HTML if needed
|
|
38
|
+
*/
|
|
39
|
+
export function formatBodyContent(textBody, htmlBody) {
|
|
40
|
+
if (textBody) {
|
|
41
|
+
return textBody;
|
|
42
|
+
}
|
|
43
|
+
if (htmlBody) {
|
|
44
|
+
// Convert HTML to readable plain text
|
|
45
|
+
return convert(htmlBody, {
|
|
46
|
+
wordwrap: 80,
|
|
47
|
+
selectors: [
|
|
48
|
+
{ selector: "a", options: { ignoreHref: false } },
|
|
49
|
+
{ selector: "img", format: "skip" },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return "[No text content found]";
|
|
54
|
+
}
|
|
55
|
+
export function extractAttachments(payload) {
|
|
56
|
+
const attachments = [];
|
|
57
|
+
function extractParts(part) {
|
|
58
|
+
if (part.filename && part.body?.attachmentId) {
|
|
59
|
+
attachments.push({
|
|
60
|
+
filename: part.filename,
|
|
61
|
+
mimeType: part.mimeType || "application/octet-stream",
|
|
62
|
+
size: part.body.size || 0,
|
|
63
|
+
attachmentId: part.body.attachmentId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (part.parts) {
|
|
67
|
+
for (const subPart of part.parts) {
|
|
68
|
+
extractParts(subPart);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
extractParts(payload);
|
|
73
|
+
return attachments;
|
|
74
|
+
}
|
|
75
|
+
export function prepareGmailMessage(options) {
|
|
76
|
+
const { to, subject, body, bodyFormat, from, cc, bcc, inReplyTo, references, } = options;
|
|
77
|
+
// Build message using nodemailer's createTransport (for MIME creation only)
|
|
78
|
+
const message = {
|
|
79
|
+
from: from || "me",
|
|
80
|
+
to,
|
|
81
|
+
subject,
|
|
82
|
+
[bodyFormat === "html" ? "html" : "text"]: body,
|
|
83
|
+
};
|
|
84
|
+
if (cc)
|
|
85
|
+
message.cc = cc;
|
|
86
|
+
if (bcc)
|
|
87
|
+
message.bcc = bcc;
|
|
88
|
+
if (inReplyTo)
|
|
89
|
+
message.inReplyTo = inReplyTo;
|
|
90
|
+
if (references)
|
|
91
|
+
message.references = references;
|
|
92
|
+
// Create MIME message
|
|
93
|
+
const transport = nodemailer.createTransport({ jsonTransport: true });
|
|
94
|
+
// We'll use a synchronous approach here
|
|
95
|
+
let mimeMessage = "";
|
|
96
|
+
mimeMessage += `From: ${message.from}\r\n`;
|
|
97
|
+
mimeMessage += `To: ${message.to}\r\n`;
|
|
98
|
+
if (message.cc)
|
|
99
|
+
mimeMessage += `Cc: ${message.cc}\r\n`;
|
|
100
|
+
if (message.bcc)
|
|
101
|
+
mimeMessage += `Bcc: ${message.bcc}\r\n`;
|
|
102
|
+
mimeMessage += `Subject: ${message.subject}\r\n`;
|
|
103
|
+
if (message.inReplyTo)
|
|
104
|
+
mimeMessage += `In-Reply-To: ${message.inReplyTo}\r\n`;
|
|
105
|
+
if (message.references)
|
|
106
|
+
mimeMessage += `References: ${message.references}\r\n`;
|
|
107
|
+
mimeMessage += `Content-Type: text/${bodyFormat}; charset=utf-8\r\n`;
|
|
108
|
+
mimeMessage += `\r\n`;
|
|
109
|
+
mimeMessage += body;
|
|
110
|
+
// Base64url encode
|
|
111
|
+
return Buffer.from(mimeMessage)
|
|
112
|
+
.toString("base64")
|
|
113
|
+
.replace(/\+/g, "-")
|
|
114
|
+
.replace(/\//g, "_")
|
|
115
|
+
.replace(/=+$/, "");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate Gmail web URL for a message or thread
|
|
119
|
+
*/
|
|
120
|
+
export function generateGmailWebUrl(messageId, threadId) {
|
|
121
|
+
if (messageId) {
|
|
122
|
+
return `https://mail.google.com/mail/u/0/#all/${messageId}`;
|
|
123
|
+
}
|
|
124
|
+
if (threadId) {
|
|
125
|
+
return `https://mail.google.com/mail/u/0/#all/${threadId}`;
|
|
126
|
+
}
|
|
127
|
+
return "https://mail.google.com/mail/u/0/";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Extract header value from message headers
|
|
131
|
+
*/
|
|
132
|
+
export function getHeader(headers, name) {
|
|
133
|
+
if (!headers)
|
|
134
|
+
return "";
|
|
135
|
+
const header = headers.find((h) => h.name?.toLowerCase() === name.toLowerCase());
|
|
136
|
+
return header?.value || "";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Format thread content for display
|
|
140
|
+
*/
|
|
141
|
+
export function formatThreadContent(thread) {
|
|
142
|
+
const messages = thread.messages || [];
|
|
143
|
+
const threadId = thread.id || "";
|
|
144
|
+
if (messages.length === 0) {
|
|
145
|
+
return "No messages in thread";
|
|
146
|
+
}
|
|
147
|
+
// Extract thread subject from first message
|
|
148
|
+
const firstMessage = messages[0];
|
|
149
|
+
const subject = getHeader(firstMessage.payload?.headers, "Subject");
|
|
150
|
+
let output = `Thread ID: ${threadId}\n`;
|
|
151
|
+
output += `Subject: ${subject}\n`;
|
|
152
|
+
output += `Messages: ${messages.length}\n`;
|
|
153
|
+
output += `Web URL: ${generateGmailWebUrl(undefined, threadId)}\n\n`;
|
|
154
|
+
output += "=".repeat(80) + "\n\n";
|
|
155
|
+
// Format each message
|
|
156
|
+
messages.forEach((message, index) => {
|
|
157
|
+
const headers = message.payload?.headers || [];
|
|
158
|
+
const from = getHeader(headers, "From");
|
|
159
|
+
const date = getHeader(headers, "Date");
|
|
160
|
+
const msgSubject = getHeader(headers, "Subject");
|
|
161
|
+
output += `Message ${index + 1}/${messages.length}\n`;
|
|
162
|
+
output += `-`.repeat(40) + "\n";
|
|
163
|
+
output += `From: ${from}\n`;
|
|
164
|
+
output += `Date: ${date}\n`;
|
|
165
|
+
// Only show subject if different from thread subject
|
|
166
|
+
if (msgSubject && msgSubject !== subject) {
|
|
167
|
+
output += `Subject: ${msgSubject}\n`;
|
|
168
|
+
}
|
|
169
|
+
output += `\n`;
|
|
170
|
+
// Extract and format body
|
|
171
|
+
if (message.payload) {
|
|
172
|
+
const { textBody, htmlBody } = extractMessageBodies(message.payload);
|
|
173
|
+
const bodyContent = formatBodyContent(textBody, htmlBody);
|
|
174
|
+
// Truncate very long messages
|
|
175
|
+
const maxLength = 2000;
|
|
176
|
+
if (bodyContent.length > maxLength) {
|
|
177
|
+
output += bodyContent.substring(0, maxLength);
|
|
178
|
+
output += `\n\n[... truncated ${bodyContent.length - maxLength} characters ...]`;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
output += bodyContent;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
output += "\n\n";
|
|
185
|
+
});
|
|
186
|
+
return output;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Format message list for search results
|
|
190
|
+
*/
|
|
191
|
+
export function formatMessageList(messages, nextPageToken) {
|
|
192
|
+
let output = `Found ${messages.length} message(s)\n\n`;
|
|
193
|
+
messages.forEach((msg, index) => {
|
|
194
|
+
const messageId = msg.id || "";
|
|
195
|
+
const threadId = msg.threadId || "";
|
|
196
|
+
output += `${index + 1}. Message ID: ${messageId}\n`;
|
|
197
|
+
output += ` Thread ID: ${threadId}\n`;
|
|
198
|
+
output += ` Web URL: ${generateGmailWebUrl(messageId)}\n\n`;
|
|
199
|
+
});
|
|
200
|
+
if (nextPageToken) {
|
|
201
|
+
output += `\nMore results available. Use pageToken: ${nextPageToken}`;
|
|
202
|
+
}
|
|
203
|
+
return output;
|
|
204
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
export const schema = {
|
|
4
|
+
name: "calendar_acl_insert",
|
|
5
|
+
description: "Add a new access control rule to share a calendar with a user, group, or domain. Specify the access level (owner, writer, reader, freeBusyReader) and the scope (who gets access). Use this to grant calendar permissions to others. The system can optionally send notification emails to inform users about the new sharing.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
calendarId: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Calendar identifier. Use 'primary' for the user's primary calendar.",
|
|
12
|
+
},
|
|
13
|
+
role: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Access level: 'owner', 'writer', 'reader', 'freeBusyReader', or 'none'",
|
|
16
|
+
},
|
|
17
|
+
scopeType: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Scope type: 'user' (individual), 'group' (Google group), 'domain' (entire domain), or 'default' (public)",
|
|
20
|
+
},
|
|
21
|
+
scopeValue: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Email address (for user/group) or domain name (for domain). Omit for 'default' scope type.",
|
|
24
|
+
},
|
|
25
|
+
sendNotifications: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Whether to send email notifications about the sharing change (default: true)",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["calendarId", "role", "scopeType"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export async function insertAcl(args) {
|
|
34
|
+
try {
|
|
35
|
+
const calendar = google.calendar("v3");
|
|
36
|
+
const { calendarId, role, scopeType, scopeValue, sendNotifications = true, } = args;
|
|
37
|
+
// Validate scope
|
|
38
|
+
if ((scopeType === "user" || scopeType === "group" || scopeType === "domain") && !scopeValue) {
|
|
39
|
+
return ResponseFormatter.error(new Error(`scopeValue is required for scopeType '${scopeType}'. Provide an email address or domain name.`));
|
|
40
|
+
}
|
|
41
|
+
if (scopeType === "default" && scopeValue) {
|
|
42
|
+
return ResponseFormatter.error(new Error("scopeValue should not be provided for scopeType 'default' (public access)."));
|
|
43
|
+
}
|
|
44
|
+
const response = await calendar.acl.insert({
|
|
45
|
+
calendarId,
|
|
46
|
+
sendNotifications,
|
|
47
|
+
requestBody: {
|
|
48
|
+
role,
|
|
49
|
+
scope: {
|
|
50
|
+
type: scopeType,
|
|
51
|
+
value: scopeValue,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const rule = response.data;
|
|
56
|
+
let message = `✅ Access control rule added successfully\n\n`;
|
|
57
|
+
message += `📅 Calendar: ${calendarId}\n`;
|
|
58
|
+
message += `Role: ${rule.role?.toUpperCase()}\n`;
|
|
59
|
+
message += `Scope: ${rule.scope?.type}`;
|
|
60
|
+
if (rule.scope?.value) {
|
|
61
|
+
message += ` (${rule.scope.value})`;
|
|
62
|
+
}
|
|
63
|
+
message += `\n`;
|
|
64
|
+
message += `Rule ID: ${rule.id}\n`;
|
|
65
|
+
if (sendNotifications && rule.scope?.value) {
|
|
66
|
+
message += `\n📧 Notification email sent to ${rule.scope.value}`;
|
|
67
|
+
}
|
|
68
|
+
message += `\n\n💡 The specified user/group now has ${role} access to this calendar.`;
|
|
69
|
+
return ResponseFormatter.success({
|
|
70
|
+
ruleId: rule.id,
|
|
71
|
+
calendarId,
|
|
72
|
+
role: rule.role,
|
|
73
|
+
scopeType: rule.scope?.type,
|
|
74
|
+
scopeValue: rule.scope?.value || null,
|
|
75
|
+
}, message);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return ResponseFormatter.error(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
export const schema = {
|
|
4
|
+
name: "calendar_acl_list",
|
|
5
|
+
description: "List all access control rules (sharing settings) for a calendar. Shows who has access to the calendar and their permission levels (owner, writer, reader, freeBusyReader). Use this to view current sharing configuration before adding or removing permissions.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
calendarId: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Calendar identifier. Use 'primary' for the user's primary calendar.",
|
|
12
|
+
},
|
|
13
|
+
maxResults: {
|
|
14
|
+
type: "number",
|
|
15
|
+
description: "Maximum number of ACL entries to return per page (default: 100, max: 250)",
|
|
16
|
+
},
|
|
17
|
+
pageToken: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Token for accessing subsequent result pages",
|
|
20
|
+
},
|
|
21
|
+
showDeleted: {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
description: "Include deleted ACL entries (default: false)",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ["calendarId"],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function listAcl(args) {
|
|
30
|
+
try {
|
|
31
|
+
const calendar = google.calendar("v3");
|
|
32
|
+
const { calendarId, maxResults, pageToken, showDeleted, } = args;
|
|
33
|
+
const response = await calendar.acl.list({
|
|
34
|
+
calendarId,
|
|
35
|
+
maxResults,
|
|
36
|
+
pageToken,
|
|
37
|
+
showDeleted,
|
|
38
|
+
});
|
|
39
|
+
const rules = response.data.items || [];
|
|
40
|
+
const totalRules = rules.length;
|
|
41
|
+
if (totalRules === 0) {
|
|
42
|
+
return ResponseFormatter.success({ rules: [], count: 0 }, "No access control rules found for this calendar.");
|
|
43
|
+
}
|
|
44
|
+
let message = `🔐 Calendar Access Control List (${totalRules} rule${totalRules > 1 ? "s" : ""})\n\n`;
|
|
45
|
+
rules.forEach((rule, index) => {
|
|
46
|
+
message += `${index + 1}. ${rule.role?.toUpperCase()}\n`;
|
|
47
|
+
message += ` Scope: ${rule.scope?.type}`;
|
|
48
|
+
if (rule.scope?.value) {
|
|
49
|
+
message += ` (${rule.scope.value})`;
|
|
50
|
+
}
|
|
51
|
+
message += `\n`;
|
|
52
|
+
message += ` Rule ID: ${rule.id}\n`;
|
|
53
|
+
if (rule.scope?.type === "user" || rule.scope?.type === "group") {
|
|
54
|
+
message += ` Email: ${rule.scope.value}\n`;
|
|
55
|
+
}
|
|
56
|
+
else if (rule.scope?.type === "domain") {
|
|
57
|
+
message += ` Domain: ${rule.scope.value}\n`;
|
|
58
|
+
}
|
|
59
|
+
else if (rule.scope?.type === "default") {
|
|
60
|
+
message += ` Public Access\n`;
|
|
61
|
+
}
|
|
62
|
+
message += `\n`;
|
|
63
|
+
});
|
|
64
|
+
if (response.data.nextPageToken) {
|
|
65
|
+
message += `📄 Next Page Token: ${response.data.nextPageToken}\n`;
|
|
66
|
+
}
|
|
67
|
+
message += `\n💡 Role levels: owner > writer > reader > freeBusyReader`;
|
|
68
|
+
return ResponseFormatter.success({
|
|
69
|
+
rules: rules.map((rule) => ({
|
|
70
|
+
id: rule.id,
|
|
71
|
+
role: rule.role,
|
|
72
|
+
scopeType: rule.scope?.type,
|
|
73
|
+
scopeValue: rule.scope?.value || null,
|
|
74
|
+
})),
|
|
75
|
+
count: totalRules,
|
|
76
|
+
nextPageToken: response.data.nextPageToken || null,
|
|
77
|
+
}, message);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return ResponseFormatter.error(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { formatEvent, parseDateTime, formatAttendees, validateTimeRange } from "../../../lib/calendar-helpers.js";
|
|
4
|
+
export const schema = {
|
|
5
|
+
name: "calendar_create_event",
|
|
6
|
+
description: "Create a new event in Google Calendar with specified details. Supports setting title, time, location, description, attendees, and conference data (Google Meet). Can create all-day events or timed events with specific start/end times.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
calendarId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Calendar identifier (default: 'primary' for user's main calendar)",
|
|
13
|
+
},
|
|
14
|
+
summary: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Event title/summary",
|
|
17
|
+
},
|
|
18
|
+
startTime: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Event start time (ISO 8601 format: '2024-01-15T10:00:00Z' or date only: '2024-01-15' for all-day)",
|
|
21
|
+
},
|
|
22
|
+
endTime: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Event end time (ISO 8601 format: '2024-01-15T11:00:00Z' or date only: '2024-01-16' for all-day)",
|
|
25
|
+
},
|
|
26
|
+
location: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Event location (address or place name)",
|
|
29
|
+
},
|
|
30
|
+
description: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Event description (supports plain text and HTML)",
|
|
33
|
+
},
|
|
34
|
+
attendees: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: {
|
|
37
|
+
type: "string",
|
|
38
|
+
},
|
|
39
|
+
description: "List of attendee email addresses",
|
|
40
|
+
},
|
|
41
|
+
sendUpdates: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Whether to send event notifications: 'all' (send to all attendees), 'externalOnly' (external only), 'none' (no notifications). Default: 'none'",
|
|
44
|
+
},
|
|
45
|
+
conferenceData: {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description: "Whether to create a Google Meet conference link (default: false)",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ["summary", "startTime", "endTime"],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export async function createEvent(args) {
|
|
54
|
+
try {
|
|
55
|
+
const calendar = google.calendar("v3");
|
|
56
|
+
const { calendarId = "primary", summary, startTime, endTime, location, description, attendees, sendUpdates = "none", conferenceData = false, } = args;
|
|
57
|
+
// Validate time range
|
|
58
|
+
const validation = validateTimeRange(startTime, endTime);
|
|
59
|
+
if (!validation.valid) {
|
|
60
|
+
return ResponseFormatter.error(new Error(validation.error));
|
|
61
|
+
}
|
|
62
|
+
// Parse start and end times
|
|
63
|
+
const start = parseDateTime(startTime);
|
|
64
|
+
const end = parseDateTime(endTime);
|
|
65
|
+
// Build event resource
|
|
66
|
+
const eventResource = {
|
|
67
|
+
summary,
|
|
68
|
+
start,
|
|
69
|
+
end,
|
|
70
|
+
};
|
|
71
|
+
if (location) {
|
|
72
|
+
eventResource.location = location;
|
|
73
|
+
}
|
|
74
|
+
if (description) {
|
|
75
|
+
eventResource.description = description;
|
|
76
|
+
}
|
|
77
|
+
if (attendees && attendees.length > 0) {
|
|
78
|
+
eventResource.attendees = formatAttendees(attendees);
|
|
79
|
+
}
|
|
80
|
+
if (conferenceData) {
|
|
81
|
+
eventResource.conferenceData = {
|
|
82
|
+
createRequest: {
|
|
83
|
+
requestId: `meet-${Date.now()}`,
|
|
84
|
+
conferenceSolutionKey: {
|
|
85
|
+
type: "hangoutsMeet",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const response = await calendar.events.insert({
|
|
91
|
+
calendarId,
|
|
92
|
+
requestBody: eventResource,
|
|
93
|
+
sendUpdates,
|
|
94
|
+
conferenceDataVersion: conferenceData ? 1 : undefined,
|
|
95
|
+
});
|
|
96
|
+
const event = response.data;
|
|
97
|
+
if (!event) {
|
|
98
|
+
return ResponseFormatter.error(new Error("Failed to create event"));
|
|
99
|
+
}
|
|
100
|
+
const formattedOutput = formatEvent(event, calendarId);
|
|
101
|
+
return ResponseFormatter.success({
|
|
102
|
+
id: event.id,
|
|
103
|
+
summary: event.summary,
|
|
104
|
+
start: event.start,
|
|
105
|
+
end: event.end,
|
|
106
|
+
htmlLink: event.htmlLink,
|
|
107
|
+
conferenceData: event.conferenceData,
|
|
108
|
+
}, `Event created successfully:\n\n${formattedOutput}`);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return ResponseFormatter.error(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
export const schema = {
|
|
4
|
+
name: "calendar_delete_event",
|
|
5
|
+
description: "Delete a calendar event permanently. This action cannot be undone. Optionally send notifications to attendees about the event cancellation.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
calendarId: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "Calendar identifier (default: 'primary' for user's main calendar)",
|
|
12
|
+
},
|
|
13
|
+
eventId: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Event identifier to delete",
|
|
16
|
+
},
|
|
17
|
+
sendUpdates: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Whether to send event cancellation notifications: 'all', 'externalOnly', 'none'. Default: 'all'",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: ["eventId"],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export async function deleteEvent(args) {
|
|
26
|
+
try {
|
|
27
|
+
const calendar = google.calendar("v3");
|
|
28
|
+
const { calendarId = "primary", eventId, sendUpdates = "all", } = args;
|
|
29
|
+
// Get event details before deletion for confirmation message
|
|
30
|
+
const eventResponse = await calendar.events.get({
|
|
31
|
+
calendarId,
|
|
32
|
+
eventId,
|
|
33
|
+
});
|
|
34
|
+
const event = eventResponse.data;
|
|
35
|
+
const eventTitle = event.summary || "(No title)";
|
|
36
|
+
// Delete the event
|
|
37
|
+
await calendar.events.delete({
|
|
38
|
+
calendarId,
|
|
39
|
+
eventId,
|
|
40
|
+
sendUpdates,
|
|
41
|
+
});
|
|
42
|
+
return ResponseFormatter.success({
|
|
43
|
+
deleted: true,
|
|
44
|
+
eventId,
|
|
45
|
+
calendarId,
|
|
46
|
+
summary: eventTitle,
|
|
47
|
+
}, `Event "${eventTitle}" (ID: ${eventId}) has been deleted successfully.`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return ResponseFormatter.error(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { formatEvent } from "../../../lib/calendar-helpers.js";
|
|
4
|
+
export const schema = {
|
|
5
|
+
name: "calendar_get_event",
|
|
6
|
+
description: "Get detailed information about a specific calendar event by its ID. Returns comprehensive event details including title, time, location, description, attendees, recurrence rules, and conference data (Google Meet links).",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
calendarId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Calendar identifier (default: 'primary' for user's main calendar)",
|
|
13
|
+
},
|
|
14
|
+
eventId: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Event identifier to retrieve",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ["eventId"],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export async function getEvent(args) {
|
|
23
|
+
try {
|
|
24
|
+
const calendar = google.calendar("v3");
|
|
25
|
+
const { calendarId = "primary", eventId } = args;
|
|
26
|
+
const response = await calendar.events.get({
|
|
27
|
+
calendarId,
|
|
28
|
+
eventId,
|
|
29
|
+
});
|
|
30
|
+
const event = response.data;
|
|
31
|
+
if (!event) {
|
|
32
|
+
return ResponseFormatter.error(new Error(`Event not found: ${eventId}`));
|
|
33
|
+
}
|
|
34
|
+
const formattedOutput = formatEvent(event, calendarId);
|
|
35
|
+
return ResponseFormatter.success({
|
|
36
|
+
id: event.id,
|
|
37
|
+
summary: event.summary,
|
|
38
|
+
start: event.start,
|
|
39
|
+
end: event.end,
|
|
40
|
+
location: event.location,
|
|
41
|
+
description: event.description,
|
|
42
|
+
attendees: event.attendees,
|
|
43
|
+
htmlLink: event.htmlLink,
|
|
44
|
+
conferenceData: event.conferenceData,
|
|
45
|
+
recurrence: event.recurrence,
|
|
46
|
+
status: event.status,
|
|
47
|
+
}, `Event details:\n\n${formattedOutput}`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return ResponseFormatter.error(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { formatEventList } from "../../../lib/calendar-helpers.js";
|
|
4
|
+
export const schema = {
|
|
5
|
+
name: "calendar_list_events",
|
|
6
|
+
description: "List events from a Google Calendar. Returns upcoming events with details including title, time, location, attendees, and meeting links. Supports filtering by time range, search query, and pagination.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
calendarId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Calendar identifier (default: 'primary' for user's main calendar)",
|
|
13
|
+
},
|
|
14
|
+
timeMin: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Lower bound (inclusive) for event start time (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')",
|
|
17
|
+
},
|
|
18
|
+
timeMax: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Upper bound (exclusive) for event end time (ISO 8601 format)",
|
|
21
|
+
},
|
|
22
|
+
q: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Free text search terms to find events matching title, description, location, attendee emails, etc.",
|
|
25
|
+
},
|
|
26
|
+
maxResults: {
|
|
27
|
+
type: "number",
|
|
28
|
+
description: "Maximum number of events to return (default: 10, max: 250)",
|
|
29
|
+
},
|
|
30
|
+
pageToken: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Token for pagination to get the next page of results",
|
|
33
|
+
},
|
|
34
|
+
singleEvents: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Whether to expand recurring events into instances (default: true)",
|
|
37
|
+
},
|
|
38
|
+
orderBy: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Order of events: 'startTime' (chronological) or 'updated' (modification time). Requires singleEvents=true for 'startTime'",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: [],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
export async function listEvents(args) {
|
|
47
|
+
try {
|
|
48
|
+
const calendar = google.calendar("v3");
|
|
49
|
+
const { calendarId = "primary", timeMin, timeMax, q, maxResults = 10, pageToken, singleEvents = true, orderBy = "startTime", } = args;
|
|
50
|
+
const response = await calendar.events.list({
|
|
51
|
+
calendarId,
|
|
52
|
+
timeMin,
|
|
53
|
+
timeMax,
|
|
54
|
+
q,
|
|
55
|
+
maxResults: Math.min(maxResults, 250),
|
|
56
|
+
pageToken,
|
|
57
|
+
singleEvents,
|
|
58
|
+
orderBy: singleEvents ? orderBy : undefined,
|
|
59
|
+
});
|
|
60
|
+
const events = response.data.items || [];
|
|
61
|
+
const nextPageToken = response.data.nextPageToken;
|
|
62
|
+
if (events.length === 0) {
|
|
63
|
+
return ResponseFormatter.success({
|
|
64
|
+
count: 0,
|
|
65
|
+
events: [],
|
|
66
|
+
calendarId,
|
|
67
|
+
}, `No events found in calendar: ${calendarId}`);
|
|
68
|
+
}
|
|
69
|
+
const formattedOutput = formatEventList(events, calendarId);
|
|
70
|
+
return ResponseFormatter.success({
|
|
71
|
+
count: events.length,
|
|
72
|
+
calendarId,
|
|
73
|
+
events: events.map((e) => ({
|
|
74
|
+
id: e.id,
|
|
75
|
+
summary: e.summary,
|
|
76
|
+
start: e.start,
|
|
77
|
+
end: e.end,
|
|
78
|
+
htmlLink: e.htmlLink,
|
|
79
|
+
})),
|
|
80
|
+
nextPageToken,
|
|
81
|
+
}, `Events from ${calendarId}:\n\n${formattedOutput}`);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return ResponseFormatter.error(error);
|
|
85
|
+
}
|
|
86
|
+
}
|