mcp-google-multi 6.0.0-beta.2 → 6.0.0-beta.4

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.
Files changed (181) hide show
  1. package/README.md +5 -5
  2. package/dist/accounts.d.ts +20 -4
  3. package/dist/accounts.js +68 -44
  4. package/dist/api-probe.d.ts +5 -0
  5. package/dist/api-probe.js +20 -15
  6. package/dist/arg-normalize.js +19 -38
  7. package/dist/arg-strict.d.ts +7 -0
  8. package/dist/arg-strict.js +38 -28
  9. package/dist/auth.d.ts +5 -4
  10. package/dist/auth.js +16 -23
  11. package/dist/boot-gates.d.ts +26 -0
  12. package/dist/boot-gates.js +36 -0
  13. package/dist/cimd-cache.d.ts +38 -0
  14. package/dist/cimd-cache.js +111 -0
  15. package/dist/client-config.d.ts +2 -2
  16. package/dist/client-config.js +8 -14
  17. package/dist/client.d.ts +32 -2
  18. package/dist/client.js +0 -0
  19. package/dist/compose.d.ts +18 -0
  20. package/dist/compose.js +109 -0
  21. package/dist/config-file.d.ts +13 -0
  22. package/dist/config-file.js +41 -10
  23. package/dist/discover.js +7 -10
  24. package/dist/discovery-client.d.ts +3 -0
  25. package/dist/discovery-client.js +36 -32
  26. package/dist/doctor.d.ts +9 -5
  27. package/dist/doctor.js +119 -29
  28. package/dist/env-load.js +2 -5
  29. package/dist/executor.d.ts +3 -0
  30. package/dist/executor.js +6 -6
  31. package/dist/fanout.d.ts +9 -1
  32. package/dist/fanout.js +47 -22
  33. package/dist/fs-atomic.d.ts +8 -0
  34. package/dist/fs-atomic.js +216 -29
  35. package/dist/hint-provider.d.ts +129 -0
  36. package/dist/hint-provider.js +58 -0
  37. package/dist/http-config.d.ts +2 -2
  38. package/dist/http-config.js +3 -7
  39. package/dist/http-transport.d.ts +67 -9
  40. package/dist/http-transport.js +318 -52
  41. package/dist/identity.d.ts +6 -3
  42. package/dist/identity.js +24 -8
  43. package/dist/index.js +76 -160
  44. package/dist/master-key.d.ts +3 -3
  45. package/dist/master-key.js +16 -19
  46. package/dist/mcp-token.d.ts +126 -19
  47. package/dist/mcp-token.js +557 -76
  48. package/dist/metrics-cli.d.ts +1 -1
  49. package/dist/metrics-cli.js +1 -1
  50. package/dist/metrics-tap.js +2 -5
  51. package/dist/migrate-config.d.ts +1 -2
  52. package/dist/migrate-config.js +5 -7
  53. package/dist/net-tuning.js +2 -4
  54. package/dist/oauth-as.d.ts +104 -4
  55. package/dist/oauth-as.js +368 -114
  56. package/dist/oauth-consent.d.ts +10 -0
  57. package/dist/oauth-consent.js +20 -10
  58. package/dist/outbound-allowlist.js +9 -11
  59. package/dist/reauth-hint.d.ts +1 -1
  60. package/dist/reauth-hint.js +11 -10
  61. package/dist/registry-transfer.d.ts +5 -0
  62. package/dist/registry-transfer.js +63 -11
  63. package/dist/registry.d.ts +13 -4
  64. package/dist/registry.js +100 -72
  65. package/dist/scope-catalog.d.ts +16 -0
  66. package/dist/scope-catalog.js +40 -13
  67. package/dist/scope-observability.d.ts +10 -9
  68. package/dist/scope-observability.js +34 -24
  69. package/dist/services.d.ts +18 -10
  70. package/dist/services.js +61 -22
  71. package/dist/ssrf-guard.d.ts +23 -9
  72. package/dist/ssrf-guard.js +106 -78
  73. package/dist/tenant-purge.d.ts +26 -0
  74. package/dist/tenant-purge.js +65 -0
  75. package/dist/token-store.d.ts +20 -2
  76. package/dist/token-store.js +0 -0
  77. package/dist/tools/_coerce.d.ts +4 -4
  78. package/dist/tools/_errors.d.ts +3 -0
  79. package/dist/tools/_errors.js +72 -60
  80. package/dist/tools/_local-files.d.ts +9 -0
  81. package/dist/tools/_local-files.js +22 -8
  82. package/dist/tools/account-wizard.d.ts +13 -3
  83. package/dist/tools/account-wizard.js +107 -56
  84. package/dist/tools/accounts-tool.d.ts +8 -1
  85. package/dist/tools/accounts-tool.js +35 -19
  86. package/dist/tools/admin.d.ts +10 -1
  87. package/dist/tools/admin.js +14 -11
  88. package/dist/tools/analytics.d.ts +10 -1
  89. package/dist/tools/analytics.js +14 -10
  90. package/dist/tools/calendar.d.ts +2 -1
  91. package/dist/tools/calendar.js +19 -18
  92. package/dist/tools/chat.d.ts +10 -1
  93. package/dist/tools/chat.js +12 -8
  94. package/dist/tools/contacts.d.ts +2 -1
  95. package/dist/tools/contacts.js +22 -19
  96. package/dist/tools/docs.d.ts +2 -1
  97. package/dist/tools/docs.js +35 -36
  98. package/dist/tools/drive.d.ts +2 -1
  99. package/dist/tools/drive.js +80 -74
  100. package/dist/tools/forms.d.ts +10 -1
  101. package/dist/tools/forms.js +18 -12
  102. package/dist/tools/generated/_shared.d.ts +2 -1
  103. package/dist/tools/generated/_shared.js +7 -3
  104. package/dist/tools/generated/admin.d.ts +2 -1
  105. package/dist/tools/generated/admin.js +265 -265
  106. package/dist/tools/generated/analytics.d.ts +2 -1
  107. package/dist/tools/generated/analytics.js +125 -125
  108. package/dist/tools/generated/appsmarket.d.ts +2 -1
  109. package/dist/tools/generated/appsmarket.js +5 -5
  110. package/dist/tools/generated/calendar.d.ts +2 -1
  111. package/dist/tools/generated/calendar.js +55 -55
  112. package/dist/tools/generated/chat.d.ts +2 -1
  113. package/dist/tools/generated/chat.js +95 -95
  114. package/dist/tools/generated/classroom.d.ts +2 -1
  115. package/dist/tools/generated/classroom.js +209 -209
  116. package/dist/tools/generated/cloudidentity.d.ts +2 -1
  117. package/dist/tools/generated/cloudidentity.js +133 -133
  118. package/dist/tools/generated/cloudsearch.d.ts +2 -1
  119. package/dist/tools/generated/cloudsearch.js +99 -99
  120. package/dist/tools/generated/contacts.d.ts +2 -1
  121. package/dist/tools/generated/contacts.js +29 -29
  122. package/dist/tools/generated/drive.d.ts +2 -1
  123. package/dist/tools/generated/drive.js +71 -71
  124. package/dist/tools/generated/driveactivity.d.ts +2 -1
  125. package/dist/tools/generated/driveactivity.js +3 -3
  126. package/dist/tools/generated/drivelabels.d.ts +2 -1
  127. package/dist/tools/generated/drivelabels.js +53 -53
  128. package/dist/tools/generated/forms.d.ts +2 -1
  129. package/dist/tools/generated/forms.js +7 -7
  130. package/dist/tools/generated/gmail.d.ts +2 -1
  131. package/dist/tools/generated/gmail.js +117 -117
  132. package/dist/tools/generated/groupsmigration.d.ts +2 -1
  133. package/dist/tools/generated/groupsmigration.js +3 -3
  134. package/dist/tools/generated/groupssettings.d.ts +2 -1
  135. package/dist/tools/generated/groupssettings.js +7 -7
  136. package/dist/tools/generated/index.d.ts +2 -1
  137. package/dist/tools/generated/keep.d.ts +2 -1
  138. package/dist/tools/generated/keep.js +15 -15
  139. package/dist/tools/generated/licensing.d.ts +2 -1
  140. package/dist/tools/generated/licensing.js +15 -15
  141. package/dist/tools/generated/meet.d.ts +2 -1
  142. package/dist/tools/generated/meet.js +27 -27
  143. package/dist/tools/generated/postmaster.d.ts +2 -1
  144. package/dist/tools/generated/postmaster.js +9 -9
  145. package/dist/tools/generated/reseller.d.ts +2 -1
  146. package/dist/tools/generated/reseller.js +35 -35
  147. package/dist/tools/generated/script.d.ts +2 -1
  148. package/dist/tools/generated/script.js +33 -33
  149. package/dist/tools/generated/searchconsole.d.ts +2 -1
  150. package/dist/tools/generated/searchconsole.js +3 -3
  151. package/dist/tools/generated/sheets.d.ts +2 -1
  152. package/dist/tools/generated/sheets.js +15 -15
  153. package/dist/tools/generated/tasks.d.ts +2 -1
  154. package/dist/tools/generated/tasks.js +5 -5
  155. package/dist/tools/generated/vault.d.ts +2 -1
  156. package/dist/tools/generated/vault.js +67 -67
  157. package/dist/tools/generated/workspaceevents.d.ts +2 -1
  158. package/dist/tools/generated/workspaceevents.js +31 -31
  159. package/dist/tools/gmail-mime.d.ts +1 -1
  160. package/dist/tools/gmail-mime.js +7 -12
  161. package/dist/tools/gmail.d.ts +4 -3
  162. package/dist/tools/gmail.js +67 -69
  163. package/dist/tools/google-api.d.ts +2 -3
  164. package/dist/tools/google-api.js +34 -22
  165. package/dist/tools/meet.d.ts +10 -1
  166. package/dist/tools/meet.js +12 -9
  167. package/dist/tools/searchconsole.d.ts +2 -1
  168. package/dist/tools/searchconsole.js +16 -13
  169. package/dist/tools/sheets.d.ts +2 -1
  170. package/dist/tools/sheets.js +35 -32
  171. package/dist/tools/slides.d.ts +10 -1
  172. package/dist/tools/slides.js +16 -10
  173. package/dist/tools/tasks.d.ts +2 -1
  174. package/dist/tools/tasks.js +18 -15
  175. package/dist/trim.d.ts +5 -0
  176. package/dist/trim.js +19 -0
  177. package/dist/usage-metrics.d.ts +23 -0
  178. package/dist/usage-metrics.js +67 -56
  179. package/dist/write-control.d.ts +4 -8
  180. package/dist/write-control.js +13 -6
  181. package/package.json +47 -21
