mcp-google-multi 6.0.0-beta.1 → 6.0.0-beta.2
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 +5 -3
- package/dist/accounts.d.ts +5 -0
- package/dist/accounts.js +12 -1
- package/dist/api-probe.js +3 -0
- package/dist/arg-normalize.d.ts +61 -4
- package/dist/arg-normalize.js +228 -4
- package/dist/arg-strict.d.ts +54 -0
- package/dist/arg-strict.js +163 -0
- package/dist/auth.js +15 -72
- package/dist/client.js +15 -5
- package/dist/config-file.d.ts +1 -0
- package/dist/config-file.js +4 -0
- package/dist/discover.js +35 -14
- package/dist/discovery-client.d.ts +8 -1
- package/dist/discovery-client.js +96 -17
- package/dist/doctor.d.ts +9 -1
- package/dist/doctor.js +32 -4
- package/dist/env-load.d.ts +11 -0
- package/dist/env-load.js +23 -1
- package/dist/executor.js +12 -1
- package/dist/fanout.js +43 -8
- package/dist/http-transport.d.ts +11 -2
- package/dist/http-transport.js +10 -5
- package/dist/index.js +112 -12
- package/dist/metrics-cli.d.ts +21 -0
- package/dist/metrics-cli.js +206 -0
- package/dist/metrics-tap.d.ts +12 -0
- package/dist/metrics-tap.js +95 -0
- package/dist/oauth-consent.d.ts +25 -10
- package/dist/oauth-consent.js +85 -39
- package/dist/outbound-allowlist.d.ts +34 -0
- package/dist/outbound-allowlist.js +108 -0
- package/dist/registry.d.ts +49 -2
- package/dist/registry.js +147 -7
- package/dist/scope-catalog.js +16 -0
- package/dist/services.d.ts +8 -0
- package/dist/services.js +28 -1
- package/dist/setup-prompt.d.ts +1 -1
- package/dist/setup-prompt.js +1 -1
- package/dist/tools/_coerce.d.ts +5 -0
- package/dist/tools/_coerce.js +11 -0
- package/dist/tools/_errors.d.ts +31 -3
- package/dist/tools/_errors.js +307 -43
- package/dist/tools/_local-files.js +7 -2
- package/dist/tools/account-wizard.d.ts +24 -1
- package/dist/tools/account-wizard.js +152 -51
- package/dist/tools/admin.js +7 -6
- package/dist/tools/analytics.d.ts +18 -0
- package/dist/tools/analytics.js +284 -0
- package/dist/tools/calendar.d.ts +7 -0
- package/dist/tools/calendar.js +75 -15
- package/dist/tools/chat.js +7 -4
- package/dist/tools/contacts.js +57 -30
- package/dist/tools/docs.js +56 -54
- package/dist/tools/drive.js +132 -70
- package/dist/tools/forms.js +7 -7
- package/dist/tools/generated/_shared.d.ts +6 -0
- package/dist/tools/generated/_shared.js +11 -1
- package/dist/tools/generated/admin.js +345 -214
- package/dist/tools/generated/analytics.d.ts +2 -0
- package/dist/tools/generated/analytics.js +981 -0
- package/dist/tools/generated/appsmarket.js +4 -4
- package/dist/tools/generated/calendar.js +28 -28
- package/dist/tools/generated/chat.js +83 -56
- package/dist/tools/generated/classroom.js +258 -216
- package/dist/tools/generated/cloudidentity.js +76 -68
- package/dist/tools/generated/cloudsearch.js +40 -39
- package/dist/tools/generated/contacts.js +20 -11
- package/dist/tools/generated/drive.js +62 -53
- package/dist/tools/generated/drivelabels.js +60 -32
- package/dist/tools/generated/forms.js +7 -7
- package/dist/tools/generated/gmail.js +136 -109
- package/dist/tools/generated/groupsmigration.js +1 -1
- package/dist/tools/generated/groupssettings.js +3 -3
- package/dist/tools/generated/index.js +2 -0
- package/dist/tools/generated/keep.js +8 -7
- package/dist/tools/generated/licensing.js +39 -23
- package/dist/tools/generated/meet.js +14 -14
- package/dist/tools/generated/method-map.d.ts +2 -0
- package/dist/tools/generated/method-map.js +894 -0
- package/dist/tools/generated/postmaster.js +3 -3
- package/dist/tools/generated/reseller.js +34 -28
- package/dist/tools/generated/script.js +29 -19
- package/dist/tools/generated/searchconsole.js +5 -3
- package/dist/tools/generated/sheets.js +10 -9
- package/dist/tools/generated/tasks.js +10 -4
- package/dist/tools/generated/vault.js +65 -56
- package/dist/tools/generated/workspaceevents.js +17 -16
- package/dist/tools/gmail.d.ts +10 -1
- package/dist/tools/gmail.js +50 -18
- package/dist/tools/google-api.js +48 -19
- package/dist/tools/meet.js +5 -2
- package/dist/tools/searchconsole.js +10 -10
- package/dist/tools/sheets.js +36 -48
- package/dist/tools/slides.js +6 -6
- package/dist/tools/tasks.js +16 -16
- package/dist/trim.d.ts +21 -0
- package/dist/trim.js +47 -0
- package/dist/usage-metrics.d.ts +155 -0
- package/dist/usage-metrics.js +603 -0
- package/dist/write-control.d.ts +27 -1
- package/dist/write-control.js +90 -11
- package/package.json +8 -3
package/dist/tools/contacts.js
CHANGED
|
@@ -3,9 +3,12 @@ import { people as peopleClient } from '@googleapis/people';
|
|
|
3
3
|
import { accountAliasSchema } from '../accounts.js';
|
|
4
4
|
import { getClient } from '../client.js';
|
|
5
5
|
import { coerceBoolean } from './_coerce.js';
|
|
6
|
-
import { handleGoogleApiError } from './_errors.js';
|
|
6
|
+
import { handleGoogleApiError, invalidParams } from './_errors.js';
|
|
7
|
+
import { listResult } from '../trim.js';
|
|
7
8
|
const accountEnum = accountAliasSchema.optional();
|
|
8
9
|
const PERSON_FIELDS = 'names,emailAddresses,phoneNumbers,organizations,addresses,photos,memberships';
|
|
10
|
+
// people.getBatchGet rejects more than 200 resource names per call.
|
|
11
|
+
const BATCH_GET_LIMIT = 200;
|
|
9
12
|
function formatContact(person) {
|
|
10
13
|
return {
|
|
11
14
|
resourceName: person.resourceName,
|
|
@@ -143,9 +146,15 @@ export function registerContactsTools(server) {
|
|
|
143
146
|
readMask: PERSON_FIELDS,
|
|
144
147
|
pageSize: pageSize ?? 10,
|
|
145
148
|
});
|
|
149
|
+
const limit = pageSize ?? 10;
|
|
146
150
|
const contacts = (res.data.results ?? []).map((r) => formatContact(r.person));
|
|
147
151
|
return {
|
|
148
|
-
content: [{ type: 'text', text: JSON.stringify(contacts,
|
|
152
|
+
content: [{ type: 'text', text: JSON.stringify(listResult('contacts', contacts, {
|
|
153
|
+
// searchContacts returns no token and no total, so a full page is
|
|
154
|
+
// the only evidence that the cap, not the address book, ended it.
|
|
155
|
+
capped: contacts.length >= limit,
|
|
156
|
+
hint: `The page is full at ${limit} of a possible 30, so more contacts may match. Raise pageSize or narrow the query. This search endpoint has no pageToken, so there is no next page to fetch.`,
|
|
157
|
+
}), null, 2) }],
|
|
149
158
|
};
|
|
150
159
|
}
|
|
151
160
|
catch (error) {
|
|
@@ -200,7 +209,7 @@ export function registerContactsTools(server) {
|
|
|
200
209
|
description: 'Get a single contact by resource name',
|
|
201
210
|
inputSchema: {
|
|
202
211
|
account: accountEnum.describe('Google account alias'),
|
|
203
|
-
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
212
|
+
resourceName: z.string().min(1).describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
204
213
|
},
|
|
205
214
|
}, async ({ account, resourceName }) => {
|
|
206
215
|
try {
|
|
@@ -307,7 +316,7 @@ export function registerContactsTools(server) {
|
|
|
307
316
|
description: 'Update an existing contact (reads current etag automatically)',
|
|
308
317
|
inputSchema: {
|
|
309
318
|
account: accountEnum.describe('Google account alias'),
|
|
310
|
-
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
319
|
+
resourceName: z.string().min(1).describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
311
320
|
givenName: z.string().optional().describe('Updated first name'),
|
|
312
321
|
familyName: z.string().optional().describe('Updated last name'),
|
|
313
322
|
email: z.string().optional().describe('Updated email address'),
|
|
@@ -354,12 +363,7 @@ export function registerContactsTools(server) {
|
|
|
354
363
|
updateFields.push('organizations');
|
|
355
364
|
}
|
|
356
365
|
if (updateFields.length === 0) {
|
|
357
|
-
return
|
|
358
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
359
|
-
error: 'No fields to update. Provide at least one of: givenName, familyName, email, phone, organization, jobTitle',
|
|
360
|
-
}, null, 2) }],
|
|
361
|
-
isError: true,
|
|
362
|
-
};
|
|
366
|
+
return invalidParams(account, 'No fields to update: every optional field was omitted, so the request would have been a no-op.', 'Pass at least one of: givenName, familyName, email, phone, organization, jobTitle.');
|
|
363
367
|
}
|
|
364
368
|
const res = await people.people.updateContact({
|
|
365
369
|
resourceName,
|
|
@@ -379,7 +383,7 @@ export function registerContactsTools(server) {
|
|
|
379
383
|
description: 'Delete a contact (permanent, cannot be undone)',
|
|
380
384
|
inputSchema: {
|
|
381
385
|
account: accountEnum.describe('Google account alias'),
|
|
382
|
-
resourceName: z.string().describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
386
|
+
resourceName: z.string().min(1).describe('Contact resource name (e.g. "people/c1234567890")'),
|
|
383
387
|
},
|
|
384
388
|
}, async ({ account, resourceName }) => {
|
|
385
389
|
try {
|
|
@@ -429,7 +433,7 @@ export function registerContactsTools(server) {
|
|
|
429
433
|
description: 'List members of a contact group',
|
|
430
434
|
inputSchema: {
|
|
431
435
|
account: accountEnum.describe('Google account alias'),
|
|
432
|
-
groupResourceName: z.string().describe('Contact group resource name (e.g. "contactGroups/abc123")'),
|
|
436
|
+
groupResourceName: z.string().min(1).describe('Contact group resource name (e.g. "contactGroups/abc123")'),
|
|
433
437
|
maxMembers: z.number().min(1).max(1000).default(100).optional()
|
|
434
438
|
.describe('Max member resource names to return (default: 100)'),
|
|
435
439
|
},
|
|
@@ -442,26 +446,49 @@ export function registerContactsTools(server) {
|
|
|
442
446
|
maxMembers: maxMembers ?? 100,
|
|
443
447
|
});
|
|
444
448
|
const memberResourceNames = groupRes.data.memberResourceNames ?? [];
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
449
|
+
const members = [];
|
|
450
|
+
let fetchFailures = 0;
|
|
451
|
+
let firstError;
|
|
452
|
+
// getBatchGet caps at 200 names while maxMembers goes to 1000, so a
|
|
453
|
+
// large group used to fail outright rather than come back in pages.
|
|
454
|
+
for (let i = 0; i < memberResourceNames.length; i += BATCH_GET_LIMIT) {
|
|
455
|
+
const chunk = memberResourceNames.slice(i, i + BATCH_GET_LIMIT);
|
|
456
|
+
try {
|
|
457
|
+
const membersRes = await people.people.getBatchGet({
|
|
458
|
+
resourceNames: chunk,
|
|
459
|
+
personFields: PERSON_FIELDS,
|
|
460
|
+
});
|
|
461
|
+
const got = (membersRes.data.responses ?? []).filter((r) => r.person);
|
|
462
|
+
fetchFailures += chunk.length - got.length;
|
|
463
|
+
for (const r of got)
|
|
464
|
+
members.push(formatContact(r.person));
|
|
465
|
+
}
|
|
466
|
+
catch (chunkError) {
|
|
467
|
+
firstError ??= chunkError;
|
|
468
|
+
fetchFailures += chunk.length;
|
|
469
|
+
}
|
|
452
470
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
471
|
+
// Every chunk failed, so there is no partial answer to report honestly.
|
|
472
|
+
if (firstError && members.length === 0 && memberResourceNames.length > 0)
|
|
473
|
+
throw firstError;
|
|
474
|
+
const total = groupRes.data.memberCount ?? memberResourceNames.length;
|
|
475
|
+
const namesTruncated = total > memberResourceNames.length;
|
|
476
|
+
const causes = [];
|
|
477
|
+
if (namesTruncated)
|
|
478
|
+
causes.push(`the maxMembers cap returned ${memberResourceNames.length} of ${total} member names`);
|
|
479
|
+
if (fetchFailures > 0)
|
|
480
|
+
causes.push(`${fetchFailures} member record(s) could not be fetched`);
|
|
460
481
|
return {
|
|
461
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
482
|
+
content: [{ type: 'text', text: JSON.stringify(listResult('members', members, {
|
|
483
|
+
totalItems: total,
|
|
484
|
+
extra: {
|
|
485
|
+
group: groupRes.data.name,
|
|
486
|
+
...(fetchFailures > 0 ? { fetchFailures } : {}),
|
|
487
|
+
},
|
|
488
|
+
hint: causes.length > 0
|
|
489
|
+
? `Incomplete because ${causes.join(' and ')}.${namesTruncated ? ' Raise maxMembers to see the rest.' : ''}`
|
|
490
|
+
: undefined,
|
|
491
|
+
}), null, 2) }],
|
|
465
492
|
};
|
|
466
493
|
}
|
|
467
494
|
catch (error) {
|
package/dist/tools/docs.js
CHANGED
|
@@ -3,7 +3,7 @@ import { coerceBoolean, coerceJson } from './_coerce.js';
|
|
|
3
3
|
import { docs as docsClient } from '@googleapis/docs';
|
|
4
4
|
import { accountAliasSchema } from '../accounts.js';
|
|
5
5
|
import { getClient } from '../client.js';
|
|
6
|
-
import { handleGoogleApiError } from './_errors.js';
|
|
6
|
+
import { handleGoogleApiError, invalidParams } from './_errors.js';
|
|
7
7
|
import { capText } from '../trim.js';
|
|
8
8
|
const accountEnum = accountAliasSchema.optional();
|
|
9
9
|
// A single very large insertText has been observed to apply only partially while
|
|
@@ -199,7 +199,7 @@ export function registerDocsTools(server) {
|
|
|
199
199
|
description: 'Get document metadata (title, revision, named ranges). Optionally include tab content and choose a suggestionsViewMode.',
|
|
200
200
|
inputSchema: {
|
|
201
201
|
account: accountEnum.describe('Google account alias'),
|
|
202
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
202
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
203
203
|
includeTabsContent: coerceBoolean.optional()
|
|
204
204
|
.describe('Include full content for every tab (default: false — first tab only)'),
|
|
205
205
|
suggestionsViewMode: z.enum([
|
|
@@ -236,7 +236,7 @@ export function registerDocsTools(server) {
|
|
|
236
236
|
description: 'Read document content as plain text with a heading index (returns up to maxChars characters per call). Pass heading or headingIndex to read one section, tabId to read a specific tab.',
|
|
237
237
|
inputSchema: {
|
|
238
238
|
account: accountEnum.describe('Google account alias'),
|
|
239
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
239
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
240
240
|
heading: z.string().optional()
|
|
241
241
|
.describe('Read only the section under this heading (case-insensitive; exact match first, then substring)'),
|
|
242
242
|
headingIndex: z.number().int().min(0).optional()
|
|
@@ -251,13 +251,7 @@ export function registerDocsTools(server) {
|
|
|
251
251
|
}, async ({ account, documentId, heading, headingIndex, tabId, maxChars, offset }) => {
|
|
252
252
|
try {
|
|
253
253
|
if (heading !== undefined && headingIndex !== undefined) {
|
|
254
|
-
return
|
|
255
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
256
|
-
error: 'invalid_params',
|
|
257
|
-
message: 'Provide either heading or headingIndex, not both',
|
|
258
|
-
}, null, 2) }],
|
|
259
|
-
isError: true,
|
|
260
|
-
};
|
|
254
|
+
return invalidParams(account, 'Both heading and headingIndex were supplied, and they address different sections.', 'Pass heading to look a section up by its text, or headingIndex to address one by position. Not both.');
|
|
261
255
|
}
|
|
262
256
|
const auth = await getClient(account);
|
|
263
257
|
const docs = docsClient({ version: 'v1', auth });
|
|
@@ -272,7 +266,10 @@ export function registerDocsTools(server) {
|
|
|
272
266
|
return {
|
|
273
267
|
content: [{ type: 'text', text: JSON.stringify({
|
|
274
268
|
error: 'not_found',
|
|
275
|
-
message: `Tab "${tabId}" not found in document ${documentId}
|
|
269
|
+
message: `Tab "${tabId}" was not found in document ${documentId}.`,
|
|
270
|
+
hint: 'Pick a tabId from availableTabs below, or omit tabId to read the first tab.',
|
|
271
|
+
retriable: false,
|
|
272
|
+
account: account,
|
|
276
273
|
availableTabs: listTabs(res.data.tabs),
|
|
277
274
|
}, null, 2) }],
|
|
278
275
|
isError: true,
|
|
@@ -288,7 +285,10 @@ export function registerDocsTools(server) {
|
|
|
288
285
|
return {
|
|
289
286
|
content: [{ type: 'text', text: JSON.stringify({
|
|
290
287
|
error: 'invalid_params',
|
|
291
|
-
message: `headingIndex ${headingIndex} is out of range
|
|
288
|
+
message: `headingIndex ${headingIndex} is out of range: the document has ${headings.length} heading(s).`,
|
|
289
|
+
hint: 'Indexes are zero-based. Pick one from the headings list below, or omit headingIndex to read the whole document.',
|
|
290
|
+
retriable: false,
|
|
291
|
+
account: account,
|
|
292
292
|
headings: headings.map(({ i, level, text }) => ({ i, level, text })),
|
|
293
293
|
}, null, 2) }],
|
|
294
294
|
isError: true,
|
|
@@ -300,10 +300,17 @@ export function registerDocsTools(server) {
|
|
|
300
300
|
if ('error' in resolved) {
|
|
301
301
|
return {
|
|
302
302
|
content: [{ type: 'text', text: JSON.stringify({
|
|
303
|
-
|
|
303
|
+
// 'ambiguous', not 'ambiguous_heading': the slug vocabulary is
|
|
304
|
+
// shared, and a one-off name buckets as `other` in metrics.
|
|
305
|
+
error: resolved.error === 'ambiguous' ? 'ambiguous' : 'not_found',
|
|
304
306
|
message: resolved.error === 'ambiguous'
|
|
305
|
-
? `Heading "${heading}" matches
|
|
306
|
-
: `Heading "${heading}" not found in document ${documentId}
|
|
307
|
+
? `Heading "${heading}" matches ${resolved.candidates.length} headings in document ${documentId}.`
|
|
308
|
+
: `Heading "${heading}" was not found in document ${documentId}.`,
|
|
309
|
+
hint: resolved.error === 'ambiguous'
|
|
310
|
+
? 'Pass headingIndex with the i value of the one you want, from candidates below.'
|
|
311
|
+
: 'Pick a heading from candidates below (it lists every heading in the document), or pass headingIndex.',
|
|
312
|
+
retriable: false,
|
|
313
|
+
account: account,
|
|
307
314
|
candidates: resolved.candidates.map(({ i, level, text }) => ({ i, level, text })),
|
|
308
315
|
}, null, 2) }],
|
|
309
316
|
isError: true,
|
|
@@ -338,7 +345,7 @@ export function registerDocsTools(server) {
|
|
|
338
345
|
description: 'Insert text into a document at a specific position or at the end',
|
|
339
346
|
inputSchema: {
|
|
340
347
|
account: accountEnum.describe('Google account alias'),
|
|
341
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
348
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
342
349
|
text: z.string().describe('Text to insert'),
|
|
343
350
|
index: z.number().min(1).optional()
|
|
344
351
|
.describe('Character index to insert at (1-based). Omit to append at the end'),
|
|
@@ -377,7 +384,7 @@ export function registerDocsTools(server) {
|
|
|
377
384
|
description: 'Find and replace all occurrences of text in a document',
|
|
378
385
|
inputSchema: {
|
|
379
386
|
account: accountEnum.describe('Google account alias'),
|
|
380
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
387
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
381
388
|
findText: z.string().describe('Text to search for'),
|
|
382
389
|
replaceText: z.string().describe('Replacement text'),
|
|
383
390
|
matchCase: coerceBoolean.default(true).optional()
|
|
@@ -414,7 +421,7 @@ export function registerDocsTools(server) {
|
|
|
414
421
|
description: 'Delete content in a character index range within a document',
|
|
415
422
|
inputSchema: {
|
|
416
423
|
account: accountEnum.describe('Google account alias'),
|
|
417
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
424
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
418
425
|
startIndex: z.number().min(1).describe('Start index (inclusive, 1-based)'),
|
|
419
426
|
endIndex: z.number().min(2).describe('End index (exclusive)'),
|
|
420
427
|
},
|
|
@@ -447,7 +454,7 @@ export function registerDocsTools(server) {
|
|
|
447
454
|
description: 'Update text formatting (bold, italic, underline, font, size) for a range',
|
|
448
455
|
inputSchema: {
|
|
449
456
|
account: accountEnum.describe('Google account alias'),
|
|
450
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
457
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
451
458
|
startIndex: z.number().min(1).describe('Start index (inclusive, 1-based)'),
|
|
452
459
|
endIndex: z.number().min(2).describe('End index (exclusive)'),
|
|
453
460
|
bold: coerceBoolean.optional().describe('Set bold'),
|
|
@@ -483,12 +490,7 @@ export function registerDocsTools(server) {
|
|
|
483
490
|
fields.push('weightedFontFamily');
|
|
484
491
|
}
|
|
485
492
|
if (fields.length === 0) {
|
|
486
|
-
return
|
|
487
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
488
|
-
error: 'No style properties provided. Set at least one of: bold, italic, underline, fontSize, fontFamily',
|
|
489
|
-
}, null, 2) }],
|
|
490
|
-
isError: true,
|
|
491
|
-
};
|
|
493
|
+
return invalidParams(account, 'No style properties to apply: every optional property was omitted.', 'Pass at least one of: bold, italic, underline, fontSize, fontFamily.');
|
|
492
494
|
}
|
|
493
495
|
const res = await docs.documents.batchUpdate({
|
|
494
496
|
documentId,
|
|
@@ -518,7 +520,7 @@ export function registerDocsTools(server) {
|
|
|
518
520
|
description: 'Insert a table into a document at a specific position',
|
|
519
521
|
inputSchema: {
|
|
520
522
|
account: accountEnum.describe('Google account alias'),
|
|
521
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
523
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
522
524
|
rows: z.number().min(1).describe('Number of rows'),
|
|
523
525
|
columns: z.number().min(1).describe('Number of columns'),
|
|
524
526
|
index: z.number().min(1).optional()
|
|
@@ -555,7 +557,7 @@ export function registerDocsTools(server) {
|
|
|
555
557
|
description: 'Tag a range of text with a name. Multiple ranges can share the same name. Used as a template anchor for docs_replace_named_range_content.',
|
|
556
558
|
inputSchema: {
|
|
557
559
|
account: accountEnum.describe('Google account alias'),
|
|
558
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
560
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
559
561
|
name: z.string().describe('Name (1-256 chars). Need not be unique.'),
|
|
560
562
|
startIndex: z.number().min(1).describe('Range start (inclusive)'),
|
|
561
563
|
endIndex: z.number().min(2).describe('Range end (exclusive)'),
|
|
@@ -588,14 +590,14 @@ export function registerDocsTools(server) {
|
|
|
588
590
|
description: 'Delete a named range by its ID or by name. Removes every range matching the identifier.',
|
|
589
591
|
inputSchema: {
|
|
590
592
|
account: accountEnum.describe('Google account alias'),
|
|
591
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
593
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
592
594
|
namedRangeId: z.string().optional().describe('Specific named range ID'),
|
|
593
595
|
name: z.string().optional().describe('Name (deletes ALL named ranges with this name)'),
|
|
594
596
|
},
|
|
595
597
|
}, async ({ account, documentId, namedRangeId, name }) => {
|
|
596
598
|
try {
|
|
597
599
|
if (!namedRangeId && !name) {
|
|
598
|
-
return
|
|
600
|
+
return invalidParams(account, 'Neither namedRangeId nor name was supplied, so no named range is addressed.', 'Pass namedRangeId to delete one specific range, or name to delete every range carrying that name.');
|
|
599
601
|
}
|
|
600
602
|
const auth = await getClient(account);
|
|
601
603
|
const docs = docsClient({ version: 'v1', auth });
|
|
@@ -620,7 +622,7 @@ export function registerDocsTools(server) {
|
|
|
620
622
|
description: 'Replace the content of every named range matching the identifier with the supplied text. Real mail-merge primitive — far more robust than replaceAllText for templated docs.',
|
|
621
623
|
inputSchema: {
|
|
622
624
|
account: accountEnum.describe('Google account alias'),
|
|
623
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
625
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
624
626
|
namedRangeId: z.string().optional().describe('Specific named range ID'),
|
|
625
627
|
namedRangeName: z.string().optional().describe('Name to match (replaces ALL ranges with this name)'),
|
|
626
628
|
text: z.string().describe('Replacement text'),
|
|
@@ -628,7 +630,7 @@ export function registerDocsTools(server) {
|
|
|
628
630
|
}, async ({ account, documentId, namedRangeId, namedRangeName, text }) => {
|
|
629
631
|
try {
|
|
630
632
|
if (!namedRangeId && !namedRangeName) {
|
|
631
|
-
return
|
|
633
|
+
return invalidParams(account, 'Neither namedRangeId nor namedRangeName was supplied, so no named range is addressed.', 'Pass namedRangeId to replace one specific range, or namedRangeName to replace every range carrying that name.');
|
|
632
634
|
}
|
|
633
635
|
const auth = await getClient(account);
|
|
634
636
|
const docs = docsClient({ version: 'v1', auth });
|
|
@@ -654,7 +656,7 @@ export function registerDocsTools(server) {
|
|
|
654
656
|
description: 'Update paragraph styling (alignment, heading, indents, spacing) across a range. Only supplied fields are applied.',
|
|
655
657
|
inputSchema: {
|
|
656
658
|
account: accountEnum.describe('Google account alias'),
|
|
657
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
659
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
658
660
|
startIndex: z.number().min(1),
|
|
659
661
|
endIndex: z.number().min(2),
|
|
660
662
|
namedStyleType: z.enum([
|
|
@@ -679,7 +681,7 @@ export function registerDocsTools(server) {
|
|
|
679
681
|
const docs = docsClient({ version: 'v1', auth });
|
|
680
682
|
const built = buildParagraphStyle(style);
|
|
681
683
|
if (built.fields.length === 0) {
|
|
682
|
-
return
|
|
684
|
+
return invalidParams(account, 'No paragraph style properties to apply: every optional property was omitted.', 'Pass at least one of: namedStyleType, alignment, lineSpacing, spaceAbove, spaceBelow, indentStart, indentEnd, indentFirstLine, direction, keepLinesTogether, keepWithNext, avoidWidowAndOrphan.');
|
|
683
685
|
}
|
|
684
686
|
await docs.documents.batchUpdate({
|
|
685
687
|
documentId,
|
|
@@ -705,7 +707,7 @@ export function registerDocsTools(server) {
|
|
|
705
707
|
description: 'Update document-level styling (page size, margins, headers/footers behavior). Only supplied fields are applied.',
|
|
706
708
|
inputSchema: {
|
|
707
709
|
account: accountEnum.describe('Google account alias'),
|
|
708
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
710
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
709
711
|
pageWidth: z.number().optional().describe('Page width in points (e.g. 595 = A4)'),
|
|
710
712
|
pageHeight: z.number().optional().describe('Page height in points (e.g. 842 = A4)'),
|
|
711
713
|
marginTop: z.number().optional().describe('Margin in points'),
|
|
@@ -725,7 +727,7 @@ export function registerDocsTools(server) {
|
|
|
725
727
|
const docs = docsClient({ version: 'v1', auth });
|
|
726
728
|
const built = buildDocumentStyle(style);
|
|
727
729
|
if (built.fields.length === 0) {
|
|
728
|
-
return
|
|
730
|
+
return invalidParams(account, 'No document style properties to apply: every optional property was omitted.', 'Pass at least one of: pageWidth, pageHeight, marginTop, marginBottom, marginLeft, marginRight, marginHeader, marginFooter, pageNumberStart, useFirstPageHeaderFooter, useEvenPageHeaderFooter, useCustomHeaderFooterMargins.');
|
|
729
731
|
}
|
|
730
732
|
await docs.documents.batchUpdate({
|
|
731
733
|
documentId,
|
|
@@ -751,7 +753,7 @@ export function registerDocsTools(server) {
|
|
|
751
753
|
description: 'Turn paragraphs in a range into a bulleted or numbered list',
|
|
752
754
|
inputSchema: {
|
|
753
755
|
account: accountEnum.describe('Google account alias'),
|
|
754
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
756
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
755
757
|
startIndex: z.number().min(1),
|
|
756
758
|
endIndex: z.number().min(2),
|
|
757
759
|
bulletPreset: z.enum([
|
|
@@ -799,7 +801,7 @@ export function registerDocsTools(server) {
|
|
|
799
801
|
description: 'Strip bullet/number formatting from paragraphs in a range',
|
|
800
802
|
inputSchema: {
|
|
801
803
|
account: accountEnum.describe('Google account alias'),
|
|
802
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
804
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
803
805
|
startIndex: z.number().min(1),
|
|
804
806
|
endIndex: z.number().min(2),
|
|
805
807
|
},
|
|
@@ -830,7 +832,7 @@ export function registerDocsTools(server) {
|
|
|
830
832
|
description: 'Insert a publicly-accessible image (PNG/JPEG/GIF, <50MB, <25MP, URL <2KB) inline at an index or at the document end',
|
|
831
833
|
inputSchema: {
|
|
832
834
|
account: accountEnum.describe('Google account alias'),
|
|
833
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
835
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
834
836
|
uri: z.string().url().describe('Public image URL'),
|
|
835
837
|
index: z.number().min(1).optional().describe('Insert position; omit to append at the end'),
|
|
836
838
|
width: z.number().optional().describe('Width in points (omit for natural size)'),
|
|
@@ -869,7 +871,7 @@ export function registerDocsTools(server) {
|
|
|
869
871
|
description: 'Insert a page break at a position or at the document end',
|
|
870
872
|
inputSchema: {
|
|
871
873
|
account: accountEnum.describe('Google account alias'),
|
|
872
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
874
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
873
875
|
index: z.number().min(1).optional().describe('Insert position; omit to append'),
|
|
874
876
|
},
|
|
875
877
|
}, async ({ account, documentId, index }) => {
|
|
@@ -897,7 +899,7 @@ export function registerDocsTools(server) {
|
|
|
897
899
|
description: 'Insert a section break at a position. CONTINUOUS keeps the same page; NEXT_PAGE starts a new page.',
|
|
898
900
|
inputSchema: {
|
|
899
901
|
account: accountEnum.describe('Google account alias'),
|
|
900
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
902
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
901
903
|
index: z.number().min(1).optional(),
|
|
902
904
|
sectionType: z.enum(['CONTINUOUS', 'NEXT_PAGE']).default('NEXT_PAGE').optional(),
|
|
903
905
|
},
|
|
@@ -927,7 +929,7 @@ export function registerDocsTools(server) {
|
|
|
927
929
|
description: 'Create a header for the document or for a specific section. The new header is empty; insert text into it with docs_insert_text targeting its segmentId.',
|
|
928
930
|
inputSchema: {
|
|
929
931
|
account: accountEnum.describe('Google account alias'),
|
|
930
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
932
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
931
933
|
sectionBreakIndex: z.number().min(1).optional().describe('Anchor to a specific section break; omit to apply to the document'),
|
|
932
934
|
},
|
|
933
935
|
}, async ({ account, documentId, sectionBreakIndex }) => {
|
|
@@ -954,8 +956,8 @@ export function registerDocsTools(server) {
|
|
|
954
956
|
description: 'Delete a header by its headerId',
|
|
955
957
|
inputSchema: {
|
|
956
958
|
account: accountEnum.describe('Google account alias'),
|
|
957
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
958
|
-
headerId: z.string().describe('Header ID (from docs_get or createHeader reply)'),
|
|
959
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
960
|
+
headerId: z.string().min(1).describe('Header ID (from docs_get or createHeader reply)'),
|
|
959
961
|
},
|
|
960
962
|
}, async ({ account, documentId, headerId }) => {
|
|
961
963
|
try {
|
|
@@ -977,7 +979,7 @@ export function registerDocsTools(server) {
|
|
|
977
979
|
description: 'Create a footer for the document or a specific section',
|
|
978
980
|
inputSchema: {
|
|
979
981
|
account: accountEnum.describe('Google account alias'),
|
|
980
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
982
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
981
983
|
sectionBreakIndex: z.number().min(1).optional(),
|
|
982
984
|
},
|
|
983
985
|
}, async ({ account, documentId, sectionBreakIndex }) => {
|
|
@@ -1004,8 +1006,8 @@ export function registerDocsTools(server) {
|
|
|
1004
1006
|
description: 'Delete a footer by its footerId',
|
|
1005
1007
|
inputSchema: {
|
|
1006
1008
|
account: accountEnum.describe('Google account alias'),
|
|
1007
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1008
|
-
footerId: z.string().describe('Footer ID'),
|
|
1009
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1010
|
+
footerId: z.string().min(1).describe('Footer ID'),
|
|
1009
1011
|
},
|
|
1010
1012
|
}, async ({ account, documentId, footerId }) => {
|
|
1011
1013
|
try {
|
|
@@ -1030,7 +1032,7 @@ export function registerDocsTools(server) {
|
|
|
1030
1032
|
description: 'Mutate a table by operation: insertRow, insertColumn, deleteRow, deleteColumn, mergeCells, unmergeCells. Locate the cell with tableStartIndex (the table\'s start index in the doc) plus rowIndex/columnIndex (0-based).',
|
|
1031
1033
|
inputSchema: {
|
|
1032
1034
|
account: accountEnum.describe('Google account alias'),
|
|
1033
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1035
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1034
1036
|
operation: z.enum(['insertRow', 'insertColumn', 'deleteRow', 'deleteColumn', 'mergeCells', 'unmergeCells']),
|
|
1035
1037
|
tableStartIndex: z.number().min(1).describe('Start index of the table in the document'),
|
|
1036
1038
|
rowIndex: z.number().min(0).describe('Target row (0-based)'),
|
|
@@ -1103,7 +1105,7 @@ export function registerDocsTools(server) {
|
|
|
1103
1105
|
description: 'Add a new tab to a document. Can be a top-level tab or nested as a child of an existing tab.',
|
|
1104
1106
|
inputSchema: {
|
|
1105
1107
|
account: accountEnum.describe('Google account alias'),
|
|
1106
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1108
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1107
1109
|
title: z.string().describe('Tab title'),
|
|
1108
1110
|
parentTabId: z.string().optional().describe('Parent tab ID (omit for top-level)'),
|
|
1109
1111
|
index: z.number().min(0).optional().describe('Position among siblings'),
|
|
@@ -1134,8 +1136,8 @@ export function registerDocsTools(server) {
|
|
|
1134
1136
|
description: 'Delete a tab and all of its descendant tabs',
|
|
1135
1137
|
inputSchema: {
|
|
1136
1138
|
account: accountEnum.describe('Google account alias'),
|
|
1137
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1138
|
-
tabId: z.string().describe('Tab ID'),
|
|
1139
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1140
|
+
tabId: z.string().min(1).describe('Tab ID'),
|
|
1139
1141
|
},
|
|
1140
1142
|
}, async ({ account, documentId, tabId }) => {
|
|
1141
1143
|
try {
|
|
@@ -1157,8 +1159,8 @@ export function registerDocsTools(server) {
|
|
|
1157
1159
|
description: 'Rename a tab, change its position, or move it under a different parent',
|
|
1158
1160
|
inputSchema: {
|
|
1159
1161
|
account: accountEnum.describe('Google account alias'),
|
|
1160
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1161
|
-
tabId: z.string().describe('Tab ID'),
|
|
1162
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1163
|
+
tabId: z.string().min(1).describe('Tab ID'),
|
|
1162
1164
|
title: z.string().optional(),
|
|
1163
1165
|
index: z.number().min(0).optional(),
|
|
1164
1166
|
parentTabId: z.string().optional(),
|
|
@@ -1182,7 +1184,7 @@ export function registerDocsTools(server) {
|
|
|
1182
1184
|
fields.push('parentTabId');
|
|
1183
1185
|
}
|
|
1184
1186
|
if (fields.length === 0) {
|
|
1185
|
-
return
|
|
1187
|
+
return invalidParams(account, 'No tab property to update: title, index and parentTabId were all omitted.', 'Pass at least one of: title, index, parentTabId.');
|
|
1186
1188
|
}
|
|
1187
1189
|
await docs.documents.batchUpdate({
|
|
1188
1190
|
documentId,
|
|
@@ -1210,7 +1212,7 @@ export function registerDocsTools(server) {
|
|
|
1210
1212
|
description: 'Generic documents.batchUpdate pass-through. Accepts the full Request union (40 types). See https://developers.google.com/workspace/docs/api/reference/rest/v1/documents/request',
|
|
1211
1213
|
inputSchema: {
|
|
1212
1214
|
account: accountEnum.describe('Google account alias'),
|
|
1213
|
-
documentId: z.string().describe('Google Docs document ID'),
|
|
1215
|
+
documentId: z.string().min(1).describe('Google Docs document ID'),
|
|
1214
1216
|
requests: coerceJson(z.array(z.record(z.string(), z.any()))).describe('Array of Request objects, each with one request-type key'),
|
|
1215
1217
|
writeControl: z.object({
|
|
1216
1218
|
requiredRevisionId: z.string().optional(),
|