@dguido/google-workspace-mcp 1.0.0 → 1.1.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/LICENSE +1 -1
- package/README.md +40 -14
- package/dist/index.js +199 -188
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -95,6 +95,28 @@ Create a presentation called "Product Roadmap" with slides for Q1 milestones.
|
|
|
95
95
|
|
|
96
96
|
Tokens are stored at `~/.config/google-workspace-mcp/tokens.json` by default.
|
|
97
97
|
|
|
98
|
+
### Token-Efficient Output (TOON)
|
|
99
|
+
|
|
100
|
+
For LLM-optimized responses that reduce token usage by 20-50%, enable TOON format:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"google-workspace": {
|
|
106
|
+
"command": "npx",
|
|
107
|
+
"args": ["@dguido/google-workspace-mcp"],
|
|
108
|
+
"env": {
|
|
109
|
+
"GOOGLE_DRIVE_OAUTH_CREDENTIALS": "/path/to/gcp-oauth.keys.json",
|
|
110
|
+
"GOOGLE_WORKSPACE_SERVICES": "drive,gmail,calendar",
|
|
111
|
+
"GOOGLE_WORKSPACE_TOON_FORMAT": "true"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
TOON (Token-Oriented Object Notation) encodes structured responses more compactly than JSON by eliminating repeated field names. Savings are highest for list operations (calendars, events, emails, filters).
|
|
119
|
+
|
|
98
120
|
### Service Configuration
|
|
99
121
|
|
|
100
122
|
By default, we recommend enabling only the core services (`drive,gmail,calendar`) as shown in Quick Start. This provides file management, email, and calendar capabilities without the complexity of document editing tools.
|
|
@@ -125,25 +147,33 @@ See [Advanced Configuration](docs/ADVANCED.md) for multi-account setup and envir
|
|
|
125
147
|
|
|
126
148
|
## Available Tools
|
|
127
149
|
|
|
128
|
-
### Drive
|
|
150
|
+
### Drive (29 tools)
|
|
151
|
+
|
|
152
|
+
`search` `listFolder` `createFolder` `createTextFile` `updateTextFile` `deleteItem` `renameItem` `moveItem` `copyFile` `getFileMetadata` `exportFile` `shareFile` `getSharing` `removePermission` `listRevisions` `restoreRevision` `downloadFile` `uploadFile` `getStorageQuota` `starFile` `resolveFilePath` `batchDelete` `batchRestore` `batchMove` `batchShare` `listTrash` `restoreFromTrash` `emptyTrash` `getFolderTree`
|
|
129
153
|
|
|
130
|
-
|
|
154
|
+
### Google Docs (8 tools)
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
`createGoogleDoc` `updateGoogleDoc` `getGoogleDocContent` `appendToDoc` `insertTextInDoc` `deleteTextInDoc` `replaceTextInDoc` `formatGoogleDocRange`
|
|
133
157
|
|
|
134
|
-
|
|
158
|
+
### Google Sheets (7 tools)
|
|
135
159
|
|
|
136
|
-
|
|
160
|
+
`createGoogleSheet` `updateGoogleSheet` `getGoogleSheetContent` `formatGoogleSheetCells` `mergeGoogleSheetCells` `addGoogleSheetConditionalFormat` `sheetTabs`
|
|
137
161
|
|
|
138
|
-
|
|
162
|
+
### Google Slides (10 tools)
|
|
139
163
|
|
|
140
|
-
|
|
164
|
+
`createGoogleSlides` `updateGoogleSlides` `getGoogleSlidesContent` `formatSlidesText` `formatSlidesShape` `formatSlideBackground` `createGoogleSlidesTextBox` `createGoogleSlidesShape` `slidesSpeakerNotes` `listSlidePages`
|
|
141
165
|
|
|
142
|
-
|
|
166
|
+
### Calendar (7 tools)
|
|
143
167
|
|
|
144
|
-
|
|
168
|
+
`listCalendars` `listEvents` `getEvent` `createEvent` `updateEvent` `deleteEvent` `findFreeTime`
|
|
145
169
|
|
|
146
|
-
|
|
170
|
+
### Gmail (14 tools)
|
|
171
|
+
|
|
172
|
+
`sendEmail` `draftEmail` `readEmail` `searchEmails` `deleteEmail` `modifyEmail` `downloadAttachment` `listLabels` `getOrCreateLabel` `updateLabel` `deleteLabel` `createFilter` `listFilters` `deleteFilter`
|
|
173
|
+
|
|
174
|
+
### Unified (3 tools)
|
|
175
|
+
|
|
176
|
+
`createFile` `updateFile` `getFileContent`
|
|
147
177
|
|
|
148
178
|
[Full API Reference](docs/API.md)
|
|
149
179
|
|
|
@@ -194,10 +224,6 @@ npm test # Run tests
|
|
|
194
224
|
|
|
195
225
|
See [Contributing Guide](CONTRIBUTING.md) for project structure and development workflow.
|
|
196
226
|
|
|
197
|
-
## Docker
|
|
198
|
-
|
|
199
|
-
See [Docker Usage](docs/DOCKER.md) for containerized deployment.
|
|
200
|
-
|
|
201
227
|
## Links
|
|
202
228
|
|
|
203
229
|
- [GitHub Repository](https://github.com/dguido/google-workspace-mcp)
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,56 @@ var init_logging = __esm({
|
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// src/config/services.ts
|
|
25
|
+
function getEnabledServices() {
|
|
26
|
+
if (enabledServices !== null) return enabledServices;
|
|
27
|
+
const envValue = process.env.GOOGLE_WORKSPACE_SERVICES;
|
|
28
|
+
if (envValue === void 0) {
|
|
29
|
+
enabledServices = new Set(SERVICE_NAMES);
|
|
30
|
+
return enabledServices;
|
|
31
|
+
}
|
|
32
|
+
if (envValue.trim() === "") {
|
|
33
|
+
enabledServices = /* @__PURE__ */ new Set();
|
|
34
|
+
return enabledServices;
|
|
35
|
+
}
|
|
36
|
+
const requested = envValue.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
37
|
+
const valid = /* @__PURE__ */ new Set();
|
|
38
|
+
const unknown = [];
|
|
39
|
+
for (const service of requested) {
|
|
40
|
+
if (SERVICE_NAMES.includes(service)) {
|
|
41
|
+
valid.add(service);
|
|
42
|
+
} else {
|
|
43
|
+
unknown.push(service);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (unknown.length > 0) {
|
|
47
|
+
console.warn(
|
|
48
|
+
`[google-workspace-mcp] Unknown services: ${unknown.join(", ")}. Valid: ${SERVICE_NAMES.join(", ")}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
enabledServices = valid;
|
|
52
|
+
return enabledServices;
|
|
53
|
+
}
|
|
54
|
+
function isServiceEnabled(service) {
|
|
55
|
+
return getEnabledServices().has(service);
|
|
56
|
+
}
|
|
57
|
+
function areUnifiedToolsEnabled() {
|
|
58
|
+
const enabled = getEnabledServices();
|
|
59
|
+
return UNIFIED_REQUIRED_SERVICES.every((s) => enabled.has(s));
|
|
60
|
+
}
|
|
61
|
+
function isToonEnabled() {
|
|
62
|
+
return process.env.GOOGLE_WORKSPACE_TOON_FORMAT === "true";
|
|
63
|
+
}
|
|
64
|
+
var SERVICE_NAMES, UNIFIED_REQUIRED_SERVICES, enabledServices;
|
|
65
|
+
var init_services = __esm({
|
|
66
|
+
"src/config/services.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
SERVICE_NAMES = ["drive", "docs", "sheets", "slides", "calendar", "gmail"];
|
|
69
|
+
UNIFIED_REQUIRED_SERVICES = ["drive", "docs", "sheets", "slides"];
|
|
70
|
+
enabledServices = null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
24
74
|
// src/utils/responses.ts
|
|
25
75
|
function truncateResponse(content) {
|
|
26
76
|
if (content.length <= CHARACTER_LIMIT) {
|
|
@@ -38,11 +88,14 @@ function successResponse(text) {
|
|
|
38
88
|
return { content: [{ type: "text", text }], isError: false };
|
|
39
89
|
}
|
|
40
90
|
function structuredResponse(text, data) {
|
|
41
|
-
|
|
91
|
+
const response = {
|
|
42
92
|
content: [{ type: "text", text }],
|
|
43
|
-
structuredContent: data,
|
|
44
93
|
isError: false
|
|
45
94
|
};
|
|
95
|
+
if (!isToonEnabled()) {
|
|
96
|
+
response.structuredContent = data;
|
|
97
|
+
}
|
|
98
|
+
return response;
|
|
46
99
|
}
|
|
47
100
|
function errorResponse(message, options) {
|
|
48
101
|
log("Error", { message, ...options });
|
|
@@ -62,6 +115,7 @@ var CHARACTER_LIMIT;
|
|
|
62
115
|
var init_responses = __esm({
|
|
63
116
|
"src/utils/responses.ts"() {
|
|
64
117
|
"use strict";
|
|
118
|
+
init_services();
|
|
65
119
|
init_logging();
|
|
66
120
|
CHARACTER_LIMIT = 25e3;
|
|
67
121
|
}
|
|
@@ -137,7 +191,7 @@ function getGmailService(authClient2) {
|
|
|
137
191
|
return gmailService;
|
|
138
192
|
}
|
|
139
193
|
var docsService, sheetsService, slidesService, calendarService, gmailService, lastAuthClient;
|
|
140
|
-
var
|
|
194
|
+
var init_services2 = __esm({
|
|
141
195
|
"src/utils/services.ts"() {
|
|
142
196
|
"use strict";
|
|
143
197
|
docsService = null;
|
|
@@ -776,6 +830,25 @@ var init_mime = __esm({
|
|
|
776
830
|
}
|
|
777
831
|
});
|
|
778
832
|
|
|
833
|
+
// src/utils/toon.ts
|
|
834
|
+
import { encode } from "@toon-format/toon";
|
|
835
|
+
function toToon(data) {
|
|
836
|
+
if (!isToonEnabled()) {
|
|
837
|
+
return JSON.stringify(data, null, 2);
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
return encode(data);
|
|
841
|
+
} catch {
|
|
842
|
+
return JSON.stringify(data, null, 2);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
var init_toon = __esm({
|
|
846
|
+
"src/utils/toon.ts"() {
|
|
847
|
+
"use strict";
|
|
848
|
+
init_services();
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
779
852
|
// src/utils/index.ts
|
|
780
853
|
var utils_exports = {};
|
|
781
854
|
__export(utils_exports, {
|
|
@@ -807,6 +880,7 @@ __export(utils_exports, {
|
|
|
807
880
|
structuredResponse: () => structuredResponse,
|
|
808
881
|
successResponse: () => successResponse,
|
|
809
882
|
supportsFormElicitation: () => supportsFormElicitation,
|
|
883
|
+
toToon: () => toToon,
|
|
810
884
|
truncateResponse: () => truncateResponse,
|
|
811
885
|
validateArgs: () => validateArgs,
|
|
812
886
|
withProgressReporting: () => withProgressReporting,
|
|
@@ -820,13 +894,14 @@ var init_utils = __esm({
|
|
|
820
894
|
init_logging();
|
|
821
895
|
init_responses();
|
|
822
896
|
init_validation();
|
|
823
|
-
|
|
897
|
+
init_services2();
|
|
824
898
|
init_timeout();
|
|
825
899
|
init_retry();
|
|
826
900
|
init_elicitation();
|
|
827
901
|
init_progress();
|
|
828
902
|
init_pathCache();
|
|
829
903
|
init_mime();
|
|
904
|
+
init_toon();
|
|
830
905
|
}
|
|
831
906
|
});
|
|
832
907
|
|
|
@@ -1458,46 +1533,8 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
1458
1533
|
import { readFileSync } from "fs";
|
|
1459
1534
|
import { join as join3, dirname as dirname3 } from "path";
|
|
1460
1535
|
|
|
1461
|
-
// src/config/
|
|
1462
|
-
|
|
1463
|
-
var UNIFIED_REQUIRED_SERVICES = ["drive", "docs", "sheets", "slides"];
|
|
1464
|
-
var enabledServices = null;
|
|
1465
|
-
function getEnabledServices() {
|
|
1466
|
-
if (enabledServices !== null) return enabledServices;
|
|
1467
|
-
const envValue = process.env.GOOGLE_WORKSPACE_SERVICES;
|
|
1468
|
-
if (envValue === void 0) {
|
|
1469
|
-
enabledServices = new Set(SERVICE_NAMES);
|
|
1470
|
-
return enabledServices;
|
|
1471
|
-
}
|
|
1472
|
-
if (envValue.trim() === "") {
|
|
1473
|
-
enabledServices = /* @__PURE__ */ new Set();
|
|
1474
|
-
return enabledServices;
|
|
1475
|
-
}
|
|
1476
|
-
const requested = envValue.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
1477
|
-
const valid = /* @__PURE__ */ new Set();
|
|
1478
|
-
const unknown = [];
|
|
1479
|
-
for (const service of requested) {
|
|
1480
|
-
if (SERVICE_NAMES.includes(service)) {
|
|
1481
|
-
valid.add(service);
|
|
1482
|
-
} else {
|
|
1483
|
-
unknown.push(service);
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
if (unknown.length > 0) {
|
|
1487
|
-
console.warn(
|
|
1488
|
-
`[google-workspace-mcp] Unknown services: ${unknown.join(", ")}. Valid: ${SERVICE_NAMES.join(", ")}`
|
|
1489
|
-
);
|
|
1490
|
-
}
|
|
1491
|
-
enabledServices = valid;
|
|
1492
|
-
return enabledServices;
|
|
1493
|
-
}
|
|
1494
|
-
function isServiceEnabled(service) {
|
|
1495
|
-
return getEnabledServices().has(service);
|
|
1496
|
-
}
|
|
1497
|
-
function areUnifiedToolsEnabled() {
|
|
1498
|
-
const enabled = getEnabledServices();
|
|
1499
|
-
return UNIFIED_REQUIRED_SERVICES.every((s) => enabled.has(s));
|
|
1500
|
-
}
|
|
1536
|
+
// src/config/index.ts
|
|
1537
|
+
init_services();
|
|
1501
1538
|
|
|
1502
1539
|
// src/tools/definitions.ts
|
|
1503
1540
|
var driveTools = [
|
|
@@ -5109,9 +5146,6 @@ function formatBytes(bytes, options = {}) {
|
|
|
5109
5146
|
const formatted = value.toFixed(precision);
|
|
5110
5147
|
return `${formatted} ${BYTE_UNITS[unitIndex]}`;
|
|
5111
5148
|
}
|
|
5112
|
-
function formatBytesCompact(bytes, nullValue = "N/A") {
|
|
5113
|
-
return formatBytes(bytes, { precision: 1, nullValue });
|
|
5114
|
-
}
|
|
5115
5149
|
|
|
5116
5150
|
// src/schemas/drive.ts
|
|
5117
5151
|
import { z } from "zod";
|
|
@@ -6261,19 +6295,29 @@ async function handleSearch(drive2, args) {
|
|
|
6261
6295
|
includeItemsFromAllDrives: true,
|
|
6262
6296
|
supportsAllDrives: true
|
|
6263
6297
|
});
|
|
6264
|
-
const
|
|
6298
|
+
const files = res.data.files?.map((f) => ({
|
|
6299
|
+
id: f.id,
|
|
6300
|
+
name: f.name,
|
|
6301
|
+
mimeType: f.mimeType,
|
|
6302
|
+
modifiedTime: f.modifiedTime,
|
|
6303
|
+
size: f.size
|
|
6304
|
+
})) || [];
|
|
6265
6305
|
log("Search results", {
|
|
6266
6306
|
query: userQuery,
|
|
6267
|
-
resultCount:
|
|
6307
|
+
resultCount: files.length
|
|
6268
6308
|
});
|
|
6269
|
-
let
|
|
6270
|
-
|
|
6309
|
+
let textResponse = `Found ${files.length} files:
|
|
6310
|
+
|
|
6311
|
+
${toToon({ files })}`;
|
|
6271
6312
|
if (res.data.nextPageToken) {
|
|
6272
|
-
|
|
6313
|
+
textResponse += `
|
|
6273
6314
|
|
|
6274
6315
|
More results available. Use pageToken: ${res.data.nextPageToken}`;
|
|
6275
6316
|
}
|
|
6276
|
-
return
|
|
6317
|
+
return structuredResponse(textResponse, {
|
|
6318
|
+
files,
|
|
6319
|
+
nextPageToken: res.data.nextPageToken || null
|
|
6320
|
+
});
|
|
6277
6321
|
}
|
|
6278
6322
|
async function handleCreateTextFile(drive2, args) {
|
|
6279
6323
|
const validation = validateArgs(CreateTextFileSchema, args);
|
|
@@ -6736,33 +6780,23 @@ async function handleGetSharing(drive2, args) {
|
|
|
6736
6780
|
"List permissions"
|
|
6737
6781
|
);
|
|
6738
6782
|
const permissionList = permissions.data.permissions || [];
|
|
6739
|
-
const
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
}
|
|
6748
|
-
return `\u2022 ${target}: ${p.role} (ID: ${p.id})`;
|
|
6749
|
-
}).join("\n");
|
|
6783
|
+
const permissionData = permissionList.map((p) => ({
|
|
6784
|
+
id: p.id,
|
|
6785
|
+
role: p.role,
|
|
6786
|
+
type: p.type,
|
|
6787
|
+
emailAddress: p.emailAddress,
|
|
6788
|
+
domain: p.domain,
|
|
6789
|
+
displayName: p.displayName
|
|
6790
|
+
}));
|
|
6750
6791
|
const textResponse = `Sharing settings for "${file.data.name}":
|
|
6751
6792
|
|
|
6752
|
-
${
|
|
6793
|
+
${toToon({ permissions: permissionData })}
|
|
6753
6794
|
|
|
6754
6795
|
Link: ${file.data.webViewLink}`;
|
|
6755
6796
|
return structuredResponse(textResponse, {
|
|
6756
6797
|
fileName: file.data.name,
|
|
6757
6798
|
webViewLink: file.data.webViewLink,
|
|
6758
|
-
permissions:
|
|
6759
|
-
id: p.id,
|
|
6760
|
-
role: p.role,
|
|
6761
|
-
type: p.type,
|
|
6762
|
-
emailAddress: p.emailAddress,
|
|
6763
|
-
domain: p.domain,
|
|
6764
|
-
displayName: p.displayName
|
|
6765
|
-
}))
|
|
6799
|
+
permissions: permissionData
|
|
6766
6800
|
});
|
|
6767
6801
|
}
|
|
6768
6802
|
async function handleListRevisions(drive2, args) {
|
|
@@ -6796,27 +6830,22 @@ async function handleListRevisions(drive2, args) {
|
|
|
6796
6830
|
if (revisionList.length === 0) {
|
|
6797
6831
|
return successResponse(`No revisions found for "${file.data.name}".`);
|
|
6798
6832
|
}
|
|
6799
|
-
const
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6833
|
+
const revisionData = revisionList.map((r) => ({
|
|
6834
|
+
id: r.id,
|
|
6835
|
+
modifiedTime: r.modifiedTime,
|
|
6836
|
+
size: r.size,
|
|
6837
|
+
keepForever: r.keepForever,
|
|
6838
|
+
lastModifyingUser: r.lastModifyingUser ? {
|
|
6839
|
+
displayName: r.lastModifyingUser.displayName,
|
|
6840
|
+
emailAddress: r.lastModifyingUser.emailAddress
|
|
6841
|
+
} : void 0
|
|
6842
|
+
}));
|
|
6805
6843
|
const textResponse = `Revisions for "${file.data.name}" (${revisionList.length} found):
|
|
6806
6844
|
|
|
6807
|
-
${
|
|
6845
|
+
${toToon({ revisions: revisionData })}`;
|
|
6808
6846
|
return structuredResponse(textResponse, {
|
|
6809
6847
|
fileName: file.data.name,
|
|
6810
|
-
revisions:
|
|
6811
|
-
id: r.id,
|
|
6812
|
-
modifiedTime: r.modifiedTime,
|
|
6813
|
-
size: r.size,
|
|
6814
|
-
keepForever: r.keepForever,
|
|
6815
|
-
lastModifyingUser: r.lastModifyingUser ? {
|
|
6816
|
-
displayName: r.lastModifyingUser.displayName,
|
|
6817
|
-
emailAddress: r.lastModifyingUser.emailAddress
|
|
6818
|
-
} : void 0
|
|
6819
|
-
}))
|
|
6848
|
+
revisions: revisionData
|
|
6820
6849
|
});
|
|
6821
6850
|
}
|
|
6822
6851
|
async function handleRestoreRevision(drive2, args) {
|
|
@@ -7438,22 +7467,18 @@ async function handleListTrash(drive2, args) {
|
|
|
7438
7467
|
nextPageToken: null
|
|
7439
7468
|
});
|
|
7440
7469
|
}
|
|
7441
|
-
const
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7470
|
+
const fileData = files.map((f) => ({
|
|
7471
|
+
id: f.id,
|
|
7472
|
+
name: f.name,
|
|
7473
|
+
mimeType: f.mimeType,
|
|
7474
|
+
size: f.size,
|
|
7475
|
+
trashedTime: f.trashedTime
|
|
7476
|
+
}));
|
|
7446
7477
|
const textResponse = `Trash contents (${files.length} items):
|
|
7447
7478
|
|
|
7448
|
-
${
|
|
7479
|
+
${toToon({ files: fileData })}` + (response.data.nextPageToken ? "\n\n(More items available - use nextPageToken to continue)" : "");
|
|
7449
7480
|
return structuredResponse(textResponse, {
|
|
7450
|
-
files:
|
|
7451
|
-
id: f.id,
|
|
7452
|
-
name: f.name,
|
|
7453
|
-
mimeType: f.mimeType,
|
|
7454
|
-
size: f.size,
|
|
7455
|
-
trashedTime: f.trashedTime
|
|
7456
|
-
})),
|
|
7481
|
+
files: fileData,
|
|
7457
7482
|
nextPageToken: response.data.nextPageToken || null
|
|
7458
7483
|
});
|
|
7459
7484
|
}
|
|
@@ -8538,9 +8563,9 @@ async function listSheetTabs(sheets, spreadsheetId) {
|
|
|
8538
8563
|
rowCount: sheet.properties?.gridProperties?.rowCount,
|
|
8539
8564
|
columnCount: sheet.properties?.gridProperties?.columnCount
|
|
8540
8565
|
})) || [];
|
|
8541
|
-
const tabList = tabs.map((t) => `${t.index}: ${t.title} (${t.rowCount}x${t.columnCount})`).join("\n");
|
|
8542
8566
|
return structuredResponse(`Spreadsheet has ${tabs.length} tab(s):
|
|
8543
|
-
|
|
8567
|
+
|
|
8568
|
+
${toToon({ tabs })}`, {
|
|
8544
8569
|
spreadsheetId,
|
|
8545
8570
|
tabs
|
|
8546
8571
|
});
|
|
@@ -9749,13 +9774,6 @@ function formatEventTime(eventTime) {
|
|
|
9749
9774
|
}
|
|
9750
9775
|
return "Unknown";
|
|
9751
9776
|
}
|
|
9752
|
-
function formatEventSummary(event) {
|
|
9753
|
-
const start = formatEventTime(event.start);
|
|
9754
|
-
const end = formatEventTime(event.end);
|
|
9755
|
-
const isAllDay = !!event.start?.date;
|
|
9756
|
-
const timeStr = isAllDay ? start : `${start} - ${end}`;
|
|
9757
|
-
return `${event.summary || "(No title)"} | ${timeStr}`;
|
|
9758
|
-
}
|
|
9759
9777
|
async function handleListCalendars(calendar, args) {
|
|
9760
9778
|
const validation = validateArgs(ListCalendarsSchema, args);
|
|
9761
9779
|
if (!validation.success) return validation.response;
|
|
@@ -9768,26 +9786,23 @@ async function handleListCalendars(calendar, args) {
|
|
|
9768
9786
|
if (calendars.length === 0) {
|
|
9769
9787
|
return structuredResponse("No calendars found.", { calendars: [] });
|
|
9770
9788
|
}
|
|
9771
|
-
const
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9789
|
+
const calendarData = calendars.map((cal) => ({
|
|
9790
|
+
id: cal.id,
|
|
9791
|
+
summary: cal.summary,
|
|
9792
|
+
description: cal.description,
|
|
9793
|
+
primary: cal.primary,
|
|
9794
|
+
accessRole: cal.accessRole,
|
|
9795
|
+
backgroundColor: cal.backgroundColor,
|
|
9796
|
+
foregroundColor: cal.foregroundColor,
|
|
9797
|
+
timeZone: cal.timeZone
|
|
9798
|
+
}));
|
|
9776
9799
|
log("Listed calendars", { count: calendars.length });
|
|
9777
|
-
return structuredResponse(
|
|
9800
|
+
return structuredResponse(
|
|
9801
|
+
`Found ${calendars.length} calendar(s):
|
|
9778
9802
|
|
|
9779
|
-
${
|
|
9780
|
-
calendars:
|
|
9781
|
-
|
|
9782
|
-
summary: cal.summary,
|
|
9783
|
-
description: cal.description,
|
|
9784
|
-
primary: cal.primary,
|
|
9785
|
-
accessRole: cal.accessRole,
|
|
9786
|
-
backgroundColor: cal.backgroundColor,
|
|
9787
|
-
foregroundColor: cal.foregroundColor,
|
|
9788
|
-
timeZone: cal.timeZone
|
|
9789
|
-
}))
|
|
9790
|
-
});
|
|
9803
|
+
${toToon({ calendars: calendarData })}`,
|
|
9804
|
+
{ calendars: calendarData }
|
|
9805
|
+
);
|
|
9791
9806
|
}
|
|
9792
9807
|
async function handleListEvents(calendar, args) {
|
|
9793
9808
|
const validation = validateArgs(ListEventsSchema, args);
|
|
@@ -9810,10 +9825,29 @@ async function handleListEvents(calendar, args) {
|
|
|
9810
9825
|
nextPageToken: null
|
|
9811
9826
|
});
|
|
9812
9827
|
}
|
|
9813
|
-
const
|
|
9828
|
+
const eventData = events.map((event) => ({
|
|
9829
|
+
id: event.id,
|
|
9830
|
+
summary: event.summary,
|
|
9831
|
+
description: event.description,
|
|
9832
|
+
location: event.location,
|
|
9833
|
+
start: event.start,
|
|
9834
|
+
end: event.end,
|
|
9835
|
+
status: event.status,
|
|
9836
|
+
htmlLink: event.htmlLink,
|
|
9837
|
+
hangoutLink: event.hangoutLink,
|
|
9838
|
+
attendees: event.attendees?.map((a) => ({
|
|
9839
|
+
email: a.email,
|
|
9840
|
+
displayName: a.displayName,
|
|
9841
|
+
responseStatus: a.responseStatus,
|
|
9842
|
+
optional: a.optional
|
|
9843
|
+
})),
|
|
9844
|
+
organizer: event.organizer,
|
|
9845
|
+
creator: event.creator,
|
|
9846
|
+
recurringEventId: event.recurringEventId
|
|
9847
|
+
}));
|
|
9814
9848
|
let textResponse = `Found ${events.length} event(s):
|
|
9815
9849
|
|
|
9816
|
-
${
|
|
9850
|
+
${toToon({ events: eventData })}`;
|
|
9817
9851
|
if (response.data.nextPageToken) {
|
|
9818
9852
|
textResponse += `
|
|
9819
9853
|
|
|
@@ -9821,26 +9855,7 @@ More events available. Use pageToken: ${response.data.nextPageToken}`;
|
|
|
9821
9855
|
}
|
|
9822
9856
|
log("Listed events", { calendarId, count: events.length });
|
|
9823
9857
|
return structuredResponse(textResponse, {
|
|
9824
|
-
events:
|
|
9825
|
-
id: event.id,
|
|
9826
|
-
summary: event.summary,
|
|
9827
|
-
description: event.description,
|
|
9828
|
-
location: event.location,
|
|
9829
|
-
start: event.start,
|
|
9830
|
-
end: event.end,
|
|
9831
|
-
status: event.status,
|
|
9832
|
-
htmlLink: event.htmlLink,
|
|
9833
|
-
hangoutLink: event.hangoutLink,
|
|
9834
|
-
attendees: event.attendees?.map((a) => ({
|
|
9835
|
-
email: a.email,
|
|
9836
|
-
displayName: a.displayName,
|
|
9837
|
-
responseStatus: a.responseStatus,
|
|
9838
|
-
optional: a.optional
|
|
9839
|
-
})),
|
|
9840
|
-
organizer: event.organizer,
|
|
9841
|
-
creator: event.creator,
|
|
9842
|
-
recurringEventId: event.recurringEventId
|
|
9843
|
-
})),
|
|
9858
|
+
events: eventData,
|
|
9844
9859
|
nextPageToken: response.data.nextPageToken || null
|
|
9845
9860
|
});
|
|
9846
9861
|
}
|
|
@@ -10425,10 +10440,9 @@ async function handleSearchEmails(gmail, args) {
|
|
|
10425
10440
|
};
|
|
10426
10441
|
})
|
|
10427
10442
|
);
|
|
10428
|
-
const formattedList = messageDetails.map((m) => `- [${m.id}] ${m.subject || "(No subject)"} from ${m.from || "Unknown"}`).join("\n");
|
|
10429
10443
|
let textResponse = `Found ${response.data.resultSizeEstimate} email(s):
|
|
10430
10444
|
|
|
10431
|
-
${
|
|
10445
|
+
${toToon({ messages: messageDetails })}`;
|
|
10432
10446
|
if (response.data.nextPageToken) {
|
|
10433
10447
|
textResponse += `
|
|
10434
10448
|
|
|
@@ -10629,28 +10643,31 @@ async function handleListLabels(gmail, args) {
|
|
|
10629
10643
|
if (!includeSystemLabels) {
|
|
10630
10644
|
labels = userLabels;
|
|
10631
10645
|
}
|
|
10632
|
-
const
|
|
10646
|
+
const labelData = labels.map((l) => ({
|
|
10647
|
+
id: l.id,
|
|
10648
|
+
name: l.name,
|
|
10649
|
+
type: l.type,
|
|
10650
|
+
messageListVisibility: l.messageListVisibility,
|
|
10651
|
+
labelListVisibility: l.labelListVisibility,
|
|
10652
|
+
color: l.color,
|
|
10653
|
+
messagesTotal: l.messagesTotal,
|
|
10654
|
+
messagesUnread: l.messagesUnread
|
|
10655
|
+
}));
|
|
10633
10656
|
log("Listed labels", {
|
|
10634
10657
|
total: labels.length,
|
|
10635
10658
|
system: systemLabels.length,
|
|
10636
10659
|
user: userLabels.length
|
|
10637
10660
|
});
|
|
10638
|
-
return structuredResponse(
|
|
10661
|
+
return structuredResponse(
|
|
10662
|
+
`Found ${labels.length} label(s):
|
|
10639
10663
|
|
|
10640
|
-
${
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
color: l.color,
|
|
10648
|
-
messagesTotal: l.messagesTotal,
|
|
10649
|
-
messagesUnread: l.messagesUnread
|
|
10650
|
-
})),
|
|
10651
|
-
systemLabelCount: systemLabels.length,
|
|
10652
|
-
userLabelCount: userLabels.length
|
|
10653
|
-
});
|
|
10664
|
+
${toToon({ labels: labelData })}`,
|
|
10665
|
+
{
|
|
10666
|
+
labels: labelData,
|
|
10667
|
+
systemLabelCount: systemLabels.length,
|
|
10668
|
+
userLabelCount: userLabels.length
|
|
10669
|
+
}
|
|
10670
|
+
);
|
|
10654
10671
|
}
|
|
10655
10672
|
async function handleGetOrCreateLabel(gmail, args) {
|
|
10656
10673
|
const validation = validateArgs(GetOrCreateLabelSchema, args);
|
|
@@ -10823,26 +10840,20 @@ ${actionStr || "None"}`,
|
|
|
10823
10840
|
if (filters.length === 0) {
|
|
10824
10841
|
return structuredResponse("No filters found.", { filters: [] });
|
|
10825
10842
|
}
|
|
10826
|
-
const
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
criteria.subject ? `subject:${criteria.subject}` : null,
|
|
10832
|
-
criteria.query ? `query:${criteria.query}` : null
|
|
10833
|
-
].filter(Boolean).join(", ");
|
|
10834
|
-
return `- [${f.id}] ${criteriaStr || "(no criteria)"}`;
|
|
10835
|
-
}).join("\n");
|
|
10843
|
+
const filterData = filters.map((f) => ({
|
|
10844
|
+
id: f.id,
|
|
10845
|
+
criteria: f.criteria,
|
|
10846
|
+
action: f.action
|
|
10847
|
+
}));
|
|
10836
10848
|
log("Listed filters", { count: filters.length });
|
|
10837
|
-
return structuredResponse(
|
|
10849
|
+
return structuredResponse(
|
|
10850
|
+
`Found ${filters.length} filter(s):
|
|
10838
10851
|
|
|
10839
|
-
${
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
}))
|
|
10845
|
-
});
|
|
10852
|
+
${toToon({ filters: filterData })}`,
|
|
10853
|
+
{
|
|
10854
|
+
filters: filterData
|
|
10855
|
+
}
|
|
10856
|
+
);
|
|
10846
10857
|
}
|
|
10847
10858
|
async function handleDeleteFilter(gmail, args) {
|
|
10848
10859
|
const validation = validateArgs(DeleteFilterSchema, args);
|