@@ -1,4 +1,5 @@
1
1
  import type { ToolRegistry } from '../registry.js';
2
+ import { type CuratedToolDeps } from '../client.js';
2
3
  export declare function isTextualMime(mimeType: string): boolean;
3
4
  export declare function resolveConvertTarget(convertTo: string | undefined): string | undefined;
4
5
  export declare const DRIVE_QUERY_HINT: string;
@@ -10,7 +11,7 @@ export declare function resolveShareNotification(opts: {
10
11
  transferOwnership?: boolean;
11
12
  sendNotification?: boolean;
12
13
  }): boolean | undefined;
13
- export declare function registerDriveTools(server: ToolRegistry): void;
14
+ export declare function registerDriveTools(server: ToolRegistry, deps?: CuratedToolDeps): void;
14
15
  export type TransferPlan = {
15
16
  kind: 'binary';
16
17
  } | {
@@ -1,10 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  import { coerceArray, coerceBoolean, coerceNumber } from './_coerce.js';
3
3
  import { drive as driveClient } from '@googleapis/drive';
4
- import { accountAliasSchema, getAccountSet } from '../accounts.js';
4
+ import { accountArgLive } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError, invalidParams, safeMessage, stringifyEnvelope } from './_errors.js';
7
- import { openLocalReadStream, prepareLocalDest } from './_local-files.js';
7
+ import { hostFilesRefused, openLocalReadStream, prepareLocalDest } from './_local-files.js';
8
8
  import { checkOutbound, outboundDeniedEnvelope, resolveOutboundAllowlist } from '../outbound-allowlist.js';
