@dguido/google-workspace-mcp 1.1.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/dist/index.js +109 -86
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -925,12 +925,6 @@ import * as fs from "fs/promises";
|
|
|
925
925
|
// src/auth/utils.ts
|
|
926
926
|
import * as path from "path";
|
|
927
927
|
import * as os from "os";
|
|
928
|
-
import { fileURLToPath } from "url";
|
|
929
|
-
function getProjectRoot() {
|
|
930
|
-
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
931
|
-
const projectRoot = path.join(__dirname2, "..", "..");
|
|
932
|
-
return path.resolve(projectRoot);
|
|
933
|
-
}
|
|
934
928
|
function getSecureTokenPath() {
|
|
935
929
|
const customTokenPath = process.env.GOOGLE_WORKSPACE_MCP_TOKEN_PATH;
|
|
936
930
|
if (customTokenPath) {
|
|
@@ -944,25 +938,12 @@ function getSecureTokenPath() {
|
|
|
944
938
|
const tokenDir = path.join(configHome, "google-workspace-mcp");
|
|
945
939
|
return path.join(tokenDir, "tokens.json");
|
|
946
940
|
}
|
|
947
|
-
function getLegacyTokenPath() {
|
|
948
|
-
const projectRoot = getProjectRoot();
|
|
949
|
-
return path.join(projectRoot, ".gcp-saved-tokens.json");
|
|
950
|
-
}
|
|
951
|
-
function getAdditionalLegacyPaths() {
|
|
952
|
-
return [
|
|
953
|
-
process.env.GOOGLE_TOKEN_PATH,
|
|
954
|
-
path.join(process.cwd(), "google-tokens.json"),
|
|
955
|
-
path.join(process.cwd(), ".gcp-saved-tokens.json")
|
|
956
|
-
].filter(Boolean);
|
|
957
|
-
}
|
|
958
941
|
function getKeysFilePath() {
|
|
959
942
|
const envCredentialsPath = process.env.GOOGLE_DRIVE_OAUTH_CREDENTIALS;
|
|
960
943
|
if (envCredentialsPath) {
|
|
961
944
|
return path.resolve(envCredentialsPath);
|
|
962
945
|
}
|
|
963
|
-
|
|
964
|
-
const keysPath = path.join(projectRoot, "gcp-oauth.keys.json");
|
|
965
|
-
return keysPath;
|
|
946
|
+
return path.join(process.cwd(), "gcp-oauth.keys.json");
|
|
966
947
|
}
|
|
967
948
|
function generateCredentialsErrorMessage() {
|
|
968
949
|
return `
|
|
@@ -1127,46 +1108,13 @@ var TokenManager = class {
|
|
|
1127
1108
|
}
|
|
1128
1109
|
});
|
|
1129
1110
|
}
|
|
1130
|
-
async migrateLegacyTokens() {
|
|
1131
|
-
const legacyPaths = [getLegacyTokenPath(), ...getAdditionalLegacyPaths()];
|
|
1132
|
-
for (const legacyPath of legacyPaths) {
|
|
1133
|
-
try {
|
|
1134
|
-
if (!await fs2.access(legacyPath).then(() => true).catch(() => false)) {
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
const legacyTokens = JSON.parse(await fs2.readFile(legacyPath, "utf-8"));
|
|
1138
|
-
if (!legacyTokens || typeof legacyTokens !== "object") {
|
|
1139
|
-
log(`Invalid legacy token format at ${legacyPath}, skipping`);
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
await this.ensureTokenDirectoryExists();
|
|
1143
|
-
await fs2.writeFile(this.tokenPath, JSON.stringify(legacyTokens, null, 2), {
|
|
1144
|
-
mode: 384
|
|
1145
|
-
});
|
|
1146
|
-
log(`Migrated tokens from legacy location: ${legacyPath} to: ${this.tokenPath}`);
|
|
1147
|
-
try {
|
|
1148
|
-
await fs2.unlink(legacyPath);
|
|
1149
|
-
log("Removed legacy token file");
|
|
1150
|
-
} catch (unlinkErr) {
|
|
1151
|
-
log("Warning: Could not remove legacy token file:", unlinkErr);
|
|
1152
|
-
}
|
|
1153
|
-
return true;
|
|
1154
|
-
} catch (error) {
|
|
1155
|
-
log(`Error migrating legacy tokens from ${legacyPath}`, error);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
return false;
|
|
1159
|
-
}
|
|
1160
1111
|
async loadSavedTokens() {
|
|
1161
1112
|
try {
|
|
1162
1113
|
await this.ensureTokenDirectoryExists();
|
|
1163
1114
|
const tokenExists = await fs2.access(this.tokenPath).then(() => true).catch(() => false);
|
|
1164
1115
|
if (!tokenExists) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
log("No token file found at:", this.tokenPath);
|
|
1168
|
-
return false;
|
|
1169
|
-
}
|
|
1116
|
+
log("No token file found at:", this.tokenPath);
|
|
1117
|
+
return false;
|
|
1170
1118
|
}
|
|
1171
1119
|
const tokens = JSON.parse(await fs2.readFile(this.tokenPath, "utf-8"));
|
|
1172
1120
|
if (!tokens || typeof tokens !== "object") {
|
|
@@ -1529,9 +1477,9 @@ async function authenticate() {
|
|
|
1529
1477
|
|
|
1530
1478
|
// src/index.ts
|
|
1531
1479
|
init_utils();
|
|
1532
|
-
import { fileURLToPath
|
|
1480
|
+
import { fileURLToPath } from "url";
|
|
1533
1481
|
import { readFileSync } from "fs";
|
|
1534
|
-
import { join as join3, dirname as
|
|
1482
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1535
1483
|
|
|
1536
1484
|
// src/config/index.ts
|
|
1537
1485
|
init_services();
|
|
@@ -4500,13 +4448,13 @@ var gmailTools = [
|
|
|
4500
4448
|
},
|
|
4501
4449
|
{
|
|
4502
4450
|
name: "modify_email",
|
|
4503
|
-
description: "Add/remove labels (max 1000 IDs per request)",
|
|
4451
|
+
description: "Add/remove labels on threads (max 1000 IDs per request)",
|
|
4504
4452
|
inputSchema: {
|
|
4505
4453
|
type: "object",
|
|
4506
4454
|
properties: {
|
|
4507
|
-
|
|
4455
|
+
threadId: {
|
|
4508
4456
|
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" }, maxItems: 1e3 }],
|
|
4509
|
-
description: "
|
|
4457
|
+
description: "Thread ID or array of IDs (max 1000)"
|
|
4510
4458
|
},
|
|
4511
4459
|
addLabelIds: {
|
|
4512
4460
|
type: "array",
|
|
@@ -4519,14 +4467,14 @@ var gmailTools = [
|
|
|
4519
4467
|
description: "(optional) Label IDs to remove"
|
|
4520
4468
|
}
|
|
4521
4469
|
},
|
|
4522
|
-
required: ["
|
|
4470
|
+
required: ["threadId"]
|
|
4523
4471
|
},
|
|
4524
4472
|
outputSchema: {
|
|
4525
4473
|
type: "object",
|
|
4526
4474
|
properties: {
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
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" }
|
|
4530
4478
|
}
|
|
4531
4479
|
}
|
|
4532
4480
|
},
|
|
@@ -5875,7 +5823,7 @@ var DeleteEmailSchema = z7.object({
|
|
|
5875
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)")
|
|
5876
5824
|
});
|
|
5877
5825
|
var ModifyEmailSchema = z7.object({
|
|
5878
|
-
|
|
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)"),
|
|
5879
5827
|
addLabelIds: z7.array(z7.string()).optional().describe("Label IDs to add"),
|
|
5880
5828
|
removeLabelIds: z7.array(z7.string()).optional().describe("Label IDs to remove")
|
|
5881
5829
|
});
|
|
@@ -10230,6 +10178,25 @@ var SYSTEM_LABELS = /* @__PURE__ */ new Set([
|
|
|
10230
10178
|
"CATEGORY_UPDATES",
|
|
10231
10179
|
"CATEGORY_FORUMS"
|
|
10232
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
|
+
}
|
|
10233
10200
|
function extractEmailBody(payload) {
|
|
10234
10201
|
if (!payload) return { text: "", html: "" };
|
|
10235
10202
|
const result = { text: "", html: "" };
|
|
@@ -10498,36 +10465,92 @@ async function handleDeleteEmail(gmail, args) {
|
|
|
10498
10465
|
async function handleModifyEmail(gmail, args) {
|
|
10499
10466
|
const validation = validateArgs(ModifyEmailSchema, args);
|
|
10500
10467
|
if (!validation.success) return validation.response;
|
|
10501
|
-
const {
|
|
10502
|
-
const
|
|
10503
|
-
if (
|
|
10504
|
-
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({
|
|
10505
10472
|
userId: "me",
|
|
10506
|
-
id:
|
|
10473
|
+
id: ids[0],
|
|
10507
10474
|
requestBody: { addLabelIds, removeLabelIds }
|
|
10508
10475
|
});
|
|
10509
|
-
log("Modified
|
|
10476
|
+
log("Modified thread labels", { threadId: ids[0], addLabelIds, removeLabelIds });
|
|
10477
|
+
const messageCount = response.data.messages?.length || 0;
|
|
10510
10478
|
return structuredResponse(
|
|
10511
|
-
`
|
|
10512
|
-
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"}`,
|
|
10513
10481
|
{
|
|
10514
10482
|
id: response.data.id,
|
|
10515
|
-
|
|
10516
|
-
|
|
10483
|
+
historyId: response.data.historyId,
|
|
10484
|
+
messageCount,
|
|
10485
|
+
labelIds: response.data.messages?.[0]?.labelIds
|
|
10517
10486
|
}
|
|
10518
10487
|
);
|
|
10519
10488
|
}
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
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
|
+
};
|
|
10527
10515
|
});
|
|
10528
|
-
|
|
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
|
+
}
|
|
10529
10552
|
return successResponse(
|
|
10530
|
-
`Successfully modified labels for ${
|
|
10553
|
+
`Successfully modified labels for ${ids.length} thread(s).` + (addLabelIds ? `
|
|
10531
10554
|
Added labels: ${addLabelIds.join(", ")}` : "") + (removeLabelIds ? `
|
|
10532
10555
|
Removed labels: ${removeLabelIds.join(", ")}` : "")
|
|
10533
10556
|
);
|
|
@@ -10939,8 +10962,8 @@ async function handleListTools(args) {
|
|
|
10939
10962
|
var drive = null;
|
|
10940
10963
|
var authClient = null;
|
|
10941
10964
|
var authenticationPromise = null;
|
|
10942
|
-
var __filename =
|
|
10943
|
-
var __dirname =
|
|
10965
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
10966
|
+
var __dirname = dirname2(__filename);
|
|
10944
10967
|
var packageJsonPath = join3(__dirname, "..", "package.json");
|
|
10945
10968
|
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
10946
10969
|
var VERSION = packageJson.version;
|