@dguido/google-workspace-mcp 3.1.0 → 3.1.1
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 +1 -22
- package/dist/index.js +98 -11
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,7 @@ To enable additional services, add them to `GOOGLE_WORKSPACE_SERVICES`:
|
|
|
162
162
|
|
|
163
163
|
- Omit `GOOGLE_WORKSPACE_SERVICES` entirely to enable all services
|
|
164
164
|
- Unified tools (`create_file`, `update_file`, `get_file_content`) require `drive`, `docs`, `sheets`, and `slides`
|
|
165
|
+
- When you limit services, only the OAuth scopes for those services are requested during authentication. If you change enabled services, re-authenticate to update granted scopes.
|
|
165
166
|
|
|
166
167
|
See [Advanced Configuration](docs/ADVANCED.md) for named profiles, multi-account setup, and environment variables.
|
|
167
168
|
|
|
@@ -266,22 +267,6 @@ If you have a Google Workspace account:
|
|
|
266
267
|
|
|
267
268
|
Use `get_status` to check token age. Tokens older than 6 days show a warning automatically.
|
|
268
269
|
|
|
269
|
-
## Scope Filtering
|
|
270
|
-
|
|
271
|
-
When you limit services via `GOOGLE_WORKSPACE_SERVICES`, only the OAuth scopes for those services are requested during authentication. This provides:
|
|
272
|
-
|
|
273
|
-
- **Cleaner consent screen** - Users see only the permissions they need
|
|
274
|
-
- **Principle of least privilege** - App only has access to enabled services
|
|
275
|
-
|
|
276
|
-
For example, setting `GOOGLE_WORKSPACE_SERVICES=drive,calendar` will only request Drive and Calendar scopes, not Gmail or Contacts.
|
|
277
|
-
|
|
278
|
-
**Note:** If you change enabled services, re-authenticate to update granted scopes:
|
|
279
|
-
|
|
280
|
-
```bash
|
|
281
|
-
rm ~/.config/google-workspace-mcp/tokens.json
|
|
282
|
-
npx @dguido/google-workspace-mcp auth
|
|
283
|
-
```
|
|
284
|
-
|
|
285
270
|
## Development
|
|
286
271
|
|
|
287
272
|
```bash
|
|
@@ -293,12 +278,6 @@ npm test # Run tests
|
|
|
293
278
|
|
|
294
279
|
See [Contributing Guide](CONTRIBUTING.md) for project structure and development workflow.
|
|
295
280
|
|
|
296
|
-
## Links
|
|
297
|
-
|
|
298
|
-
- [GitHub Repository](https://github.com/dguido/google-workspace-mcp)
|
|
299
|
-
- [Issue Tracker](https://github.com/dguido/google-workspace-mcp/issues)
|
|
300
|
-
- [Model Context Protocol](https://modelcontextprotocol.io)
|
|
301
|
-
|
|
302
281
|
## Origin
|
|
303
282
|
|
|
304
283
|
This project is a substantial rewrite of [piotr-agier/google-drive-mcp](https://github.com/piotr-agier/google-drive-mcp), originally created by Piotr Agier.
|
package/dist/index.js
CHANGED
|
@@ -5253,16 +5253,50 @@ var gmailTools = [
|
|
|
5253
5253
|
},
|
|
5254
5254
|
{
|
|
5255
5255
|
name: "search_emails",
|
|
5256
|
-
description: "Search emails using Gmail query syntax (max 500 per request)",
|
|
5256
|
+
description: "Search emails using structured parameters or Gmail query syntax (max 500 per request). At least one search parameter required.",
|
|
5257
5257
|
inputSchema: {
|
|
5258
5258
|
type: "object",
|
|
5259
5259
|
properties: {
|
|
5260
|
-
query: {
|
|
5260
|
+
query: {
|
|
5261
|
+
type: "string",
|
|
5262
|
+
description: "Gmail search query. Operators: from: to: subject: has:attachment is:unread after:YYYY/MM/DD before:YYYY/MM/DD larger: smaller: label:. Gmail ignores special characters like $ and commas \u2014 use plain numbers (5149 not $5,149)."
|
|
5263
|
+
},
|
|
5264
|
+
from: {
|
|
5265
|
+
type: "string",
|
|
5266
|
+
description: "Sender email or name"
|
|
5267
|
+
},
|
|
5268
|
+
to: {
|
|
5269
|
+
type: "string",
|
|
5270
|
+
description: "Recipient email or name"
|
|
5271
|
+
},
|
|
5272
|
+
subject: {
|
|
5273
|
+
type: "string",
|
|
5274
|
+
description: "Subject line text"
|
|
5275
|
+
},
|
|
5276
|
+
after: {
|
|
5277
|
+
type: "string",
|
|
5278
|
+
description: "After date (YYYY/MM/DD)"
|
|
5279
|
+
},
|
|
5280
|
+
before: {
|
|
5281
|
+
type: "string",
|
|
5282
|
+
description: "Before date (YYYY/MM/DD)"
|
|
5283
|
+
},
|
|
5284
|
+
hasAttachment: {
|
|
5285
|
+
type: "boolean",
|
|
5286
|
+
description: "Filter for messages with attachments"
|
|
5287
|
+
},
|
|
5288
|
+
label: {
|
|
5289
|
+
type: "string",
|
|
5290
|
+
description: "Gmail label name"
|
|
5291
|
+
},
|
|
5261
5292
|
maxResults: {
|
|
5262
5293
|
type: "number",
|
|
5263
5294
|
description: "(optional, default: 50) Maximum results (max 500)"
|
|
5264
5295
|
},
|
|
5265
|
-
pageToken: {
|
|
5296
|
+
pageToken: {
|
|
5297
|
+
type: "string",
|
|
5298
|
+
description: "(optional) Pagination token"
|
|
5299
|
+
},
|
|
5266
5300
|
labelIds: {
|
|
5267
5301
|
type: "array",
|
|
5268
5302
|
items: { type: "string" },
|
|
@@ -5272,8 +5306,7 @@ var gmailTools = [
|
|
|
5272
5306
|
type: "boolean",
|
|
5273
5307
|
description: "(optional, default: false) Include spam and trash"
|
|
5274
5308
|
}
|
|
5275
|
-
}
|
|
5276
|
-
required: ["query"]
|
|
5309
|
+
}
|
|
5277
5310
|
},
|
|
5278
5311
|
outputSchema: {
|
|
5279
5312
|
type: "object",
|
|
@@ -7138,12 +7171,24 @@ var ReadEmailSchema = z8.object({
|
|
|
7138
7171
|
contentFormat: z8.enum(["full", "text", "headers"]).optional().default("full").describe("Content format: 'full' (text+HTML), 'text' (plain text only), 'headers' (no body)")
|
|
7139
7172
|
});
|
|
7140
7173
|
var SearchEmailsSchema = z8.object({
|
|
7141
|
-
query: z8.string().
|
|
7174
|
+
query: z8.string().max(500).optional().describe(
|
|
7175
|
+
"Gmail search query. Operators: from: to: subject: has:attachment is:unread after:YYYY/MM/DD before:YYYY/MM/DD larger: smaller: label:. Gmail ignores special characters like $ and commas \u2014 use plain numbers (5149 not $5,149)."
|
|
7176
|
+
),
|
|
7177
|
+
from: z8.string().max(254).optional().describe("Sender email or name"),
|
|
7178
|
+
to: z8.string().max(254).optional().describe("Recipient email or name"),
|
|
7179
|
+
subject: z8.string().max(500).optional().describe("Subject line text"),
|
|
7180
|
+
after: z8.string().max(10).optional().describe("After date (YYYY/MM/DD)"),
|
|
7181
|
+
before: z8.string().max(10).optional().describe("Before date (YYYY/MM/DD)"),
|
|
7182
|
+
hasAttachment: z8.boolean().optional().describe("Filter for messages with attachments"),
|
|
7183
|
+
label: z8.string().max(225).optional().describe("Gmail label name"),
|
|
7142
7184
|
maxResults: z8.number().int().min(1).max(500).optional().default(50).describe("Maximum results"),
|
|
7143
7185
|
pageToken: z8.string().optional().describe("Token for pagination"),
|
|
7144
7186
|
labelIds: z8.array(z8.string()).optional().describe("Filter by label IDs"),
|
|
7145
7187
|
includeSpamTrash: z8.boolean().optional().default(false).describe("Include spam and trash")
|
|
7146
|
-
})
|
|
7188
|
+
}).refine(
|
|
7189
|
+
(d) => d.query || d.from || d.to || d.subject || d.after || d.before || d.hasAttachment || d.label,
|
|
7190
|
+
{ message: "At least one search parameter required" }
|
|
7191
|
+
);
|
|
7147
7192
|
var DeleteEmailSchema = z8.object({
|
|
7148
7193
|
messageId: z8.union([z8.string().min(1), z8.array(z8.string().min(1)).min(1).max(1e3)]).describe("Message ID or array of IDs (max 1000 for batch)")
|
|
7149
7194
|
});
|
|
@@ -11891,10 +11936,45 @@ async function handleReadEmail(gmail, args) {
|
|
|
11891
11936
|
truncated
|
|
11892
11937
|
});
|
|
11893
11938
|
}
|
|
11939
|
+
function buildSearchQuery(args) {
|
|
11940
|
+
const parts = [];
|
|
11941
|
+
if (args.from) parts.push(`from:${args.from}`);
|
|
11942
|
+
if (args.to) parts.push(`to:${args.to}`);
|
|
11943
|
+
if (args.subject) parts.push(`subject:${args.subject}`);
|
|
11944
|
+
if (args.after) parts.push(`after:${args.after}`);
|
|
11945
|
+
if (args.before) parts.push(`before:${args.before}`);
|
|
11946
|
+
if (args.hasAttachment) parts.push("has:attachment");
|
|
11947
|
+
if (args.label) parts.push(`label:${args.label}`);
|
|
11948
|
+
if (args.query) parts.push(args.query);
|
|
11949
|
+
return parts.join(" ");
|
|
11950
|
+
}
|
|
11951
|
+
function buildSearchHints(args) {
|
|
11952
|
+
const hints = [];
|
|
11953
|
+
if (args.query && /[$#,]/.test(args.query)) {
|
|
11954
|
+
hints.push(
|
|
11955
|
+
"Gmail ignores special characters like $, #, and commas \u2014 use plain numbers (e.g. 5149 not $5,149)"
|
|
11956
|
+
);
|
|
11957
|
+
}
|
|
11958
|
+
const dateFormat = /^\d{4}\/\d{2}\/\d{2}$/;
|
|
11959
|
+
if (args.after && !dateFormat.test(args.after)) {
|
|
11960
|
+
hints.push(`Date format for 'after' should be YYYY/MM/DD (got: ${args.after})`);
|
|
11961
|
+
}
|
|
11962
|
+
if (args.before && !dateFormat.test(args.before)) {
|
|
11963
|
+
hints.push(`Date format for 'before' should be YYYY/MM/DD (got: ${args.before})`);
|
|
11964
|
+
}
|
|
11965
|
+
if (args.query && /\d{4}[-/]\d{2}[-/]\d{2}/.test(args.query) && !/(?:after|before):/.test(args.query)) {
|
|
11966
|
+
hints.push("Dates in query need operators: use after:YYYY/MM/DD or before:YYYY/MM/DD");
|
|
11967
|
+
}
|
|
11968
|
+
if (args.query && args.query.length > 200) {
|
|
11969
|
+
hints.push("Try simplifying \u2014 shorter queries often match more");
|
|
11970
|
+
}
|
|
11971
|
+
return hints;
|
|
11972
|
+
}
|
|
11894
11973
|
async function handleSearchEmails(gmail, args) {
|
|
11895
11974
|
const validation = validateArgs(SearchEmailsSchema, args);
|
|
11896
11975
|
if (!validation.success) return validation.response;
|
|
11897
|
-
const {
|
|
11976
|
+
const { maxResults, pageToken, labelIds, includeSpamTrash } = validation.data;
|
|
11977
|
+
const query = buildSearchQuery(validation.data);
|
|
11898
11978
|
const response = await gmail.users.messages.list({
|
|
11899
11979
|
userId: "me",
|
|
11900
11980
|
q: query,
|
|
@@ -11905,7 +11985,12 @@ async function handleSearchEmails(gmail, args) {
|
|
|
11905
11985
|
});
|
|
11906
11986
|
const messages = response.data.messages || [];
|
|
11907
11987
|
if (messages.length === 0) {
|
|
11908
|
-
|
|
11988
|
+
const hints = buildSearchHints(validation.data);
|
|
11989
|
+
const text = `No emails found matching: ${query}` + (hints.length ? `
|
|
11990
|
+
|
|
11991
|
+
Hints:
|
|
11992
|
+
${hints.map((h) => `- ${h}`).join("\n")}` : "");
|
|
11993
|
+
return structuredResponse(text, { messages: [] });
|
|
11909
11994
|
}
|
|
11910
11995
|
const messageDetails = await Promise.all(
|
|
11911
11996
|
messages.slice(0, 50).map(async (msg) => {
|
|
@@ -13303,10 +13388,12 @@ async function ensureAuthenticated() {
|
|
|
13303
13388
|
authenticationPromise = authenticate();
|
|
13304
13389
|
try {
|
|
13305
13390
|
authClient = await authenticationPromise;
|
|
13391
|
+
const hasCredentials = !!authClient?.credentials;
|
|
13392
|
+
const hasAccessToken = !!authClient?.credentials?.access_token;
|
|
13306
13393
|
log("Authentication complete", {
|
|
13307
13394
|
authClientType: authClient?.constructor?.name,
|
|
13308
|
-
hasCredentials
|
|
13309
|
-
hasAccessToken
|
|
13395
|
+
hasCredentials,
|
|
13396
|
+
hasAccessToken
|
|
13310
13397
|
});
|
|
13311
13398
|
ensureDriveService();
|
|
13312
13399
|
const healthy = await verifyAuthHealth();
|