9
9
  import { isAllowed, writeDisabledResult } from '../write-control.js';
10
10
  import { capText, listResult } from '../trim.js';
@@ -14,10 +14,8 @@ import * as os from 'os';
14
14
  import * as crypto from 'crypto';
15
15
  import { pipeline } from 'node:stream/promises';
16
16
  import mime from 'mime-types';
17
- const accountEnum = accountAliasSchema.optional();
18
17
  // drive_transfer's two-account form is the sanctioned schema exception and
19
18
  // stays REQUIRED: omission must fail at the schema, not as a runtime riddle.
20
- const requiredAccountEnum = accountAliasSchema;
21
19
  const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
22
20
  const FALLBACK_MAX_BYTES = 1024 * 1024 * 1024; // 1GB
23
21
  const GOOGLE_WORKSPACE_TYPES = new Set([
@@ -26,9 +24,8 @@ const GOOGLE_WORKSPACE_TYPES = new Set([
26
24
  'application/vnd.google-apps.presentation',
27
25
  'application/vnd.google-apps.drawing',
28
26
  ]);
29
- // drive_read inlines only textual content. Beyond text/*, RFC 6839 structured-
30
- // syntax suffixes (+json/+xml/...) and a few bare application/* types are text
31
- // in practice — image/svg+xml was the motivating false "binary" refusal.
27
+ // drive_read inlines only textual content; RFC 6839 +json/+xml/... suffixes and
28
+ // a few bare application/* types count too (image/svg+xml was the motivating case).
32
29
  const TEXTUAL_EXACT = new Set([
33
30
  'application/json',
34
31
  'application/xml',
@@ -83,17 +80,14 @@ export const DRIVE_QUERY_HINT = "Drive search syntax: a plain keyword is treated
83
80
  "structured query needs an operator, e.g. \"name contains 'report'\", " +
84
81
  "\"mimeType = 'application/pdf'\", or \"'me' in owners\". " +
85
82
  'Reference: https://developers.google.com/drive/api/guides/search-files';
86
- // A bare keyword is not valid Drive `q` syntax (Google 400s "Invalid Value"), yet
87
- // it is the natural thing to pass to a "search" tool, so wrap it as a full-text
88
- // match. A query already carrying a comparison operator or a quoted literal is a
89
- // structured query and is passed through untouched.
83
+ // A bare keyword isn't valid Drive `q` syntax (400s "Invalid Value"), so wrap it
84
+ // as a full-text match; a query with a comparison operator is passed through untouched.
90
85
  export function normalizeDriveQuery(raw) {
91
86
  const q = raw.trim();
92
87
  if (q === '')
93
88
  return q;
94
- // Structured iff it carries a comparison operator or the membership form
95
- // "<value> in (parents|owners|writers|readers)". A bare apostrophe does NOT
96
- // count (names like O'Brien are keywords, not query syntax).
89
+ // Structured iff it has a comparison operator or the membership form "<value> in
90
+ // (parents|owners|writers|readers)"; a bare apostrophe (e.g. O'Brien) doesn't count.
97
91
  const hasComparison = /(\bcontains\b|!=|<=|>=|[=<>])/i.test(q);
98
92
  const hasMembership = /\bin\s+(parents|owners|writers|readers)\b/i.test(q);
99
93
  if (hasComparison || hasMembership)
@@ -101,9 +95,8 @@ export function normalizeDriveQuery(raw) {
101
95
  const escaped = q.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
102
96
  return `fullText contains '${escaped}'`;
103
97
  }
104
- // Drive rejects a malformed query with HTTP 400 / reason "invalid" and the opaque
105
- // message "Invalid Value"; detect that so drive_search returns query-syntax help
106
- // instead of a generic upstream_error.
98
+ // Drive 400s a malformed query with the opaque reason "invalid"/"Invalid Value";
99
+ // detect it so drive_search returns query-syntax help instead of a generic upstream_error.
107
100
  export function isDriveInvalidQuery(error) {
108
101
  const raw = error?.code ?? error?.status ?? error?.response?.status;
109
102
  const status = typeof raw === 'string' ? Number(raw) : raw;
@@ -123,7 +116,14 @@ export function resolveShareNotification(opts) {
123
116
  return true;
124
117
  return opts.sendNotification ?? true;
125
118
  }
126
- export function registerDriveTools(server) {
119
+ export function registerDriveTools(server, deps = {}) {
120
+ // Per-registry, live account enum + injectable client: the schema follows
121
+ // the registry's account view at parse time, not a process-global default.
122
+ const accountEnum = accountArgLive(() => server.accountAliases()).optional();
123
+ const requiredAccountEnum = accountArgLive(() => server.accountAliases());
124
+ const getClientFn = deps.getClientFn ?? getClient;
125
+ const localFiles = deps.localFiles ?? true;
126
+ const registerHostFileTool = localFiles ? server.registerTool : (() => undefined);
127
127
  // ─── Read / search / list ──────────────────────────────────────────────
128
128
  server.registerTool('drive_search', {
129
129
  description: 'Search files in a Google Drive account. A plain keyword is treated as a full-text search; pass Drive query syntax for a structured search.',
@@ -138,19 +138,18 @@ export function registerDriveTools(server) {
138
138
  },
139
139
  }, async ({ account, query, maxResults, driveId, pageToken }) => {
140
140
  try {
141
- // normalizeDriveQuery trims to '' and Drive reads that as "everything",
142
- // so a blank query returned a raw directory listing as a search result.
141
+ // normalizeDriveQuery trims to '' and Drive reads that as "everything", so
142
+ // a blank query would list the whole drive as a "search" result.
143
143
  if (query.trim() === '') {
144
144
  return invalidParams(account, '`query` is empty, and Drive reads an empty query as "match everything".', 'Pass a search term, or a structured Drive query such as "mimeType = \'application/pdf\'". To browse a folder instead, use drive_list.');
145
145
  }
146
- const auth = await getClient(account);
146
+ const auth = await getClientFn(account);
147
147
  const drive = driveClient({ version: 'v3', auth });
148
148
  const params = {
149
149
  q: normalizeDriveQuery(query),
150
150
  pageSize: maxResults ?? 10,
151
- // nextPageToken and incompleteSearch live OUTSIDE files(...): omit
152
- // them from the mask and Drive drops them, so a partial result was
153
- // indistinguishable from a complete one.
151
+ // nextPageToken and incompleteSearch live OUTSIDE files(...): omit them from
152
+ // the mask and Drive drops them, making a partial result indistinguishable from a complete one.
154
153
  fields: 'nextPageToken,incompleteSearch,files(id,name,mimeType,modifiedTime,webViewLink,size,parents,driveId)',
155
154
  supportsAllDrives: true,
156
155
  includeItemsFromAllDrives: true,
@@ -176,9 +175,8 @@ export function registerDriveTools(server) {
176
175
  }
177
176
  catch (error) {
178
177
  if (isDriveInvalidQuery(error)) {
179
- // Through safeMessage and stringifyEnvelope like every other
180
- // envelope: this path used to read the upstream body directly, so a
181
- // non-JSON response landed here uncapped.
178
+ // Goes through safeMessage/stringifyEnvelope like every other envelope
179
+ // so a non-JSON upstream response can't land here uncapped.
182
180
  return {
183
181
  content: [{ type: 'text', text: stringifyEnvelope({
184
182
  error: 'invalid_query',
@@ -206,7 +204,7 @@ export function registerDriveTools(server) {
206
204
  },
207
205
  }, async ({ account, fileId, maxChars, offset }) => {
208
206
  try {
209
- const auth = await getClient(account);
207
+ const auth = await getClientFn(account);
210
208
  const drive = driveClient({ version: 'v3', auth });
211
209
  const meta = await drive.files.get({
212
210
  fileId,
@@ -316,7 +314,7 @@ export function registerDriveTools(server) {
316
314
  },
317
315
  }, async ({ account, folderId, maxResults, pageToken }) => {
318
316
  try {
319
- const auth = await getClient(account);
317
+ const auth = await getClientFn(account);
320
318
  const drive = driveClient({ version: 'v3', auth });
321
319
  // Escape single quotes per Drive query syntax to prevent breaking out of the literal.
322
320
  const parent = (folderId ?? 'root').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
@@ -339,7 +337,7 @@ export function registerDriveTools(server) {
339
337
  }
340
338
  });
341
339
  // ─── Write / upload / download ─────────────────────────────────────────
342
- server.registerTool('drive_upload', {
340
+ registerHostFileTool('drive_upload', {
343
341
  description: 'Upload a local file to Google Drive. Pass `convertTo` to import it as a native, editable Google Doc/Sheet/Slides/Drawing instead of storing the raw bytes.',
344
342
  inputSchema: {
345
343
  account: accountEnum.describe('Google account alias'),
@@ -351,7 +349,7 @@ export function registerDriveTools(server) {
351
349
  },
352
350
  }, async ({ account, localPath, filename, mimeType: mimeTypeArg, convertTo, parentFolderId }) => {
353
351
  try {
354
- const auth = await getClient(account);
352
+ const auth = await getClientFn(account);
355
353
  const drive = driveClient({ version: 'v3', auth });
356
354
  const resolvedMime = mimeTypeArg ?? (mime.lookup(localPath) || 'application/octet-stream');
357
355
  const fileStream = await openLocalReadStream(localPath);
@@ -377,7 +375,7 @@ export function registerDriveTools(server) {
377
375
  return handleDriveError(error, account);
378
376
  }
379
377
  });
380
- server.registerTool('drive_download', {
378
+ registerHostFileTool('drive_download', {
381
379
  description: 'Download a binary file from Drive to local disk. For Google Workspace formats (Docs, Sheets, Slides), use drive_export instead.',
382
380
  inputSchema: {
383
381
  account: accountEnum.describe('Google account alias'),
@@ -387,7 +385,7 @@ export function registerDriveTools(server) {
387
385
  },
388
386
  }, async ({ account, fileId, savePath, filename }) => {
389
387
  try {
390
- const auth = await getClient(account);
388
+ const auth = await getClientFn(account);
391
389
  const drive = driveClient({ version: 'v3', auth });
392
390
  const name = filename
393
391
  ?? (await drive.files.get({ fileId, fields: 'name', supportsAllDrives: true })).data.name
@@ -405,7 +403,7 @@ export function registerDriveTools(server) {
405
403
  return handleDriveError(error, account);
406
404
  }
407
405
  });
408
- server.registerTool('drive_export', {
406
+ registerHostFileTool('drive_export', {
409
407
  description: 'Export a Google Workspace document (Doc, Sheet, Slide) to a standard format and save to disk. Supported: PDF, DOCX, XLSX, PPTX, TXT, CSV, Markdown (text/markdown for Docs).',
410
408
  inputSchema: {
411
409
  account: accountEnum.describe('Google account alias'),
@@ -416,7 +414,7 @@ export function registerDriveTools(server) {
416
414
  },
417
415
  }, async ({ account, fileId, mimeType: exportMime, savePath, filename }) => {
418
416
  try {
419
- const auth = await getClient(account);
417
+ const auth = await getClientFn(account);
420
418
  const drive = driveClient({ version: 'v3', auth });
421
419
  let name = filename;
422
420
  if (!name) {
@@ -446,7 +444,7 @@ export function registerDriveTools(server) {
446
444
  },
447
445
  }, async ({ account, name, parentFolderId }) => {
448
446
  try {
449
- const auth = await getClient(account);
447
+ const auth = await getClientFn(account);
450
448
  const drive = driveClient({ version: 'v3', auth });
451
449
  const res = await drive.files.create({
452
450
  requestBody: {
@@ -472,13 +470,22 @@ export function registerDriveTools(server) {
472
470
  fileId: z.string().min(1).describe('Google Drive file ID'),
473
471
  newName: z.string().optional().describe('New filename'),
474
472
  newParentFolderId: z.string().optional().describe('Move to this folder, named newParentFolderId here, not parentFolderId'),
475
- localPath: z.string().optional().describe('Replace file content with this local file (path on the machine running the server)'),
476
- mimeType: z.string().optional().describe('MIME type of the replacement file (required if localPath is provided)'),
477
- convertTo: z.enum(CONVERT_TO_VALUES).optional().describe('When replacing content via localPath, convert the new content into this native Google Workspace type on import: "document" | "spreadsheet" | "presentation" | "drawing" (full application/vnd.google-apps.* ids also accepted).'),
473
+ ...(localFiles
474
+ ? {
475
+ localPath: z.string().optional().describe('Replace file content with this local file (path on the machine running the server)'),
476
+ mimeType: z.string().optional().describe('MIME type of the replacement file (required if localPath is provided)'),
477
+ convertTo: z.enum(CONVERT_TO_VALUES).optional().describe('When replacing content via localPath, convert the new content into this native Google Workspace type on import: "document" | "spreadsheet" | "presentation" | "drawing" (full application/vnd.google-apps.* ids also accepted).'),
478
+ }
479
+ : {}),
478
480
  },
479
- }, async ({ account, fileId, newName, newParentFolderId, localPath: localPathArg, mimeType: mimeTypeArg, convertTo }) => {
481
+ }, async (args) => {
482
+ const { account, fileId, newName, newParentFolderId } = args;
483
+ // Declared only when the context may read host files (see the shape).
484
+ const { localPath: localPathArg, mimeType: mimeTypeArg, convertTo } = args;
485
+ if (localPathArg && !localFiles)
486
+ return hostFilesRefused(account, 'localPath');
480
487
  try {
481
- const auth = await getClient(account);
488
+ const auth = await getClientFn(account);
482
489
  const drive = driveClient({ version: 'v3', auth });
483
490
  const requestBody = {};
484
491
  if (newName)
@@ -520,7 +527,7 @@ export function registerDriveTools(server) {
520
527
  },
521
528
  }, async ({ account, fileId }) => {
522
529
  try {
523
- const auth = await getClient(account);
530
+ const auth = await getClientFn(account);
524
531
  const drive = driveClient({ version: 'v3', auth });
525
532
  await drive.files.delete({ fileId, supportsAllDrives: true });
526
533
  return {
@@ -539,7 +546,7 @@ export function registerDriveTools(server) {
539
546
  },
540
547
  }, async ({ account, fileId }) => {
541
548
  try {
542
- const auth = await getClient(account);
549
+ const auth = await getClientFn(account);
543
550
  const drive = driveClient({ version: 'v3', auth });
544
551
  await drive.files.update({
545
552
  fileId,
@@ -562,7 +569,7 @@ export function registerDriveTools(server) {
562
569
  },
563
570
  }, async ({ account, fileId }) => {
564
571
  try {
565
- const auth = await getClient(account);
572
+ const auth = await getClientFn(account);
566
573
  const drive = driveClient({ version: 'v3', auth });
567
574
  const res = await drive.files.update({
568
575
  fileId,
@@ -585,7 +592,7 @@ export function registerDriveTools(server) {
585
592
  },
586
593
  }, async ({ account }) => {
587
594
  try {
588
- const auth = await getClient(account);
595
+ const auth = await getClientFn(account);
589
596
  const drive = driveClient({ version: 'v3', auth });
590
597
  await drive.files.emptyTrash({});
591
598
  return {
@@ -607,7 +614,7 @@ export function registerDriveTools(server) {
607
614
  },
608
615
  }, async ({ account, fileId, newName, parentFolderId }) => {
609
616
  try {
610
- const auth = await getClient(account);
617
+ const auth = await getClientFn(account);
611
618
  const drive = driveClient({ version: 'v3', auth });
612
619
  const res = await drive.files.copy({
613
620
  fileId,
@@ -635,7 +642,7 @@ export function registerDriveTools(server) {
635
642
  },
636
643
  }, async ({ account, fileId, newParentFolderId }) => {
637
644
  try {
638
- const auth = await getClient(account);
645
+ const auth = await getClientFn(account);
639
646
  const drive = driveClient({ version: 'v3', auth });
640
647
  const current = await drive.files.get({ fileId, fields: 'parents', supportsAllDrives: true });
641
648
  const res = await drive.files.update({
@@ -670,9 +677,8 @@ export function registerDriveTools(server) {
670
677
  },
671
678
  }, async ({ account, fileId, type, role, emailAddress, domain, sendNotification, emailMessage, transferOwnership, expirationTime }) => {
672
679
  {
673
- // Outbound allowlist (off unless GOOGLE_OUTBOUND_ALLOWLIST is set):
674
- // user/group grantees must match; a domain share needs its exact
675
- // "@domain" entry; "anyone" (link sharing) is refused while active.
680
+ // Outbound allowlist (off unless GOOGLE_OUTBOUND_ALLOWLIST is set): user/group
681
+ // grantees must match, a domain share needs its exact "@domain" entry, "anyone" is refused while active.
676
682
  const list = resolveOutboundAllowlist();
677
683
  if (list) {
678
684
  if ((type === 'user' || type === 'group') && emailAddress) {
@@ -689,7 +695,7 @@ export function registerDriveTools(server) {
689
695
  }
690
696
  }
691
697
  try {
692
- const auth = await getClient(account);
698
+ const auth = await getClientFn(account);
693
699
  const drive = driveClient({ version: 'v3', auth });
694
700
  const requestBody = { type, role, emailAddress, domain };
695
701
  if (expirationTime)
@@ -720,7 +726,7 @@ export function registerDriveTools(server) {
720
726
  },
721
727
  }, async ({ account, fileId }) => {
722
728
  try {
723
- const auth = await getClient(account);
729
+ const auth = await getClientFn(account);
724
730
  const drive = driveClient({ version: 'v3', auth });
725
731
  const res = await drive.permissions.list({
726
732
  fileId,
@@ -752,7 +758,7 @@ export function registerDriveTools(server) {
752
758
  },
753
759
  }, async ({ account, fileId, permissionId, role, expirationTime, removeExpiration, transferOwnership }) => {
754
760
  try {
755
- const auth = await getClient(account);
761
+ const auth = await getClientFn(account);
756
762
  const drive = driveClient({ version: 'v3', auth });
757
763
  const requestBody = {};
758
764
  if (role)
@@ -785,7 +791,7 @@ export function registerDriveTools(server) {
785
791
  },
786
792
  }, async ({ account, fileId, permissionId }) => {
787
793
  try {
788
- const auth = await getClient(account);
794
+ const auth = await getClientFn(account);
789
795
  const drive = driveClient({ version: 'v3', auth });
790
796
  await drive.permissions.delete({ fileId, permissionId, supportsAllDrives: true });
791
797
  return {
@@ -811,7 +817,7 @@ export function registerDriveTools(server) {
811
817
  },
812
818
  }, async ({ account, fileId, content, anchor, quotedFileContent }) => {
813
819
  try {
814
- const auth = await getClient(account);
820
+ const auth = await getClientFn(account);
815
821
  const drive = driveClient({ version: 'v3', auth });
816
822
  const requestBody = { content };
817
823
  if (anchor)
@@ -843,7 +849,7 @@ export function registerDriveTools(server) {
843
849
  },
844
850
  }, async ({ account, fileId, includeDeleted, pageSize, pageToken, startModifiedTime }) => {
845
851
  try {
846
- const auth = await getClient(account);
852
+ const auth = await getClientFn(account);
847
853
  const drive = driveClient({ version: 'v3', auth });
848
854
  const res = await drive.comments.list({
849
855
  fileId,
@@ -871,7 +877,7 @@ export function registerDriveTools(server) {
871
877
  },
872
878
  }, async ({ account, fileId, commentId, includeDeleted }) => {
873
879
  try {
874
- const auth = await getClient(account);
880
+ const auth = await getClientFn(account);
875
881
  const drive = driveClient({ version: 'v3', auth });
876
882
  const res = await drive.comments.get({
877
883
  fileId,
@@ -897,7 +903,7 @@ export function registerDriveTools(server) {
897
903
  },
898
904
  }, async ({ account, fileId, commentId, content }) => {
899
905
  try {
900
- const auth = await getClient(account);
906
+ const auth = await getClientFn(account);
901
907
  const drive = driveClient({ version: 'v3', auth });
902
908
  const res = await drive.comments.update({
903
909
  fileId,
@@ -922,7 +928,7 @@ export function registerDriveTools(server) {
922
928
  },
923
929
  }, async ({ account, fileId, commentId }) => {
924
930
  try {
925
- const auth = await getClient(account);
931
+ const auth = await getClientFn(account);
926
932
  const drive = driveClient({ version: 'v3', auth });
927
933
  await drive.comments.delete({ fileId, commentId });
928
934
  return {
@@ -946,7 +952,7 @@ export function registerDriveTools(server) {
946
952
  },
947
953
  }, async ({ account, fileId, commentId, content, action }) => {
948
954
  try {
949
- const auth = await getClient(account);
955
+ const auth = await getClientFn(account);
950
956
  const drive = driveClient({ version: 'v3', auth });
951
957
  const requestBody = { content };
952
958
  if (action)
@@ -977,7 +983,7 @@ export function registerDriveTools(server) {
977
983
  },
978
984
  }, async ({ account, fileId, commentId, includeDeleted, pageSize, pageToken }) => {
979
985
  try {
980
- const auth = await getClient(account);
986
+ const auth = await getClientFn(account);
981
987
  const drive = driveClient({ version: 'v3', auth });
982
988
  const res = await drive.replies.list({
983
989
  fileId,
@@ -1006,7 +1012,7 @@ export function registerDriveTools(server) {
1006
1012
  },
1007
1013
  }, async ({ account, fileId, commentId, replyId, content }) => {
1008
1014
  try {
1009
- const auth = await getClient(account);
1015
+ const auth = await getClientFn(account);
1010
1016
  const drive = driveClient({ version: 'v3', auth });
1011
1017
  const res = await drive.replies.update({
1012
1018
  fileId,
@@ -1033,7 +1039,7 @@ export function registerDriveTools(server) {
1033
1039
  },
1034
1040
  }, async ({ account, fileId, commentId, replyId }) => {
1035
1041
  try {
1036
- const auth = await getClient(account);
1042
+ const auth = await getClientFn(account);
1037
1043
  const drive = driveClient({ version: 'v3', auth });
1038
1044
  await drive.replies.delete({ fileId, commentId, replyId });
1039
1045
  return {
@@ -1055,7 +1061,7 @@ export function registerDriveTools(server) {
1055
1061
  },
1056
1062
  }, async ({ account, fileId, pageSize, pageToken }) => {
1057
1063
  try {
1058
- const auth = await getClient(account);
1064
+ const auth = await getClientFn(account);
1059
1065
  const drive = driveClient({ version: 'v3', auth });
1060
1066
  const res = await drive.revisions.list({
1061
1067
  fileId,
@@ -1084,7 +1090,7 @@ export function registerDriveTools(server) {
1084
1090
  },
1085
1091
  }, async ({ account, fileId, revisionId, keepForever, published, publishAuto, publishedOutsideDomain }) => {
1086
1092
  try {
1087
- const auth = await getClient(account);
1093
+ const auth = await getClientFn(account);
1088
1094
  const drive = driveClient({ version: 'v3', auth });
1089
1095
  const requestBody = {};
1090
1096
  if (keepForever !== undefined)
@@ -1118,7 +1124,7 @@ export function registerDriveTools(server) {
1118
1124
  },
1119
1125
  }, async ({ account, fileId, revisionId }) => {
1120
1126
  try {
1121
- const auth = await getClient(account);
1127
+ const auth = await getClientFn(account);
1122
1128
  const drive = driveClient({ version: 'v3', auth });
1123
1129
  await drive.revisions.delete({ fileId, revisionId });
1124
1130
  return {
@@ -1140,7 +1146,7 @@ export function registerDriveTools(server) {
1140
1146
  },
1141
1147
  }, async ({ account, fileId, pageSize, pageToken }) => {
1142
1148
  try {
1143
- const auth = await getClient(account);
1149
+ const auth = await getClientFn(account);
1144
1150
  const drive = driveClient({ version: 'v3', auth });
1145
1151
  const res = await drive.accessproposals.list({
1146
1152
  fileId,
@@ -1170,7 +1176,7 @@ export function registerDriveTools(server) {
1170
1176
  },
1171
1177
  }, async ({ account, fileId, proposalId, action, role, view, sendNotification }) => {
1172
1178
  try {
1173
- const auth = await getClient(account);
1179
+ const auth = await getClientFn(account);
1174
1180
  const drive = driveClient({ version: 'v3', auth });
1175
1181
  const requestBody = { action };
1176
1182
  if (role)
@@ -1203,7 +1209,7 @@ export function registerDriveTools(server) {
1203
1209
  },
1204
1210
  }, async ({ account, pageSize, pageToken, q }) => {
1205
1211
  try {
1206
- const auth = await getClient(account);
1212
+ const auth = await getClientFn(account);
1207
1213
  const drive = driveClient({ version: 'v3', auth });
1208
1214
  const res = await drive.drives.list({
1209
1215
  pageSize: pageSize ?? 50,
@@ -1227,7 +1233,7 @@ export function registerDriveTools(server) {
1227
1233
  },
1228
1234
  }, async ({ account, driveId }) => {
1229
1235
  try {
1230
- const auth = await getClient(account);
1236
+ const auth = await getClientFn(account);
1231
1237
  const drive = driveClient({ version: 'v3', auth });
1232
1238
  const res = await drive.drives.get({
1233
1239
  driveId,
@@ -1278,10 +1284,10 @@ export function registerDriveTools(server) {
1278
1284
  }
1279
1285
  let activeAccount = fromAccount;
1280
1286
  try {
1281
- const sourceAuth = await getClient(fromAccount);
1287
+ const sourceAuth = await getClientFn(fromAccount);
1282
1288
  const sourceDrive = driveClient({ version: 'v3', auth: sourceAuth });
1283
1289
  activeAccount = toAccount;
1284
- const targetAuth = await getClient(toAccount);
1290
+ const targetAuth = await getClientFn(toAccount);
1285
1291
  const targetDrive = driveClient({ version: 'v3', auth: targetAuth });
1286
1292
  activeAccount = fromAccount;
1287
1293
  const meta = await sourceDrive.files.get({
@@ -1305,7 +1311,7 @@ export function registerDriveTools(server) {
1305
1311
  };
1306
1312
  }
1307
1313
  const intendedName = newName ?? meta.data.name ?? 'transferred-file';
1308
- const targetEmail = getAccountSet().configs[toAccount].email;
1314
+ const targetEmail = server.accountSet().configs[toAccount].email;
1309
1315
  const finish = async (data, strategy, flags) => {
1310
1316
  let moveFailed = false;
1311
1317
  let moveSkipped = false;
@@ -1379,7 +1385,7 @@ export function registerDriveTools(server) {
1379
1385
  },
1380
1386
  }, async ({ account }) => {
1381
1387
  try {
1382
- const auth = await getClient(account);
1388
+ const auth = await getClientFn(account);
1383
1389
  const drive = driveClient({ version: 'v3', auth });
1384
1390
  const res = await drive.about.get({
1385
1391
  fields: 'user,storageQuota',
@@ -1,2 +1,11 @@
1
1
  import type { ToolRegistry } from '../registry.js';
2
- export declare function registerFormsTools(server: ToolRegistry): void;
2
+ import type { Account } from '../accounts.js';
3
+ import { type CuratedToolDeps } from '../client.js';
4
+ export declare function registerFormsTools(server: ToolRegistry, deps?: CuratedToolDeps): void;
5
+ export declare function handleFormsError(error: any, account: Account): {
6
+ content: {
7
+ type: "text";
8
+ text: string;
9
+ }[];
10
+ isError: true;
11
+ };
@@ -1,11 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { forms as formsClient } from '@googleapis/forms';
3
- import { accountAliasSchema } from '../accounts.js';
3
+ import { accountArgLive } from '../accounts.js';
4
4
  import { getClient } from '../client.js';
5
5
  import { handleGoogleApiError } from './_errors.js';
6
6
  import { coerceArray, coerceBoolean, coerceJson } from './_coerce.js';
7
- const accountEnum = accountAliasSchema.optional();
8
- export function registerFormsTools(server) {
7
+ export function registerFormsTools(server, deps = {}) {
8
+ // Per-registry, live account enum + injectable client: the schema follows
9
+ // the registry's account view at parse time, not a process-global default.
10
+ const accountEnum = accountArgLive(() => server.accountAliases()).optional();
11
+ const getClientFn = deps.getClientFn ?? getClient;
9
12
  server.registerTool('forms_get', {
10
13
  description: 'Get a Google Form (definition: questions, sections, settings). Requires forms.body scope.',
11
14
  inputSchema: {
@@ -14,7 +17,7 @@ export function registerFormsTools(server) {
14
17
  },
15
18
  }, async ({ account, formId }) => {
16
19
  try {
17
- const auth = await getClient(account);
20
+ const auth = await getClientFn(account);
18
21
  const forms = formsClient({ version: 'v1', auth });
19
22
  const res = await forms.forms.get({ formId });
20
23
  return {
@@ -36,7 +39,7 @@ export function registerFormsTools(server) {
36
39
  },
37
40
  }, async ({ account, formId, pageSize, pageToken, filter }) => {
38
41
  try {
39
- const auth = await getClient(account);
42
+ const auth = await getClientFn(account);
40
43
  const forms = formsClient({ version: 'v1', auth });
41
44
  const res = await forms.forms.responses.list({
42
45
  formId,
@@ -61,7 +64,7 @@ export function registerFormsTools(server) {
61
64
  },
62
65
  }, async ({ account, formId, responseId }) => {
63
66
  try {
64
- const auth = await getClient(account);
67
+ const auth = await getClientFn(account);
65
68
  const forms = formsClient({ version: 'v1', auth });
66
69
  const res = await forms.forms.responses.get({ formId, responseId });
67
70
  return {
@@ -80,7 +83,7 @@ export function registerFormsTools(server) {
80
83
  },
81
84
  }, async ({ account, formId }) => {
82
85
  try {
83
- const auth = await getClient(account);
86
+ const auth = await getClientFn(account);
84
87
  const forms = formsClient({ version: 'v1', auth });
85
88
  const res = await forms.forms.watches.list({ formId });
86
89
  return {
@@ -100,7 +103,7 @@ export function registerFormsTools(server) {
100
103
  },
101
104
  }, async ({ account, title, documentTitle }) => {
102
105
  try {
103
- const auth = await getClient(account);
106
+ const auth = await getClientFn(account);
104
107
  const forms = formsClient({ version: 'v1', auth });
105
108
  const res = await forms.forms.create({
106
109
  requestBody: { info: { title, documentTitle } },
@@ -135,7 +138,7 @@ export function registerFormsTools(server) {
135
138
  },
136
139
  }, async ({ account, formId, requests, includeFormInResponse, writeControl }) => {
137
140
  try {
138
- const auth = await getClient(account);
141
+ const auth = await getClientFn(account);
139
142
  const forms = formsClient({ version: 'v1', auth });
140
143
  const res = await forms.forms.batchUpdate({
141
144
  formId,
@@ -163,7 +166,7 @@ export function registerFormsTools(server) {
163
166
  },
164
167
  }, async ({ account, formId, isPublished, isAcceptingResponses }) => {
165
168
  try {
166
- const auth = await getClient(account);
169
+ const auth = await getClientFn(account);
167
170
  const forms = formsClient({ version: 'v1', auth });
168
171
  const updateMask = ['publishState.isPublished'];
169
172
  if (isAcceptingResponses !== undefined)
@@ -186,6 +189,9 @@ export function registerFormsTools(server) {
186
189
  }
187
190
  });
188
191
  }
189
- function handleFormsError(error, account) {
190
- return handleGoogleApiError(error, account, "Forms tools require the optional \"forms\" scope bundle. Add GOOGLE_OPTIONAL_SCOPES=forms (or include \"forms\" in the list) and re-auth.");
192
+ export function handleFormsError(error, account) {
193
+ return handleGoogleApiError(error, account, {
194
+ scope: "Forms tools require the optional \"forms\" scope bundle. Add GOOGLE_OPTIONAL_SCOPES=forms (or include \"forms\" in the list) and re-auth.",
195
+ bundle: 'forms',
196
+ });
191
197
  }
@@ -21,5 +21,6 @@ export interface GeneratedToolDef {
21
21
  }>;
22
22
  shape: z.ZodRawShape;
23
23
  }
24
- export declare function accountField(): z.ZodOptional<z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>>;
24
+ export declare function accountField(aliases?: readonly string[] | (() => readonly string[])): z.ZodOptional<z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>>;
25
+ export type { ExecuteDeps } from '../../executor.js';
25
26
  export declare function registerGeneratedTool(registry: ToolRegistry, def: GeneratedToolDef, deps?: ExecuteDeps): void;
@@ -1,7 +1,11 @@
1
- import { accountAliasSchema } from '../../accounts.js';
1
+ import { accountAliasSchema, accountAliasSchemaFor, accountArgLive } from '../../accounts.js';
2
2
  import { executeApiMethod } from '../../executor.js';
3
- export function accountField() {
4
- return accountAliasSchema.optional().describe('Google account alias (omit for the default account)');
3
+ export function accountField(aliases) {
4
+ // Generated code passes a getter over registry.accountAliases(), so the field
5
+ // validates against the registry's CURRENT set, as curated tools do. An
6
+ // array bakes that set; the no-arg form keeps the global-bound schema.
7
+ const schema = typeof aliases === 'function' ? accountArgLive(aliases) : aliases ? accountAliasSchemaFor(aliases) : accountAliasSchema;
8
+ return schema.optional().describe('Google account alias (omit for the default account)');
5
9
  }
6
10
  export function registerGeneratedTool(registry, def, deps = {}) {
7
11
  // Widened locally: `cud` is a registry extension the SDK config type doesn't
@@ -1,2 +1,3 @@
1
1
  import type { ToolRegistry } from '../../registry.js';
2
- export declare function registerAdminGeneratedTools(registry: ToolRegistry): void;
2
+ import { type ExecuteDeps } from './_shared.js';
3
+ export declare function registerAdminGeneratedTools(registry: ToolRegistry, deps?: ExecuteDeps): void;