@dguido/google-workspace-mcp 1.0.0 → 1.2.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 +308 -274
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
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
|
|
|
@@ -850,12 +925,6 @@ import * as fs from "fs/promises";
|
|
|
850
925
|
// src/auth/utils.ts
|
|
851
926
|
import * as path from "path";
|
|
852
927
|
import * as os from "os";
|
|
853
|
-
import { fileURLToPath } from "url";
|
|
854
|
-
function getProjectRoot() {
|
|
855
|
-
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
856
|
-
const projectRoot = path.join(__dirname2, "..", "..");
|
|
857
|
-
return path.resolve(projectRoot);
|
|
858
|
-
}
|
|
859
928
|
function getSecureTokenPath() {
|
|
860
929
|
const customTokenPath = process.env.GOOGLE_WORKSPACE_MCP_TOKEN_PATH;
|
|
861
930
|
if (customTokenPath) {
|
|
@@ -869,25 +938,12 @@ function getSecureTokenPath() {
|
|
|
869
938
|
const tokenDir = path.join(configHome, "google-workspace-mcp");
|
|
870
939
|
return path.join(tokenDir, "tokens.json");
|
|
871
940
|
}
|
|
872
|
-
function getLegacyTokenPath() {
|
|
873
|
-
const projectRoot = getProjectRoot();
|
|
874
|
-
return path.join(projectRoot, ".gcp-saved-tokens.json");
|
|
875
|
-
}
|
|
876
|
-
function getAdditionalLegacyPaths() {
|
|
877
|
-
return [
|
|
878
|
-
process.env.GOOGLE_TOKEN_PATH,
|
|
879
|
-
path.join(process.cwd(), "google-tokens.json"),
|
|
880
|
-
path.join(process.cwd(), ".gcp-saved-tokens.json")
|
|
881
|
-
].filter(Boolean);
|
|
882
|
-
}
|
|
883
941
|
function getKeysFilePath() {
|
|
884
942
|
const envCredentialsPath = process.env.GOOGLE_DRIVE_OAUTH_CREDENTIALS;
|
|
885
943
|
if (envCredentialsPath) {
|
|
886
944
|
return path.resolve(envCredentialsPath);
|
|
887
945
|
}
|
|
888
|
-
|
|
889
|
-
const keysPath = path.join(projectRoot, "gcp-oauth.keys.json");
|
|
890
|
-
return keysPath;
|
|
946
|
+
return path.join(process.cwd(), "gcp-oauth.keys.json");
|
|
891
947
|
}
|
|
892
948
|
function generateCredentialsErrorMessage() {
|
|
893
949
|
return `
|
|
@@ -1052,46 +1108,13 @@ var TokenManager = class {
|
|
|
1052
1108
|
}
|
|
1053
1109
|
});
|
|
1054
1110
|
}
|
|
1055
|
-
async migrateLegacyTokens() {
|
|
1056
|
-
const legacyPaths = [getLegacyTokenPath(), ...getAdditionalLegacyPaths()];
|
|
1057
|
-
for (const legacyPath of legacyPaths) {
|
|
1058
|
-
try {
|
|
1059
|
-
if (!await fs2.access(legacyPath).then(() => true).catch(() => false)) {
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
const legacyTokens = JSON.parse(await fs2.readFile(legacyPath, "utf-8"));
|
|
1063
|
-
if (!legacyTokens || typeof legacyTokens !== "object") {
|
|
1064
|
-
log(`Invalid legacy token format at ${legacyPath}, skipping`);
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
await this.ensureTokenDirectoryExists();
|
|
1068
|
-
await fs2.writeFile(this.tokenPath, JSON.stringify(legacyTokens, null, 2), {
|
|
1069
|
-
mode: 384
|
|
1070
|
-
});
|
|
1071
|
-
log(`Migrated tokens from legacy location: ${legacyPath} to: ${this.tokenPath}`);
|
|
1072
|
-
try {
|
|
1073
|
-
await fs2.unlink(legacyPath);
|
|
1074
|
-
log("Removed legacy token file");
|
|
1075
|
-
} catch (unlinkErr) {
|
|
1076
|
-
log("Warning: Could not remove legacy token file:", unlinkErr);
|
|
1077
|
-
}
|
|
1078
|
-
return true;
|
|
1079
|
-
} catch (error) {
|
|
1080
|
-
log(`Error migrating legacy tokens from ${legacyPath}`, error);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
1085
1111
|
async loadSavedTokens() {
|
|
1086
1112
|
try {
|
|
1087
1113
|
await this.ensureTokenDirectoryExists();
|
|
1088
1114
|
const tokenExists = await fs2.access(this.tokenPath).then(() => true).catch(() => false);
|
|
1089
1115
|
if (!tokenExists) {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
log("No token file found at:", this.tokenPath);
|
|
1093
|
-
return false;
|
|
1094
|
-
}
|
|
1116
|
+
log("No token file found at:", this.tokenPath);
|
|
1117
|
+
return false;
|
|
1095
1118
|
}
|
|
1096
1119
|
const tokens = JSON.parse(await fs2.readFile(this.tokenPath, "utf-8"));
|
|
1097
1120
|
if (!tokens || typeof tokens !== "object") {
|
|
@@ -1454,50 +1477,12 @@ async function authenticate() {
|
|
|
1454
1477
|
|
|
1455
1478
|
// src/index.ts
|
|
1456
1479
|
init_utils();
|
|
1457
|
-
import { fileURLToPath
|
|
1480
|
+
import { fileURLToPath } from "url";
|
|
1458
1481
|
import { readFileSync } from "fs";
|
|
1459
|
-
import { join as join3, dirname as
|
|
1482
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1460
1483
|
|
|
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
|
-
}
|
|
1484
|
+
// src/config/index.ts
|
|
1485
|
+
init_services();
|
|
1501
1486
|
|
|
1502
1487
|
// src/tools/definitions.ts
|
|
1503
1488
|
var driveTools = [
|
|
@@ -4463,13 +4448,13 @@ var gmailTools = [
|
|
|
4463
4448
|
},
|
|
4464
4449
|
{
|
|
4465
4450
|
name: "modify_email",
|
|
4466
|
-
description: "Add/remove labels (max 1000 IDs per request)",
|
|
4451
|
+
description: "Add/remove labels on threads (max 1000 IDs per request)",
|
|
4467
4452
|
inputSchema: {
|
|
4468
4453
|
type: "object",
|
|
4469
4454
|
properties: {
|
|
4470
|
-
|
|
4455
|
+
threadId: {
|
|
4471
4456
|
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" }, maxItems: 1e3 }],
|
|
4472
|
-
description: "
|
|
4457
|
+
description: "Thread ID or array of IDs (max 1000)"
|
|
4473
4458
|
},
|
|
4474
4459
|
addLabelIds: {
|
|
4475
4460
|
type: "array",
|
|
@@ -4482,14 +4467,14 @@ var gmailTools = [
|
|
|
4482
4467
|
description: "(optional) Label IDs to remove"
|
|
4483
4468
|
}
|
|
4484
4469
|
},
|
|
4485
|
-
required: ["
|
|
4470
|
+
required: ["threadId"]
|
|
4486
4471
|
},
|
|
4487
4472
|
outputSchema: {
|
|
4488
4473
|
type: "object",
|
|
4489
4474
|
properties: {
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4475
|
+
id: { type: "string", description: "Thread ID" },
|
|
4476
|
+
messageCount: { type: "number", description: "Number of messages in thread" },
|
|
4477
|
+
labelIds: { type: "array", items: { type: "string" }, description: "Current labels" }
|
|
4493
4478
|
}
|
|
4494
4479
|
}
|
|
4495
4480
|
},
|
|
@@ -5109,9 +5094,6 @@ function formatBytes(bytes, options = {}) {
|
|
|
5109
5094
|
const formatted = value.toFixed(precision);
|
|
5110
5095
|
return `${formatted} ${BYTE_UNITS[unitIndex]}`;
|
|
5111
5096
|
}
|
|
5112
|
-
function formatBytesCompact(bytes, nullValue = "N/A") {
|
|
5113
|
-
return formatBytes(bytes, { precision: 1, nullValue });
|
|
5114
|
-
}
|
|
5115
5097
|
|
|
5116
5098
|
// src/schemas/drive.ts
|
|
5117
5099
|
import { z } from "zod";
|
|
@@ -5841,7 +5823,7 @@ var DeleteEmailSchema = z7.object({
|
|
|
5841
5823
|
messageId: z7.union([z7.string().min(1), z7.array(z7.string().min(1)).min(1).max(1e3)]).describe("Message ID or array of IDs (max 1000 for batch)")
|
|
5842
5824
|
});
|
|
5843
5825
|
var ModifyEmailSchema = z7.object({
|
|
5844
|
-
|
|
5826
|
+
threadId: z7.union([z7.string().min(1), z7.array(z7.string().min(1)).min(1).max(1e3)]).describe("Thread ID or array of IDs (max 1000 for batch)"),
|
|
5845
5827
|
addLabelIds: z7.array(z7.string()).optional().describe("Label IDs to add"),
|
|
5846
5828
|
removeLabelIds: z7.array(z7.string()).optional().describe("Label IDs to remove")
|
|
5847
5829
|
});
|
|
@@ -6261,19 +6243,29 @@ async function handleSearch(drive2, args) {
|
|
|
6261
6243
|
includeItemsFromAllDrives: true,
|
|
6262
6244
|
supportsAllDrives: true
|
|
6263
6245
|
});
|
|
6264
|
-
const
|
|
6246
|
+
const files = res.data.files?.map((f) => ({
|
|
6247
|
+
id: f.id,
|
|
6248
|
+
name: f.name,
|
|
6249
|
+
mimeType: f.mimeType,
|
|
6250
|
+
modifiedTime: f.modifiedTime,
|
|
6251
|
+
size: f.size
|
|
6252
|
+
})) || [];
|
|
6265
6253
|
log("Search results", {
|
|
6266
6254
|
query: userQuery,
|
|
6267
|
-
resultCount:
|
|
6255
|
+
resultCount: files.length
|
|
6268
6256
|
});
|
|
6269
|
-
let
|
|
6270
|
-
|
|
6257
|
+
let textResponse = `Found ${files.length} files:
|
|
6258
|
+
|
|
6259
|
+
${toToon({ files })}`;
|
|
6271
6260
|
if (res.data.nextPageToken) {
|
|
6272
|
-
|
|
6261
|
+
textResponse += `
|
|
6273
6262
|
|
|
6274
6263
|
More results available. Use pageToken: ${res.data.nextPageToken}`;
|
|
6275
6264
|
}
|
|
6276
|
-
return
|
|
6265
|
+
return structuredResponse(textResponse, {
|
|
6266
|
+
files,
|
|
6267
|
+
nextPageToken: res.data.nextPageToken || null
|
|
6268
|
+
});
|
|
6277
6269
|
}
|
|
6278
6270
|
async function handleCreateTextFile(drive2, args) {
|
|
6279
6271
|
const validation = validateArgs(CreateTextFileSchema, args);
|
|
@@ -6736,33 +6728,23 @@ async function handleGetSharing(drive2, args) {
|
|
|
6736
6728
|
"List permissions"
|
|
6737
6729
|
);
|
|
6738
6730
|
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");
|
|
6731
|
+
const permissionData = permissionList.map((p) => ({
|
|
6732
|
+
id: p.id,
|
|
6733
|
+
role: p.role,
|
|
6734
|
+
type: p.type,
|
|
6735
|
+
emailAddress: p.emailAddress,
|
|
6736
|
+
domain: p.domain,
|
|
6737
|
+
displayName: p.displayName
|
|
6738
|
+
}));
|
|
6750
6739
|
const textResponse = `Sharing settings for "${file.data.name}":
|
|
6751
6740
|
|
|
6752
|
-
${
|
|
6741
|
+
${toToon({ permissions: permissionData })}
|
|
6753
6742
|
|
|
6754
6743
|
Link: ${file.data.webViewLink}`;
|
|
6755
6744
|
return structuredResponse(textResponse, {
|
|
6756
6745
|
fileName: file.data.name,
|
|
6757
6746
|
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
|
-
}))
|
|
6747
|
+
permissions: permissionData
|
|
6766
6748
|
});
|
|
6767
6749
|
}
|
|
6768
6750
|
async function handleListRevisions(drive2, args) {
|
|
@@ -6796,27 +6778,22 @@ async function handleListRevisions(drive2, args) {
|
|
|
6796
6778
|
if (revisionList.length === 0) {
|
|
6797
6779
|
return successResponse(`No revisions found for "${file.data.name}".`);
|
|
6798
6780
|
}
|
|
6799
|
-
const
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6781
|
+
const revisionData = revisionList.map((r) => ({
|
|
6782
|
+
id: r.id,
|
|
6783
|
+
modifiedTime: r.modifiedTime,
|
|
6784
|
+
size: r.size,
|
|
6785
|
+
keepForever: r.keepForever,
|
|
6786
|
+
lastModifyingUser: r.lastModifyingUser ? {
|
|
6787
|
+
displayName: r.lastModifyingUser.displayName,
|
|
6788
|
+
emailAddress: r.lastModifyingUser.emailAddress
|
|
6789
|
+
} : void 0
|
|
6790
|
+
}));
|
|
6805
6791
|
const textResponse = `Revisions for "${file.data.name}" (${revisionList.length} found):
|
|
6806
6792
|
|
|
6807
|
-
${
|
|
6793
|
+
${toToon({ revisions: revisionData })}`;
|
|
6808
6794
|
return structuredResponse(textResponse, {
|
|
6809
6795
|
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
|
-
}))
|
|
6796
|
+
revisions: revisionData
|
|
6820
6797
|
});
|
|
6821
6798
|
}
|
|
6822
6799
|
async function handleRestoreRevision(drive2, args) {
|
|
@@ -7438,22 +7415,18 @@ async function handleListTrash(drive2, args) {
|
|
|
7438
7415
|
nextPageToken: null
|
|
7439
7416
|
});
|
|
7440
7417
|
}
|
|
7441
|
-
const
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7418
|
+
const fileData = files.map((f) => ({
|
|
7419
|
+
id: f.id,
|
|
7420
|
+
name: f.name,
|
|
7421
|
+
mimeType: f.mimeType,
|
|
7422
|
+
size: f.size,
|
|
7423
|
+
trashedTime: f.trashedTime
|
|
7424
|
+
}));
|
|
7446
7425
|
const textResponse = `Trash contents (${files.length} items):
|
|
7447
7426
|
|
|
7448
|
-
${
|
|
7427
|
+
${toToon({ files: fileData })}` + (response.data.nextPageToken ? "\n\n(More items available - use nextPageToken to continue)" : "");
|
|
7449
7428
|
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
|
-
})),
|
|
7429
|
+
files: fileData,
|
|
7457
7430
|
nextPageToken: response.data.nextPageToken || null
|
|
7458
7431
|
});
|
|
7459
7432
|
}
|
|
@@ -8538,9 +8511,9 @@ async function listSheetTabs(sheets, spreadsheetId) {
|
|
|
8538
8511
|
rowCount: sheet.properties?.gridProperties?.rowCount,
|
|
8539
8512
|
columnCount: sheet.properties?.gridProperties?.columnCount
|
|
8540
8513
|
})) || [];
|
|
8541
|
-
const tabList = tabs.map((t) => `${t.index}: ${t.title} (${t.rowCount}x${t.columnCount})`).join("\n");
|
|
8542
8514
|
return structuredResponse(`Spreadsheet has ${tabs.length} tab(s):
|
|
8543
|
-
|
|
8515
|
+
|
|
8516
|
+
${toToon({ tabs })}`, {
|
|
8544
8517
|
spreadsheetId,
|
|
8545
8518
|
tabs
|
|
8546
8519
|
});
|
|
@@ -9749,13 +9722,6 @@ function formatEventTime(eventTime) {
|
|
|
9749
9722
|
}
|
|
9750
9723
|
return "Unknown";
|
|
9751
9724
|
}
|
|
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
9725
|
async function handleListCalendars(calendar, args) {
|
|
9760
9726
|
const validation = validateArgs(ListCalendarsSchema, args);
|
|
9761
9727
|
if (!validation.success) return validation.response;
|
|
@@ -9768,26 +9734,23 @@ async function handleListCalendars(calendar, args) {
|
|
|
9768
9734
|
if (calendars.length === 0) {
|
|
9769
9735
|
return structuredResponse("No calendars found.", { calendars: [] });
|
|
9770
9736
|
}
|
|
9771
|
-
const
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9737
|
+
const calendarData = calendars.map((cal) => ({
|
|
9738
|
+
id: cal.id,
|
|
9739
|
+
summary: cal.summary,
|
|
9740
|
+
description: cal.description,
|
|
9741
|
+
primary: cal.primary,
|
|
9742
|
+
accessRole: cal.accessRole,
|
|
9743
|
+
backgroundColor: cal.backgroundColor,
|
|
9744
|
+
foregroundColor: cal.foregroundColor,
|
|
9745
|
+
timeZone: cal.timeZone
|
|
9746
|
+
}));
|
|
9776
9747
|
log("Listed calendars", { count: calendars.length });
|
|
9777
|
-
return structuredResponse(
|
|
9748
|
+
return structuredResponse(
|
|
9749
|
+
`Found ${calendars.length} calendar(s):
|
|
9778
9750
|
|
|
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
|
-
});
|
|
9751
|
+
${toToon({ calendars: calendarData })}`,
|
|
9752
|
+
{ calendars: calendarData }
|
|
9753
|
+
);
|
|
9791
9754
|
}
|
|
9792
9755
|
async function handleListEvents(calendar, args) {
|
|
9793
9756
|
const validation = validateArgs(ListEventsSchema, args);
|
|
@@ -9810,10 +9773,29 @@ async function handleListEvents(calendar, args) {
|
|
|
9810
9773
|
nextPageToken: null
|
|
9811
9774
|
});
|
|
9812
9775
|
}
|
|
9813
|
-
const
|
|
9776
|
+
const eventData = events.map((event) => ({
|
|
9777
|
+
id: event.id,
|
|
9778
|
+
summary: event.summary,
|
|
9779
|
+
description: event.description,
|
|
9780
|
+
location: event.location,
|
|
9781
|
+
start: event.start,
|
|
9782
|
+
end: event.end,
|
|
9783
|
+
status: event.status,
|
|
9784
|
+
htmlLink: event.htmlLink,
|
|
9785
|
+
hangoutLink: event.hangoutLink,
|
|
9786
|
+
attendees: event.attendees?.map((a) => ({
|
|
9787
|
+
email: a.email,
|
|
9788
|
+
displayName: a.displayName,
|
|
9789
|
+
responseStatus: a.responseStatus,
|
|
9790
|
+
optional: a.optional
|
|
9791
|
+
})),
|
|
9792
|
+
organizer: event.organizer,
|
|
9793
|
+
creator: event.creator,
|
|
9794
|
+
recurringEventId: event.recurringEventId
|
|
9795
|
+
}));
|
|
9814
9796
|
let textResponse = `Found ${events.length} event(s):
|
|
9815
9797
|
|
|
9816
|
-
${
|
|
9798
|
+
${toToon({ events: eventData })}`;
|
|
9817
9799
|
if (response.data.nextPageToken) {
|
|
9818
9800
|
textResponse += `
|
|
9819
9801
|
|
|
@@ -9821,26 +9803,7 @@ More events available. Use pageToken: ${response.data.nextPageToken}`;
|
|
|
9821
9803
|
}
|
|
9822
9804
|
log("Listed events", { calendarId, count: events.length });
|
|
9823
9805
|
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
|
-
})),
|
|
9806
|
+
events: eventData,
|
|
9844
9807
|
nextPageToken: response.data.nextPageToken || null
|
|
9845
9808
|
});
|
|
9846
9809
|
}
|
|
@@ -10215,6 +10178,25 @@ var SYSTEM_LABELS = /* @__PURE__ */ new Set([
|
|
|
10215
10178
|
"CATEGORY_UPDATES",
|
|
10216
10179
|
"CATEGORY_FORUMS"
|
|
10217
10180
|
]);
|
|
10181
|
+
var THREAD_ID_PATTERN = /^[0-9a-f]{16}$/i;
|
|
10182
|
+
var CATEGORY_SUGGESTIONS = {
|
|
10183
|
+
INVALID_FORMAT: "Use search_emails to get valid thread IDs",
|
|
10184
|
+
NOT_FOUND: "Thread may have been deleted. Use search_emails to refresh",
|
|
10185
|
+
PERMISSION_DENIED: "You don't have access to this thread",
|
|
10186
|
+
RATE_LIMITED: "Too many requests. Wait and retry with smaller batches",
|
|
10187
|
+
UNKNOWN: "Check the thread ID and try again"
|
|
10188
|
+
};
|
|
10189
|
+
function isValidThreadIdFormat(id) {
|
|
10190
|
+
return THREAD_ID_PATTERN.test(id);
|
|
10191
|
+
}
|
|
10192
|
+
function categorizeError(errorMessage) {
|
|
10193
|
+
const msg = errorMessage.toLowerCase();
|
|
10194
|
+
if (msg.includes("invalid id") || msg.includes("invalid value")) return "INVALID_FORMAT";
|
|
10195
|
+
if (msg.includes("not found")) return "NOT_FOUND";
|
|
10196
|
+
if (msg.includes("permission") || msg.includes("forbidden")) return "PERMISSION_DENIED";
|
|
10197
|
+
if (msg.includes("rate") || msg.includes("quota")) return "RATE_LIMITED";
|
|
10198
|
+
return "UNKNOWN";
|
|
10199
|
+
}
|
|
10218
10200
|
function extractEmailBody(payload) {
|
|
10219
10201
|
if (!payload) return { text: "", html: "" };
|
|
10220
10202
|
const result = { text: "", html: "" };
|
|
@@ -10425,10 +10407,9 @@ async function handleSearchEmails(gmail, args) {
|
|
|
10425
10407
|
};
|
|
10426
10408
|
})
|
|
10427
10409
|
);
|
|
10428
|
-
const formattedList = messageDetails.map((m) => `- [${m.id}] ${m.subject || "(No subject)"} from ${m.from || "Unknown"}`).join("\n");
|
|
10429
10410
|
let textResponse = `Found ${response.data.resultSizeEstimate} email(s):
|
|
10430
10411
|
|
|
10431
|
-
${
|
|
10412
|
+
${toToon({ messages: messageDetails })}`;
|
|
10432
10413
|
if (response.data.nextPageToken) {
|
|
10433
10414
|
textResponse += `
|
|
10434
10415
|
|
|
@@ -10484,36 +10465,92 @@ async function handleDeleteEmail(gmail, args) {
|
|
|
10484
10465
|
async function handleModifyEmail(gmail, args) {
|
|
10485
10466
|
const validation = validateArgs(ModifyEmailSchema, args);
|
|
10486
10467
|
if (!validation.success) return validation.response;
|
|
10487
|
-
const {
|
|
10488
|
-
const
|
|
10489
|
-
if (
|
|
10490
|
-
const response = await gmail.users.
|
|
10468
|
+
const { threadId, addLabelIds, removeLabelIds } = validation.data;
|
|
10469
|
+
const ids = Array.isArray(threadId) ? threadId : [threadId];
|
|
10470
|
+
if (ids.length === 1) {
|
|
10471
|
+
const response = await gmail.users.threads.modify({
|
|
10491
10472
|
userId: "me",
|
|
10492
|
-
id:
|
|
10473
|
+
id: ids[0],
|
|
10493
10474
|
requestBody: { addLabelIds, removeLabelIds }
|
|
10494
10475
|
});
|
|
10495
|
-
log("Modified
|
|
10476
|
+
log("Modified thread labels", { threadId: ids[0], addLabelIds, removeLabelIds });
|
|
10477
|
+
const messageCount = response.data.messages?.length || 0;
|
|
10496
10478
|
return structuredResponse(
|
|
10497
|
-
`
|
|
10498
|
-
Current labels: ${response.data.labelIds?.join(", ") || "None"}`,
|
|
10479
|
+
`Thread ${ids[0]} labels updated (${messageCount} message(s) affected).
|
|
10480
|
+
Current labels: ${response.data.messages?.[0]?.labelIds?.join(", ") || "None"}`,
|
|
10499
10481
|
{
|
|
10500
10482
|
id: response.data.id,
|
|
10501
|
-
|
|
10502
|
-
|
|
10483
|
+
historyId: response.data.historyId,
|
|
10484
|
+
messageCount,
|
|
10485
|
+
labelIds: response.data.messages?.[0]?.labelIds
|
|
10503
10486
|
}
|
|
10504
10487
|
);
|
|
10505
10488
|
}
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10489
|
+
const validIds = ids.filter(isValidThreadIdFormat);
|
|
10490
|
+
const invalidIds = ids.filter((id) => !isValidThreadIdFormat(id));
|
|
10491
|
+
const formatFailures = invalidIds.map((id) => ({
|
|
10492
|
+
threadId: id,
|
|
10493
|
+
category: "INVALID_FORMAT",
|
|
10494
|
+
error: "Invalid thread ID format"
|
|
10495
|
+
}));
|
|
10496
|
+
const results = await Promise.allSettled(
|
|
10497
|
+
validIds.map(
|
|
10498
|
+
(id) => gmail.users.threads.modify({
|
|
10499
|
+
userId: "me",
|
|
10500
|
+
id,
|
|
10501
|
+
requestBody: { addLabelIds, removeLabelIds }
|
|
10502
|
+
})
|
|
10503
|
+
)
|
|
10504
|
+
);
|
|
10505
|
+
const apiSucceeded = results.filter((r) => r.status === "fulfilled").length;
|
|
10506
|
+
const apiFailures = results.map((r, i) => ({ id: validIds[i], result: r })).filter(
|
|
10507
|
+
(item) => item.result.status === "rejected"
|
|
10508
|
+
).map((item) => {
|
|
10509
|
+
const errorMsg = item.result.reason?.message || String(item.result.reason) || "Unknown error";
|
|
10510
|
+
return {
|
|
10511
|
+
threadId: item.id,
|
|
10512
|
+
category: categorizeError(errorMsg),
|
|
10513
|
+
error: errorMsg
|
|
10514
|
+
};
|
|
10513
10515
|
});
|
|
10514
|
-
|
|
10516
|
+
const allFailures = [...formatFailures, ...apiFailures];
|
|
10517
|
+
const succeeded = apiSucceeded;
|
|
10518
|
+
log("Batch modified threads", {
|
|
10519
|
+
count: ids.length,
|
|
10520
|
+
succeeded,
|
|
10521
|
+
failed: allFailures.length,
|
|
10522
|
+
preFiltered: invalidIds.length,
|
|
10523
|
+
addLabelIds,
|
|
10524
|
+
removeLabelIds
|
|
10525
|
+
});
|
|
10526
|
+
if (allFailures.length > 0) {
|
|
10527
|
+
const failuresByCategory = allFailures.reduce(
|
|
10528
|
+
(acc, f) => {
|
|
10529
|
+
if (!acc[f.category]) acc[f.category] = [];
|
|
10530
|
+
acc[f.category].push(f.threadId);
|
|
10531
|
+
return acc;
|
|
10532
|
+
},
|
|
10533
|
+
{}
|
|
10534
|
+
);
|
|
10535
|
+
const categoryLines = Object.entries(failuresByCategory).map(([category, threadIds]) => {
|
|
10536
|
+
const suggestion = CATEGORY_SUGGESTIONS[category];
|
|
10537
|
+
const idList = threadIds.slice(0, 5).join(", ");
|
|
10538
|
+
const moreText = threadIds.length > 5 ? ` (+${threadIds.length - 5} more)` : "";
|
|
10539
|
+
return `- ${category} (${threadIds.length}): ${suggestion}
|
|
10540
|
+
${idList}${moreText}`;
|
|
10541
|
+
}).join("\n");
|
|
10542
|
+
return structuredResponse(
|
|
10543
|
+
`Partially completed: ${succeeded} thread(s) modified, ${allFailures.length} failed.` + (addLabelIds ? `
|
|
10544
|
+
Added labels: ${addLabelIds.join(", ")}` : "") + (removeLabelIds ? `
|
|
10545
|
+
Removed labels: ${removeLabelIds.join(", ")}` : "") + `
|
|
10546
|
+
|
|
10547
|
+
Failures:
|
|
10548
|
+
${categoryLines}`,
|
|
10549
|
+
{ succeeded, failed: allFailures.length, total: ids.length, failuresByCategory }
|
|
10550
|
+
);
|
|
10551
|
+
}
|
|
10515
10552
|
return successResponse(
|
|
10516
|
-
`Successfully modified labels for ${
|
|
10553
|
+
`Successfully modified labels for ${ids.length} thread(s).` + (addLabelIds ? `
|
|
10517
10554
|
Added labels: ${addLabelIds.join(", ")}` : "") + (removeLabelIds ? `
|
|
10518
10555
|
Removed labels: ${removeLabelIds.join(", ")}` : "")
|
|
10519
10556
|
);
|
|
@@ -10629,28 +10666,31 @@ async function handleListLabels(gmail, args) {
|
|
|
10629
10666
|
if (!includeSystemLabels) {
|
|
10630
10667
|
labels = userLabels;
|
|
10631
10668
|
}
|
|
10632
|
-
const
|
|
10669
|
+
const labelData = labels.map((l) => ({
|
|
10670
|
+
id: l.id,
|
|
10671
|
+
name: l.name,
|
|
10672
|
+
type: l.type,
|
|
10673
|
+
messageListVisibility: l.messageListVisibility,
|
|
10674
|
+
labelListVisibility: l.labelListVisibility,
|
|
10675
|
+
color: l.color,
|
|
10676
|
+
messagesTotal: l.messagesTotal,
|
|
10677
|
+
messagesUnread: l.messagesUnread
|
|
10678
|
+
}));
|
|
10633
10679
|
log("Listed labels", {
|
|
10634
10680
|
total: labels.length,
|
|
10635
10681
|
system: systemLabels.length,
|
|
10636
10682
|
user: userLabels.length
|
|
10637
10683
|
});
|
|
10638
|
-
return structuredResponse(
|
|
10684
|
+
return structuredResponse(
|
|
10685
|
+
`Found ${labels.length} label(s):
|
|
10639
10686
|
|
|
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
|
-
});
|
|
10687
|
+
${toToon({ labels: labelData })}`,
|
|
10688
|
+
{
|
|
10689
|
+
labels: labelData,
|
|
10690
|
+
systemLabelCount: systemLabels.length,
|
|
10691
|
+
userLabelCount: userLabels.length
|
|
10692
|
+
}
|
|
10693
|
+
);
|
|
10654
10694
|
}
|
|
10655
10695
|
async function handleGetOrCreateLabel(gmail, args) {
|
|
10656
10696
|
const validation = validateArgs(GetOrCreateLabelSchema, args);
|
|
@@ -10823,26 +10863,20 @@ ${actionStr || "None"}`,
|
|
|
10823
10863
|
if (filters.length === 0) {
|
|
10824
10864
|
return structuredResponse("No filters found.", { filters: [] });
|
|
10825
10865
|
}
|
|
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");
|
|
10866
|
+
const filterData = filters.map((f) => ({
|
|
10867
|
+
id: f.id,
|
|
10868
|
+
criteria: f.criteria,
|
|
10869
|
+
action: f.action
|
|
10870
|
+
}));
|
|
10836
10871
|
log("Listed filters", { count: filters.length });
|
|
10837
|
-
return structuredResponse(
|
|
10872
|
+
return structuredResponse(
|
|
10873
|
+
`Found ${filters.length} filter(s):
|
|
10838
10874
|
|
|
10839
|
-
${
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
}))
|
|
10845
|
-
});
|
|
10875
|
+
${toToon({ filters: filterData })}`,
|
|
10876
|
+
{
|
|
10877
|
+
filters: filterData
|
|
10878
|
+
}
|
|
10879
|
+
);
|
|
10846
10880
|
}
|
|
10847
10881
|
async function handleDeleteFilter(gmail, args) {
|
|
10848
10882
|
const validation = validateArgs(DeleteFilterSchema, args);
|
|
@@ -10928,8 +10962,8 @@ async function handleListTools(args) {
|
|
|
10928
10962
|
var drive = null;
|
|
10929
10963
|
var authClient = null;
|
|
10930
10964
|
var authenticationPromise = null;
|
|
10931
|
-
var __filename =
|
|
10932
|
-
var __dirname =
|
|
10965
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
10966
|
+
var __dirname = dirname2(__filename);
|
|
10933
10967
|
var packageJsonPath = join3(__dirname, "..", "package.json");
|
|
10934
10968
|
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
10935
10969
|
var VERSION = packageJson.version;
|