gmcp 0.4.0 → 0.5.0
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 +2 -1
- package/dist/index.js +267 -164
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
148
148
|
|
|
149
149
|
## Tools
|
|
150
150
|
|
|
151
|
-
### Gmail (
|
|
151
|
+
### Gmail (17 tools)
|
|
152
152
|
|
|
153
153
|
| Tool | Description |
|
|
154
154
|
|------|-------------|
|
|
@@ -161,6 +161,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
161
161
|
| `gmcp_gmail_reply` | Reply to email in thread |
|
|
162
162
|
| `gmcp_gmail_create_draft` | Create draft message |
|
|
163
163
|
| `gmcp_gmail_delete_email` | Permanently delete email (bypasses trash) |
|
|
164
|
+
| `gmcp_gmail_archive_email` | Archive email (remove from inbox) |
|
|
164
165
|
| `gmcp_gmail_list_labels` | List all labels |
|
|
165
166
|
| `gmcp_gmail_get_label` | Get label details |
|
|
166
167
|
| `gmcp_gmail_create_label` | Create custom label |
|
package/dist/index.js
CHANGED
|
@@ -739,7 +739,7 @@ function registerTools(server, client, tools, logger) {
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
|
-
// src/tools/
|
|
742
|
+
// src/tools/archive-email.ts
|
|
743
743
|
import json2md from "json2md";
|
|
744
744
|
import { z } from "zod";
|
|
745
745
|
|
|
@@ -775,13 +775,108 @@ function createErrorResponse(context, error, logger) {
|
|
|
775
775
|
};
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
-
// src/tools/
|
|
779
|
-
var
|
|
780
|
-
|
|
781
|
-
add_labels: z.array(z.string()).optional().describe("Label IDs to add to all messages (e.g., ['STARRED', 'IMPORTANT'])"),
|
|
782
|
-
remove_labels: z.array(z.string()).optional().describe("Label IDs to remove from all messages (e.g., ['UNREAD', 'INBOX'])"),
|
|
778
|
+
// src/tools/archive-email.ts
|
|
779
|
+
var ArchiveEmailInputSchema = z.object({
|
|
780
|
+
message_id: z.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to archive"),
|
|
783
781
|
output_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
784
782
|
});
|
|
783
|
+
function archiveToMarkdown(email) {
|
|
784
|
+
const sections = [
|
|
785
|
+
{ h1: "Email Archived Successfully" },
|
|
786
|
+
{ h2: "Message Details" },
|
|
787
|
+
{
|
|
788
|
+
ul: [
|
|
789
|
+
`**Subject:** ${email.subject}`,
|
|
790
|
+
`**From:** ${email.from}`,
|
|
791
|
+
`**Message ID:** ${email.id}`
|
|
792
|
+
]
|
|
793
|
+
},
|
|
794
|
+
{ h2: "Current Labels" },
|
|
795
|
+
{
|
|
796
|
+
p: email.labels.length > 0 ? email.labels.join(", ") : "*No labels on this message*"
|
|
797
|
+
},
|
|
798
|
+
{ h2: "Notes" },
|
|
799
|
+
{
|
|
800
|
+
ul: [
|
|
801
|
+
"The email has been removed from your inbox",
|
|
802
|
+
"The email is still accessible in All Mail",
|
|
803
|
+
"To unarchive, add the INBOX label back to the message"
|
|
804
|
+
]
|
|
805
|
+
}
|
|
806
|
+
];
|
|
807
|
+
return json2md(sections);
|
|
808
|
+
}
|
|
809
|
+
async function archiveEmailTool(gmailClient, params) {
|
|
810
|
+
try {
|
|
811
|
+
const email = await gmailClient.modifyLabels(params.message_id, undefined, [
|
|
812
|
+
"INBOX"
|
|
813
|
+
]);
|
|
814
|
+
const formattedEmail = formatEmailForOutput(email);
|
|
815
|
+
const output = {
|
|
816
|
+
message_id: params.message_id,
|
|
817
|
+
subject: email.subject,
|
|
818
|
+
archived: true,
|
|
819
|
+
removed_labels: ["INBOX"],
|
|
820
|
+
current_labels: email.labels || []
|
|
821
|
+
};
|
|
822
|
+
const textContent = params.output_format === "json" ? JSON.stringify(output, null, 2) : archiveToMarkdown({
|
|
823
|
+
...formattedEmail,
|
|
824
|
+
labels: email.labels || []
|
|
825
|
+
});
|
|
826
|
+
return {
|
|
827
|
+
content: [{ type: "text", text: textContent }],
|
|
828
|
+
structuredContent: output
|
|
829
|
+
};
|
|
830
|
+
} catch (error) {
|
|
831
|
+
return createErrorResponse("archiving email", error);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
var ARCHIVE_EMAIL_DESCRIPTION = `Archive a Gmail email by removing it from the inbox.
|
|
835
|
+
|
|
836
|
+
This tool archives an email message by removing the INBOX label. The email remains accessible in "All Mail" and can be unarchived by adding the INBOX label back.
|
|
837
|
+
|
|
838
|
+
**Parameters**:
|
|
839
|
+
- \`message_id\` (string, required): The Gmail message ID to archive
|
|
840
|
+
- \`output_format\` (string, optional): Output format: "markdown" (default) or "json"
|
|
841
|
+
|
|
842
|
+
**Returns**:
|
|
843
|
+
- \`message_id\`: The archived message ID
|
|
844
|
+
- \`subject\`: Email subject
|
|
845
|
+
- \`archived\`: Always true on success
|
|
846
|
+
- \`removed_labels\`: Labels that were removed (always ["INBOX"])
|
|
847
|
+
- \`current_labels\`: All current labels on the message after archiving
|
|
848
|
+
|
|
849
|
+
**Examples**:
|
|
850
|
+
- Archive email: \`{ "message_id": "18f3c5d4e8a2b1c0" }\`
|
|
851
|
+
- Archive with JSON output: \`{ "message_id": "18f3c5d4e8a2b1c0", "output_format": "json" }\`
|
|
852
|
+
|
|
853
|
+
**Important Notes**:
|
|
854
|
+
- This is a reversible operation - archived emails can be unarchived
|
|
855
|
+
- The email is moved out of the inbox but remains in "All Mail"
|
|
856
|
+
- To unarchive, use \`modify_labels\` with \`add_labels: ["INBOX"]\`
|
|
857
|
+
- This is different from \`delete_email\` which permanently removes the message
|
|
858
|
+
|
|
859
|
+
**Use Cases**:
|
|
860
|
+
- Clean up inbox without deleting emails
|
|
861
|
+
- Archive emails after reading/processing them
|
|
862
|
+
- Remove emails from inbox while keeping them for reference
|
|
863
|
+
|
|
864
|
+
**Error Handling**:
|
|
865
|
+
- Returns error if message ID doesn't exist
|
|
866
|
+
- Returns error if authentication lacks sufficient permissions
|
|
867
|
+
|
|
868
|
+
**Scope Requirements**:
|
|
869
|
+
- Requires \`gmail.modify\` or full \`mail.google.com\` scope`;
|
|
870
|
+
|
|
871
|
+
// src/tools/batch-modify.ts
|
|
872
|
+
import json2md2 from "json2md";
|
|
873
|
+
import { z as z2 } from "zod";
|
|
874
|
+
var BatchModifyInputSchema = z2.object({
|
|
875
|
+
message_ids: z2.array(z2.string()).min(1, "Must provide at least one message ID").max(1000, "Maximum 1000 messages per batch").describe("Array of Gmail message IDs to modify (max 1000)"),
|
|
876
|
+
add_labels: z2.array(z2.string()).optional().describe("Label IDs to add to all messages (e.g., ['STARRED', 'IMPORTANT'])"),
|
|
877
|
+
remove_labels: z2.array(z2.string()).optional().describe("Label IDs to remove from all messages (e.g., ['UNREAD', 'INBOX'])"),
|
|
878
|
+
output_format: z2.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
879
|
+
});
|
|
785
880
|
function batchModificationToMarkdown(messageCount, addedLabels, removedLabels) {
|
|
786
881
|
const sections = [
|
|
787
882
|
{ h1: "Batch Label Modification Successful" },
|
|
@@ -798,7 +893,7 @@ function batchModificationToMarkdown(messageCount, addedLabels, removedLabels) {
|
|
|
798
893
|
sections.push({
|
|
799
894
|
p: `All ${messageCount} messages have been updated successfully.`
|
|
800
895
|
});
|
|
801
|
-
return
|
|
896
|
+
return json2md2(sections);
|
|
802
897
|
}
|
|
803
898
|
async function batchModifyTool(gmailClient, params) {
|
|
804
899
|
try {
|
|
@@ -890,10 +985,10 @@ This tool allows you to efficiently add or remove labels from multiple Gmail mes
|
|
|
890
985
|
- Recommended for operations affecting more than 5 messages`;
|
|
891
986
|
|
|
892
987
|
// src/tools/calendar-create.ts
|
|
893
|
-
import { z as
|
|
988
|
+
import { z as z3 } from "zod";
|
|
894
989
|
|
|
895
990
|
// src/utils/markdown.ts
|
|
896
|
-
import
|
|
991
|
+
import json2md3 from "json2md";
|
|
897
992
|
function emailToJson2md(email) {
|
|
898
993
|
const elements = [
|
|
899
994
|
{ h2: email.subject },
|
|
@@ -935,7 +1030,7 @@ function searchResultsToMarkdown(query, data) {
|
|
|
935
1030
|
});
|
|
936
1031
|
}
|
|
937
1032
|
}
|
|
938
|
-
return
|
|
1033
|
+
return json2md3(elements);
|
|
939
1034
|
}
|
|
940
1035
|
function calendarListToMarkdown(data) {
|
|
941
1036
|
const sections = [
|
|
@@ -961,7 +1056,7 @@ function calendarListToMarkdown(data) {
|
|
|
961
1056
|
sections.push({ ul: details });
|
|
962
1057
|
}
|
|
963
1058
|
}
|
|
964
|
-
return
|
|
1059
|
+
return json2md3(sections);
|
|
965
1060
|
}
|
|
966
1061
|
function formatEventTime(time) {
|
|
967
1062
|
if (time.date) {
|
|
@@ -1028,7 +1123,7 @@ function eventListToMarkdown(data) {
|
|
|
1028
1123
|
sections.push({ hr: "" });
|
|
1029
1124
|
}
|
|
1030
1125
|
}
|
|
1031
|
-
return
|
|
1126
|
+
return json2md3(sections);
|
|
1032
1127
|
}
|
|
1033
1128
|
function formatAttendeeDetailed(att) {
|
|
1034
1129
|
const name = att.display_name || att.email;
|
|
@@ -1076,23 +1171,23 @@ function eventToMarkdown(event, successMessage) {
|
|
|
1076
1171
|
sections.push({ ul: event.attendees.map(formatAttendeeDetailed) });
|
|
1077
1172
|
}
|
|
1078
1173
|
addEventMetadata(sections, event);
|
|
1079
|
-
return
|
|
1174
|
+
return json2md3(sections);
|
|
1080
1175
|
}
|
|
1081
1176
|
|
|
1082
1177
|
// src/tools/calendar-create.ts
|
|
1083
|
-
var CalendarCreateEventInputSchema =
|
|
1084
|
-
calendar_id:
|
|
1085
|
-
summary:
|
|
1086
|
-
start:
|
|
1087
|
-
end:
|
|
1088
|
-
description:
|
|
1089
|
-
location:
|
|
1090
|
-
attendees:
|
|
1091
|
-
timezone:
|
|
1092
|
-
recurrence:
|
|
1093
|
-
add_meet:
|
|
1094
|
-
confirm:
|
|
1095
|
-
output_format:
|
|
1178
|
+
var CalendarCreateEventInputSchema = z3.object({
|
|
1179
|
+
calendar_id: z3.string().default("primary").describe('Calendar ID to create event in (default: "primary")'),
|
|
1180
|
+
summary: z3.string().min(1, "Event title is required").describe("Event title/summary (required)"),
|
|
1181
|
+
start: z3.string().min(1, "Start time is required").describe('Start time: RFC3339 (e.g., "2024-01-15T09:00:00-08:00") or date for all-day (e.g., "2024-01-15")'),
|
|
1182
|
+
end: z3.string().min(1, "End time is required").describe('End time: RFC3339 (e.g., "2024-01-15T10:00:00-08:00") or date for all-day (e.g., "2024-01-15")'),
|
|
1183
|
+
description: z3.string().optional().describe("Event description (optional)"),
|
|
1184
|
+
location: z3.string().optional().describe("Event location (optional)"),
|
|
1185
|
+
attendees: z3.array(z3.string().email()).optional().describe("Array of attendee email addresses (optional)"),
|
|
1186
|
+
timezone: z3.string().optional().describe('IANA timezone (e.g., "America/Los_Angeles", optional)'),
|
|
1187
|
+
recurrence: z3.array(z3.string()).optional().describe('Recurrence rules as RRULE strings (e.g., ["RRULE:FREQ=WEEKLY;COUNT=10"], optional)'),
|
|
1188
|
+
add_meet: z3.boolean().default(false).describe("Auto-create Google Meet conference link (default: false)"),
|
|
1189
|
+
confirm: z3.boolean().default(false).describe("Must be true to create the event (safety check)"),
|
|
1190
|
+
output_format: z3.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1096
1191
|
});
|
|
1097
1192
|
async function calendarCreateEventTool(calendarClient, params) {
|
|
1098
1193
|
if (!params.confirm) {
|
|
@@ -1243,16 +1338,16 @@ Created event object containing:
|
|
|
1243
1338
|
- Returns error if Calendar API request fails`;
|
|
1244
1339
|
|
|
1245
1340
|
// src/tools/calendar-events.ts
|
|
1246
|
-
import { z as
|
|
1247
|
-
var CalendarEventsInputSchema =
|
|
1248
|
-
calendar_id:
|
|
1249
|
-
time_min:
|
|
1250
|
-
time_max:
|
|
1251
|
-
max_results:
|
|
1252
|
-
query:
|
|
1253
|
-
single_events:
|
|
1254
|
-
order_by:
|
|
1255
|
-
output_format:
|
|
1341
|
+
import { z as z4 } from "zod";
|
|
1342
|
+
var CalendarEventsInputSchema = z4.object({
|
|
1343
|
+
calendar_id: z4.string().default("primary").describe('Calendar ID to list events from (default: "primary")'),
|
|
1344
|
+
time_min: z4.string().optional().describe("Lower bound for event start time (RFC3339, e.g., 2024-01-01T00:00:00Z)"),
|
|
1345
|
+
time_max: z4.string().optional().describe("Upper bound for event start time (RFC3339, e.g., 2024-12-31T23:59:59Z)"),
|
|
1346
|
+
max_results: z4.number().int().min(1).max(250).default(10).describe("Maximum number of events to return (default: 10, max: 250)"),
|
|
1347
|
+
query: z4.string().optional().describe("Free text search query to filter events"),
|
|
1348
|
+
single_events: z4.boolean().default(true).describe("Expand recurring events into individual instances (default: true)"),
|
|
1349
|
+
order_by: z4.enum(["startTime", "updated"]).default("startTime").describe('Sort order: "startTime" (default) or "updated"'),
|
|
1350
|
+
output_format: z4.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1256
1351
|
});
|
|
1257
1352
|
async function calendarEventsTool(calendarClient, params) {
|
|
1258
1353
|
try {
|
|
@@ -1350,11 +1445,11 @@ This tool retrieves events from a specified calendar with support for time range
|
|
|
1350
1445
|
- Returns empty array if no events match filters`;
|
|
1351
1446
|
|
|
1352
1447
|
// src/tools/calendar-get-event.ts
|
|
1353
|
-
import { z as
|
|
1354
|
-
var CalendarGetEventInputSchema =
|
|
1355
|
-
calendar_id:
|
|
1356
|
-
event_id:
|
|
1357
|
-
output_format:
|
|
1448
|
+
import { z as z5 } from "zod";
|
|
1449
|
+
var CalendarGetEventInputSchema = z5.object({
|
|
1450
|
+
calendar_id: z5.string().default("primary").describe('Calendar ID (default: "primary")'),
|
|
1451
|
+
event_id: z5.string().min(1, "Event ID is required").describe("Event ID to retrieve"),
|
|
1452
|
+
output_format: z5.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1358
1453
|
});
|
|
1359
1454
|
async function calendarGetEventTool(calendarClient, params) {
|
|
1360
1455
|
try {
|
|
@@ -1446,10 +1541,10 @@ Complete event object containing:
|
|
|
1446
1541
|
- Returns error if Calendar API request fails`;
|
|
1447
1542
|
|
|
1448
1543
|
// src/tools/calendar-list.ts
|
|
1449
|
-
import { z as
|
|
1450
|
-
var CalendarListInputSchema =
|
|
1451
|
-
show_hidden:
|
|
1452
|
-
output_format:
|
|
1544
|
+
import { z as z6 } from "zod";
|
|
1545
|
+
var CalendarListInputSchema = z6.object({
|
|
1546
|
+
show_hidden: z6.boolean().default(false).describe("Include hidden calendars in the results (default: false)"),
|
|
1547
|
+
output_format: z6.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1453
1548
|
});
|
|
1454
1549
|
async function calendarListTool(calendarClient, params) {
|
|
1455
1550
|
try {
|
|
@@ -1511,16 +1606,16 @@ This tool retrieves all calendars accessible by the authenticated user, includin
|
|
|
1511
1606
|
- Returns error if Calendar API request fails`;
|
|
1512
1607
|
|
|
1513
1608
|
// src/tools/create-draft.ts
|
|
1514
|
-
import
|
|
1515
|
-
import { z as
|
|
1516
|
-
var CreateDraftInputSchema =
|
|
1517
|
-
to:
|
|
1518
|
-
subject:
|
|
1519
|
-
body:
|
|
1520
|
-
content_type:
|
|
1521
|
-
cc:
|
|
1522
|
-
bcc:
|
|
1523
|
-
output_format:
|
|
1609
|
+
import json2md4 from "json2md";
|
|
1610
|
+
import { z as z7 } from "zod";
|
|
1611
|
+
var CreateDraftInputSchema = z7.object({
|
|
1612
|
+
to: z7.email("Must be a valid email address").describe("Recipient email address"),
|
|
1613
|
+
subject: z7.string().min(1, "Subject cannot be empty").describe("Email subject"),
|
|
1614
|
+
body: z7.string().min(1, "Body cannot be empty").describe("Email body content"),
|
|
1615
|
+
content_type: z7.enum(["text/plain", "text/html"]).default("text/plain").describe("Content type: text/plain (default) or text/html for HTML emails"),
|
|
1616
|
+
cc: z7.email("CC must be a valid email address").optional().describe("CC (carbon copy) email address"),
|
|
1617
|
+
bcc: z7.email("BCC must be a valid email address").optional().describe("BCC (blind carbon copy) email address"),
|
|
1618
|
+
output_format: z7.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1524
1619
|
});
|
|
1525
1620
|
function draftCreatedToMarkdown(params, result) {
|
|
1526
1621
|
const sections = [
|
|
@@ -1540,7 +1635,7 @@ function draftCreatedToMarkdown(params, result) {
|
|
|
1540
1635
|
p: "The draft has been saved and will appear in your Drafts folder. You can edit and send it later from Gmail."
|
|
1541
1636
|
}
|
|
1542
1637
|
];
|
|
1543
|
-
return
|
|
1638
|
+
return json2md4(sections);
|
|
1544
1639
|
}
|
|
1545
1640
|
async function createDraftTool(gmailClient, params) {
|
|
1546
1641
|
try {
|
|
@@ -1634,15 +1729,15 @@ This tool creates a draft email in your Gmail Drafts folder. Unlike sending an e
|
|
|
1634
1729
|
- Collaborative: Others with access can review drafts`;
|
|
1635
1730
|
|
|
1636
1731
|
// src/tools/create-label.ts
|
|
1637
|
-
import
|
|
1638
|
-
import { z as
|
|
1639
|
-
var CreateLabelInputSchema =
|
|
1640
|
-
name:
|
|
1641
|
-
message_list_visibility:
|
|
1642
|
-
label_list_visibility:
|
|
1643
|
-
background_color:
|
|
1644
|
-
text_color:
|
|
1645
|
-
output_format:
|
|
1732
|
+
import json2md5 from "json2md";
|
|
1733
|
+
import { z as z8 } from "zod";
|
|
1734
|
+
var CreateLabelInputSchema = z8.object({
|
|
1735
|
+
name: z8.string().min(1, "Label name cannot be empty").describe("The label name (e.g., 'Work', 'Personal/Family'). Use '/' for nested labels."),
|
|
1736
|
+
message_list_visibility: z8.enum(["show", "hide"]).optional().describe("How label appears in message list. Default: 'show'. Use 'hide' to hide messages with this label from message list."),
|
|
1737
|
+
label_list_visibility: z8.enum(["labelShow", "labelShowIfUnread", "labelHide"]).optional().describe("How label appears in label list. Default: 'labelShow'. Options: 'labelShow' (always visible), 'labelShowIfUnread' (only when unread), 'labelHide' (hidden)."),
|
|
1738
|
+
background_color: z8.string().optional().describe("Background color in hex format (e.g., '#ff0000'). Must provide both background and text color together."),
|
|
1739
|
+
text_color: z8.string().optional().describe("Text color in hex format (e.g., '#ffffff'). Must provide both background and text color together."),
|
|
1740
|
+
output_format: z8.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1646
1741
|
});
|
|
1647
1742
|
function createdLabelToMarkdown(label) {
|
|
1648
1743
|
const sections = [
|
|
@@ -1679,7 +1774,7 @@ function createdLabelToMarkdown(label) {
|
|
|
1679
1774
|
sections.push({
|
|
1680
1775
|
p: `You can now use this label ID (${label.id}) with other tools like modify_labels or batch_modify.`
|
|
1681
1776
|
});
|
|
1682
|
-
return
|
|
1777
|
+
return json2md5(sections);
|
|
1683
1778
|
}
|
|
1684
1779
|
async function createLabelTool(gmailClient, params) {
|
|
1685
1780
|
try {
|
|
@@ -1771,11 +1866,11 @@ This tool creates a new label with customizable visibility and color settings. L
|
|
|
1771
1866
|
- Requires \`gmail.labels\` or \`gmail.modify\` scope`;
|
|
1772
1867
|
|
|
1773
1868
|
// src/tools/delete-email.ts
|
|
1774
|
-
import
|
|
1775
|
-
import { z as
|
|
1776
|
-
var DeleteEmailInputSchema =
|
|
1777
|
-
message_id:
|
|
1778
|
-
output_format:
|
|
1869
|
+
import json2md6 from "json2md";
|
|
1870
|
+
import { z as z9 } from "zod";
|
|
1871
|
+
var DeleteEmailInputSchema = z9.object({
|
|
1872
|
+
message_id: z9.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to permanently delete"),
|
|
1873
|
+
output_format: z9.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1779
1874
|
});
|
|
1780
1875
|
function deletionToMarkdown(messageId) {
|
|
1781
1876
|
const sections = [
|
|
@@ -1792,7 +1887,7 @@ function deletionToMarkdown(messageId) {
|
|
|
1792
1887
|
]
|
|
1793
1888
|
}
|
|
1794
1889
|
];
|
|
1795
|
-
return
|
|
1890
|
+
return json2md6(sections);
|
|
1796
1891
|
}
|
|
1797
1892
|
async function deleteEmailTool(gmailClient, params) {
|
|
1798
1893
|
try {
|
|
@@ -1850,8 +1945,8 @@ This tool permanently deletes an email message from your Gmail account. Unlike m
|
|
|
1850
1945
|
- Requires \`gmail.modify\` or full \`mail.google.com\` scope`;
|
|
1851
1946
|
|
|
1852
1947
|
// src/tools/delete-label.ts
|
|
1853
|
-
import
|
|
1854
|
-
import { z as
|
|
1948
|
+
import json2md7 from "json2md";
|
|
1949
|
+
import { z as z10 } from "zod";
|
|
1855
1950
|
var SYSTEM_LABELS = [
|
|
1856
1951
|
"INBOX",
|
|
1857
1952
|
"SENT",
|
|
@@ -1867,9 +1962,9 @@ var SYSTEM_LABELS = [
|
|
|
1867
1962
|
"CATEGORY_UPDATES",
|
|
1868
1963
|
"CATEGORY_FORUMS"
|
|
1869
1964
|
];
|
|
1870
|
-
var DeleteLabelInputSchema =
|
|
1871
|
-
label_id:
|
|
1872
|
-
output_format:
|
|
1965
|
+
var DeleteLabelInputSchema = z10.object({
|
|
1966
|
+
label_id: z10.string().min(1, "Label ID cannot be empty").describe("The label ID to delete (e.g., 'Label_123'). Cannot delete system labels."),
|
|
1967
|
+
output_format: z10.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1873
1968
|
});
|
|
1874
1969
|
function deletionToMarkdown2(labelId) {
|
|
1875
1970
|
const sections = [
|
|
@@ -1887,7 +1982,7 @@ function deletionToMarkdown2(labelId) {
|
|
|
1887
1982
|
]
|
|
1888
1983
|
}
|
|
1889
1984
|
];
|
|
1890
|
-
return
|
|
1985
|
+
return json2md7(sections);
|
|
1891
1986
|
}
|
|
1892
1987
|
async function deleteLabelTool(gmailClient, params) {
|
|
1893
1988
|
try {
|
|
@@ -2009,11 +2104,11 @@ This tool permanently deletes a user-created label from your Gmail account. Syst
|
|
|
2009
2104
|
If you want to hide a label instead of deleting it, consider using the \`update_label\` tool to set \`label_list_visibility\` to "labelHide" instead.`;
|
|
2010
2105
|
|
|
2011
2106
|
// src/tools/get-attachment.ts
|
|
2012
|
-
import { z as
|
|
2013
|
-
var GetAttachmentInputSchema =
|
|
2014
|
-
message_id:
|
|
2015
|
-
attachment_id:
|
|
2016
|
-
output_format:
|
|
2107
|
+
import { z as z11 } from "zod";
|
|
2108
|
+
var GetAttachmentInputSchema = z11.object({
|
|
2109
|
+
message_id: z11.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID containing the attachment"),
|
|
2110
|
+
attachment_id: z11.string().min(1, "Attachment ID cannot be empty").describe("The attachment ID to download (from list_attachments)"),
|
|
2111
|
+
output_format: z11.enum(["base64", "json"]).default("base64").describe("Output format: base64 (default, returns raw base64url string) or json (returns structured object)")
|
|
2017
2112
|
});
|
|
2018
2113
|
async function getAttachmentTool(gmailClient, params) {
|
|
2019
2114
|
try {
|
|
@@ -2080,12 +2175,12 @@ To decode:
|
|
|
2080
2175
|
3. Decode base64url data to get original file content`;
|
|
2081
2176
|
|
|
2082
2177
|
// src/tools/get-email.ts
|
|
2083
|
-
import
|
|
2084
|
-
import { z as
|
|
2085
|
-
var GetEmailInputSchema =
|
|
2086
|
-
message_id:
|
|
2087
|
-
include_body:
|
|
2088
|
-
output_format:
|
|
2178
|
+
import json2md8 from "json2md";
|
|
2179
|
+
import { z as z12 } from "zod";
|
|
2180
|
+
var GetEmailInputSchema = z12.object({
|
|
2181
|
+
message_id: z12.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to retrieve"),
|
|
2182
|
+
include_body: z12.boolean().default(true).describe("Whether to include full email body in results (default: true)"),
|
|
2183
|
+
output_format: z12.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2089
2184
|
});
|
|
2090
2185
|
function emailToMarkdown(email) {
|
|
2091
2186
|
const sections = [
|
|
@@ -2112,7 +2207,7 @@ function emailToMarkdown(email) {
|
|
|
2112
2207
|
sections.push({ h2: "Snippet" });
|
|
2113
2208
|
sections.push({ p: email.snippet });
|
|
2114
2209
|
}
|
|
2115
|
-
return
|
|
2210
|
+
return json2md8(sections);
|
|
2116
2211
|
}
|
|
2117
2212
|
async function getEmailTool(gmailClient, params) {
|
|
2118
2213
|
try {
|
|
@@ -2163,11 +2258,11 @@ This tool retrieves a specific email message from Gmail using its unique message
|
|
|
2163
2258
|
- Retrieve email for further processing or analysis`;
|
|
2164
2259
|
|
|
2165
2260
|
// src/tools/get-label.ts
|
|
2166
|
-
import
|
|
2167
|
-
import { z as
|
|
2168
|
-
var GetLabelInputSchema =
|
|
2169
|
-
label_id:
|
|
2170
|
-
output_format:
|
|
2261
|
+
import json2md9 from "json2md";
|
|
2262
|
+
import { z as z13 } from "zod";
|
|
2263
|
+
var GetLabelInputSchema = z13.object({
|
|
2264
|
+
label_id: z13.string().min(1, "Label ID cannot be empty").describe("The label ID to retrieve (e.g., 'INBOX', 'Label_123')"),
|
|
2265
|
+
output_format: z13.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2171
2266
|
});
|
|
2172
2267
|
function labelToMarkdown(label) {
|
|
2173
2268
|
const sections = [
|
|
@@ -2202,7 +2297,7 @@ function labelToMarkdown(label) {
|
|
|
2202
2297
|
]
|
|
2203
2298
|
});
|
|
2204
2299
|
}
|
|
2205
|
-
return
|
|
2300
|
+
return json2md9(sections);
|
|
2206
2301
|
}
|
|
2207
2302
|
async function getLabelTool(gmailClient, params) {
|
|
2208
2303
|
try {
|
|
@@ -2269,12 +2364,12 @@ This tool retrieves comprehensive details about a label, including its name, typ
|
|
|
2269
2364
|
- System labels and custom labels are both supported`;
|
|
2270
2365
|
|
|
2271
2366
|
// src/tools/get-thread.ts
|
|
2272
|
-
import
|
|
2273
|
-
import { z as
|
|
2274
|
-
var GetThreadInputSchema =
|
|
2275
|
-
thread_id:
|
|
2276
|
-
include_body:
|
|
2277
|
-
output_format:
|
|
2367
|
+
import json2md10 from "json2md";
|
|
2368
|
+
import { z as z14 } from "zod";
|
|
2369
|
+
var GetThreadInputSchema = z14.object({
|
|
2370
|
+
thread_id: z14.string().min(1, "Thread ID cannot be empty").describe("The Gmail thread ID to retrieve"),
|
|
2371
|
+
include_body: z14.boolean().default(false).describe("Whether to include full email body for all messages (default: false)"),
|
|
2372
|
+
output_format: z14.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2278
2373
|
});
|
|
2279
2374
|
function threadToMarkdown(threadId, messages) {
|
|
2280
2375
|
const sections = [
|
|
@@ -2309,7 +2404,7 @@ function threadToMarkdown(threadId, messages) {
|
|
|
2309
2404
|
sections.push({ p: "---" });
|
|
2310
2405
|
}
|
|
2311
2406
|
}
|
|
2312
|
-
return
|
|
2407
|
+
return json2md10(sections);
|
|
2313
2408
|
}
|
|
2314
2409
|
async function getThreadTool(gmailClient, params) {
|
|
2315
2410
|
try {
|
|
@@ -2367,11 +2462,11 @@ This tool retrieves all messages in a Gmail conversation thread (email chain). T
|
|
|
2367
2462
|
- Extract all messages from a discussion thread`;
|
|
2368
2463
|
|
|
2369
2464
|
// src/tools/list-attachments.ts
|
|
2370
|
-
import
|
|
2371
|
-
import { z as
|
|
2372
|
-
var ListAttachmentsInputSchema =
|
|
2373
|
-
message_id:
|
|
2374
|
-
output_format:
|
|
2465
|
+
import json2md11 from "json2md";
|
|
2466
|
+
import { z as z15 } from "zod";
|
|
2467
|
+
var ListAttachmentsInputSchema = z15.object({
|
|
2468
|
+
message_id: z15.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to list attachments from"),
|
|
2469
|
+
output_format: z15.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2375
2470
|
});
|
|
2376
2471
|
function formatBytes(bytes) {
|
|
2377
2472
|
if (bytes === 0) {
|
|
@@ -2403,7 +2498,7 @@ function attachmentsToMarkdown(messageId, attachments) {
|
|
|
2403
2498
|
});
|
|
2404
2499
|
}
|
|
2405
2500
|
}
|
|
2406
|
-
return
|
|
2501
|
+
return json2md11(sections);
|
|
2407
2502
|
}
|
|
2408
2503
|
async function listAttachmentsTool(gmailClient, params) {
|
|
2409
2504
|
try {
|
|
@@ -2461,10 +2556,10 @@ This tool retrieves metadata about all attachments in a specific email message,
|
|
|
2461
2556
|
- List attachment IDs for use with get_attachment tool`;
|
|
2462
2557
|
|
|
2463
2558
|
// src/tools/list-labels.ts
|
|
2464
|
-
import
|
|
2465
|
-
import { z as
|
|
2466
|
-
var ListLabelsInputSchema =
|
|
2467
|
-
output_format:
|
|
2559
|
+
import json2md12 from "json2md";
|
|
2560
|
+
import { z as z16 } from "zod";
|
|
2561
|
+
var ListLabelsInputSchema = z16.object({
|
|
2562
|
+
output_format: z16.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2468
2563
|
});
|
|
2469
2564
|
function labelsToMarkdown(labels) {
|
|
2470
2565
|
const sections = [{ h1: "Gmail Labels" }];
|
|
@@ -2481,7 +2576,7 @@ function labelsToMarkdown(labels) {
|
|
|
2481
2576
|
if (labels.system.length === 0 && labels.user.length === 0) {
|
|
2482
2577
|
sections.push({ p: "*No labels found*" });
|
|
2483
2578
|
}
|
|
2484
|
-
return
|
|
2579
|
+
return json2md12(sections);
|
|
2485
2580
|
}
|
|
2486
2581
|
async function listLabelsTool(gmailClient, params) {
|
|
2487
2582
|
try {
|
|
@@ -2554,13 +2649,13 @@ Custom labels are created by users for organizing emails. They have IDs like \`L
|
|
|
2554
2649
|
- Returns error if Gmail API is unreachable`;
|
|
2555
2650
|
|
|
2556
2651
|
// src/tools/modify-labels.ts
|
|
2557
|
-
import
|
|
2558
|
-
import { z as
|
|
2559
|
-
var ModifyLabelsInputSchema =
|
|
2560
|
-
message_id:
|
|
2561
|
-
add_labels:
|
|
2562
|
-
remove_labels:
|
|
2563
|
-
output_format:
|
|
2652
|
+
import json2md13 from "json2md";
|
|
2653
|
+
import { z as z17 } from "zod";
|
|
2654
|
+
var ModifyLabelsInputSchema = z17.object({
|
|
2655
|
+
message_id: z17.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to modify labels for"),
|
|
2656
|
+
add_labels: z17.array(z17.string()).optional().describe("Label IDs to add (e.g., ['STARRED', 'INBOX', 'UNREAD'] or custom label IDs)"),
|
|
2657
|
+
remove_labels: z17.array(z17.string()).optional().describe("Label IDs to remove (e.g., ['UNREAD', 'INBOX'] to mark read and archive)"),
|
|
2658
|
+
output_format: z17.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2564
2659
|
});
|
|
2565
2660
|
function labelModificationToMarkdown(email, addedLabels, removedLabels) {
|
|
2566
2661
|
const sections = [
|
|
@@ -2586,7 +2681,7 @@ function labelModificationToMarkdown(email, addedLabels, removedLabels) {
|
|
|
2586
2681
|
sections.push({
|
|
2587
2682
|
p: email.labels.length > 0 ? email.labels.join(", ") : "*No labels on this message*"
|
|
2588
2683
|
});
|
|
2589
|
-
return
|
|
2684
|
+
return json2md13(sections);
|
|
2590
2685
|
}
|
|
2591
2686
|
async function modifyLabelsTool(gmailClient, params) {
|
|
2592
2687
|
try {
|
|
@@ -2677,15 +2772,15 @@ This tool allows you to add or remove labels from a Gmail message. Labels are us
|
|
|
2677
2772
|
- Move messages to trash or spam`;
|
|
2678
2773
|
|
|
2679
2774
|
// src/tools/reply.ts
|
|
2680
|
-
import
|
|
2681
|
-
import { z as
|
|
2682
|
-
var ReplyInputSchema =
|
|
2683
|
-
message_id:
|
|
2684
|
-
body:
|
|
2685
|
-
content_type:
|
|
2686
|
-
cc:
|
|
2687
|
-
confirm:
|
|
2688
|
-
output_format:
|
|
2775
|
+
import json2md14 from "json2md";
|
|
2776
|
+
import { z as z18 } from "zod";
|
|
2777
|
+
var ReplyInputSchema = z18.object({
|
|
2778
|
+
message_id: z18.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to reply to"),
|
|
2779
|
+
body: z18.string().min(1, "Reply body cannot be empty").describe("Reply message body"),
|
|
2780
|
+
content_type: z18.enum(["text/plain", "text/html"]).default("text/plain").describe("Content type: text/plain (default) or text/html for HTML replies"),
|
|
2781
|
+
cc: z18.email("CC must be a valid email address").optional().describe("CC (carbon copy) email address"),
|
|
2782
|
+
confirm: z18.boolean().default(false).describe("Set to true to confirm and send the reply. If false, returns preview only."),
|
|
2783
|
+
output_format: z18.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2689
2784
|
});
|
|
2690
2785
|
function replyPreviewToMarkdown(originalEmail, replyBody, contentType, cc) {
|
|
2691
2786
|
const sections = [
|
|
@@ -2713,7 +2808,7 @@ function replyPreviewToMarkdown(originalEmail, replyBody, contentType, cc) {
|
|
|
2713
2808
|
{ h2: "Reply Body" },
|
|
2714
2809
|
{ p: replyBody }
|
|
2715
2810
|
];
|
|
2716
|
-
return
|
|
2811
|
+
return json2md14(sections);
|
|
2717
2812
|
}
|
|
2718
2813
|
function replySentToMarkdown(originalEmail, result, cc) {
|
|
2719
2814
|
const sections = [
|
|
@@ -2732,7 +2827,7 @@ function replySentToMarkdown(originalEmail, result, cc) {
|
|
|
2732
2827
|
p: "Your reply has been sent and added to the conversation thread."
|
|
2733
2828
|
}
|
|
2734
2829
|
];
|
|
2735
|
-
return
|
|
2830
|
+
return json2md14(sections);
|
|
2736
2831
|
}
|
|
2737
2832
|
async function replyTool(gmailClient, params) {
|
|
2738
2833
|
try {
|
|
@@ -2856,13 +2951,13 @@ This tool sends a reply to an existing email, automatically threading it in the
|
|
|
2856
2951
|
4. Call with \`confirm: true\` to send`;
|
|
2857
2952
|
|
|
2858
2953
|
// src/tools/search.ts
|
|
2859
|
-
import { z as
|
|
2860
|
-
var SearchEmailsInputSchema =
|
|
2861
|
-
query:
|
|
2862
|
-
max_results:
|
|
2863
|
-
include_body:
|
|
2864
|
-
page_token:
|
|
2865
|
-
output_format:
|
|
2954
|
+
import { z as z19 } from "zod";
|
|
2955
|
+
var SearchEmailsInputSchema = z19.object({
|
|
2956
|
+
query: z19.string().min(1, "Query cannot be empty").describe('Gmail search query using Gmail search syntax (e.g., "from:user@example.com subject:test is:unread")'),
|
|
2957
|
+
max_results: z19.number().int().min(1).max(100).default(10).describe("Maximum number of emails to return (default: 10, max: 100)"),
|
|
2958
|
+
include_body: z19.boolean().default(false).describe("Whether to include full email body in results (default: false)"),
|
|
2959
|
+
page_token: z19.string().optional().describe("Token for pagination to fetch next page of results"),
|
|
2960
|
+
output_format: z19.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2866
2961
|
});
|
|
2867
2962
|
async function searchEmailsTool(gmailClient, params) {
|
|
2868
2963
|
try {
|
|
@@ -2932,17 +3027,17 @@ This tool allows you to search through your Gmail messages using the same query
|
|
|
2932
3027
|
- Empty results if no emails match the query`;
|
|
2933
3028
|
|
|
2934
3029
|
// src/tools/send-email.ts
|
|
2935
|
-
import
|
|
2936
|
-
import { z as
|
|
2937
|
-
var SendEmailInputSchema =
|
|
2938
|
-
to:
|
|
2939
|
-
subject:
|
|
2940
|
-
body:
|
|
2941
|
-
content_type:
|
|
2942
|
-
cc:
|
|
2943
|
-
bcc:
|
|
2944
|
-
confirm:
|
|
2945
|
-
output_format:
|
|
3030
|
+
import json2md15 from "json2md";
|
|
3031
|
+
import { z as z20 } from "zod";
|
|
3032
|
+
var SendEmailInputSchema = z20.object({
|
|
3033
|
+
to: z20.email("Must be a valid email address").describe("Recipient email address"),
|
|
3034
|
+
subject: z20.string().min(1, "Subject cannot be empty").describe("Email subject"),
|
|
3035
|
+
body: z20.string().min(1, "Body cannot be empty").describe("Email body content"),
|
|
3036
|
+
content_type: z20.enum(["text/plain", "text/html"]).default("text/plain").describe("Content type: text/plain (default) or text/html for HTML emails"),
|
|
3037
|
+
cc: z20.email("CC must be a valid email address").optional().describe("CC (carbon copy) email address"),
|
|
3038
|
+
bcc: z20.email("BCC must be a valid email address").optional().describe("BCC (blind carbon copy) email address"),
|
|
3039
|
+
confirm: z20.boolean().default(false).describe("Set to true to confirm and send the email. If false, returns preview only."),
|
|
3040
|
+
output_format: z20.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2946
3041
|
});
|
|
2947
3042
|
function emailPreviewToMarkdown(params) {
|
|
2948
3043
|
const sections = [
|
|
@@ -2963,7 +3058,7 @@ function emailPreviewToMarkdown(params) {
|
|
|
2963
3058
|
{ h2: "Body" },
|
|
2964
3059
|
{ p: params.body }
|
|
2965
3060
|
];
|
|
2966
|
-
return
|
|
3061
|
+
return json2md15(sections);
|
|
2967
3062
|
}
|
|
2968
3063
|
function emailSentToMarkdown(params, result) {
|
|
2969
3064
|
const sections = [
|
|
@@ -2983,7 +3078,7 @@ function emailSentToMarkdown(params, result) {
|
|
|
2983
3078
|
p: "The email has been sent and will appear in your Sent folder."
|
|
2984
3079
|
}
|
|
2985
3080
|
];
|
|
2986
|
-
return
|
|
3081
|
+
return json2md15(sections);
|
|
2987
3082
|
}
|
|
2988
3083
|
async function sendEmailTool(gmailClient, params) {
|
|
2989
3084
|
try {
|
|
@@ -3089,8 +3184,8 @@ This tool sends email through your Gmail account. It includes a safety feature:
|
|
|
3089
3184
|
3. Call again with \`confirm: true\` to send`;
|
|
3090
3185
|
|
|
3091
3186
|
// src/tools/update-label.ts
|
|
3092
|
-
import
|
|
3093
|
-
import { z as
|
|
3187
|
+
import json2md16 from "json2md";
|
|
3188
|
+
import { z as z21 } from "zod";
|
|
3094
3189
|
var SYSTEM_LABELS2 = [
|
|
3095
3190
|
"INBOX",
|
|
3096
3191
|
"SENT",
|
|
@@ -3101,14 +3196,14 @@ var SYSTEM_LABELS2 = [
|
|
|
3101
3196
|
"IMPORTANT",
|
|
3102
3197
|
"UNREAD"
|
|
3103
3198
|
];
|
|
3104
|
-
var UpdateLabelInputSchema =
|
|
3105
|
-
label_id:
|
|
3106
|
-
name:
|
|
3107
|
-
message_list_visibility:
|
|
3108
|
-
label_list_visibility:
|
|
3109
|
-
background_color:
|
|
3110
|
-
text_color:
|
|
3111
|
-
output_format:
|
|
3199
|
+
var UpdateLabelInputSchema = z21.object({
|
|
3200
|
+
label_id: z21.string().min(1, "Label ID cannot be empty").describe("The label ID to update (e.g., 'Label_123' or 'INBOX')"),
|
|
3201
|
+
name: z21.string().optional().describe("New label name. Cannot rename system labels. Use '/' for nested labels."),
|
|
3202
|
+
message_list_visibility: z21.enum(["show", "hide"]).optional().describe("How label appears in message list"),
|
|
3203
|
+
label_list_visibility: z21.enum(["labelShow", "labelShowIfUnread", "labelHide"]).optional().describe("How label appears in label list (sidebar)"),
|
|
3204
|
+
background_color: z21.string().optional().describe("Background color in hex format (e.g., '#ff0000'). Must provide both background and text color together."),
|
|
3205
|
+
text_color: z21.string().optional().describe("Text color in hex format (e.g., '#ffffff'). Must provide both background and text color together."),
|
|
3206
|
+
output_format: z21.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
3112
3207
|
});
|
|
3113
3208
|
function updatedLabelToMarkdown(label, updates) {
|
|
3114
3209
|
const sections = [
|
|
@@ -3156,7 +3251,7 @@ function updatedLabelToMarkdown(label, updates) {
|
|
|
3156
3251
|
]
|
|
3157
3252
|
});
|
|
3158
3253
|
}
|
|
3159
|
-
return
|
|
3254
|
+
return json2md16(sections);
|
|
3160
3255
|
}
|
|
3161
3256
|
async function updateLabelTool(gmailClient, params) {
|
|
3162
3257
|
try {
|
|
@@ -3434,6 +3529,14 @@ async function startServer() {
|
|
|
3434
3529
|
inputSchema: DeleteEmailInputSchema,
|
|
3435
3530
|
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
3436
3531
|
handler: deleteEmailTool
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
name: "gmcp_gmail_archive_email",
|
|
3535
|
+
title: "Archive Gmail Email",
|
|
3536
|
+
description: ARCHIVE_EMAIL_DESCRIPTION,
|
|
3537
|
+
inputSchema: ArchiveEmailInputSchema,
|
|
3538
|
+
annotations: MODIFY_ANNOTATIONS,
|
|
3539
|
+
handler: archiveEmailTool
|
|
3437
3540
|
}
|
|
3438
3541
|
];
|
|
3439
3542
|
const calendarTools = [
|