mcp-google-multi 6.0.0-alpha.2 → 6.0.0-alpha.21

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 (60) hide show
  1. package/README.md +5 -3
  2. package/dist/api-probe.d.ts +18 -0
  3. package/dist/api-probe.js +68 -0
  4. package/dist/arg-normalize.d.ts +19 -0
  5. package/dist/arg-normalize.js +90 -0
  6. package/dist/auth.js +15 -72
  7. package/dist/client.js +3 -1
  8. package/dist/discover.js +35 -14
  9. package/dist/discovery-client.d.ts +8 -1
  10. package/dist/discovery-client.js +67 -15
  11. package/dist/doctor.d.ts +12 -0
  12. package/dist/doctor.js +98 -15
  13. package/dist/http-transport.d.ts +3 -0
  14. package/dist/http-transport.js +2 -1
  15. package/dist/index.js +4 -1
  16. package/dist/oauth-consent.d.ts +25 -10
  17. package/dist/oauth-consent.js +85 -39
  18. package/dist/registry.d.ts +12 -0
  19. package/dist/registry.js +61 -3
  20. package/dist/scope-catalog.d.ts +1 -0
  21. package/dist/scope-catalog.js +17 -1
  22. package/dist/services.js +3 -1
  23. package/dist/tools/_errors.js +69 -5
  24. package/dist/tools/_local-files.d.ts +3 -0
  25. package/dist/tools/_local-files.js +34 -0
  26. package/dist/tools/account-wizard.d.ts +10 -0
  27. package/dist/tools/account-wizard.js +52 -19
  28. package/dist/tools/analytics.d.ts +18 -0
  29. package/dist/tools/analytics.js +279 -0
  30. package/dist/tools/drive.d.ts +2 -1
  31. package/dist/tools/drive.js +75 -33
  32. package/dist/tools/generated/_shared.d.ts +6 -0
  33. package/dist/tools/generated/_shared.js +11 -1
  34. package/dist/tools/generated/admin.js +160 -29
  35. package/dist/tools/generated/analytics.d.ts +2 -0
  36. package/dist/tools/generated/analytics.js +981 -0
  37. package/dist/tools/generated/chat.js +39 -12
  38. package/dist/tools/generated/classroom.js +56 -14
  39. package/dist/tools/generated/cloudidentity.js +18 -10
  40. package/dist/tools/generated/cloudsearch.js +4 -3
  41. package/dist/tools/generated/contacts.js +13 -4
  42. package/dist/tools/generated/drive.js +15 -6
  43. package/dist/tools/generated/drivelabels.js +33 -5
  44. package/dist/tools/generated/forms.js +1 -1
  45. package/dist/tools/generated/gmail.js +39 -12
  46. package/dist/tools/generated/index.js +2 -0
  47. package/dist/tools/generated/keep.js +3 -2
  48. package/dist/tools/generated/licensing.js +20 -4
  49. package/dist/tools/generated/meet.js +1 -1
  50. package/dist/tools/generated/reseller.js +8 -2
  51. package/dist/tools/generated/script.js +13 -3
  52. package/dist/tools/generated/searchconsole.js +4 -2
  53. package/dist/tools/generated/sheets.js +2 -1
  54. package/dist/tools/generated/tasks.js +7 -1
  55. package/dist/tools/generated/vault.js +18 -9
  56. package/dist/tools/generated/workspaceevents.js +3 -2
  57. package/dist/tools/gmail.js +3 -3
  58. package/dist/tools/google-api.d.ts +4 -1
  59. package/dist/tools/google-api.js +59 -15
  60. package/package.json +22 -17
@@ -1,6 +1,5 @@
1
1
  // GENERATED by scripts/gen-tools.ts — do not edit. Regenerate: npm run gen:tools
2
2
  import { z } from 'zod';
3
- import { coerceJson } from '../_coerce.js';
4
3
  import { accountField, registerGeneratedTool } from './_shared.js';
5
4
  export function registerLicensingGeneratedTools(registry) {
6
5
  // Interned method scope sets (shared across tools; see scope-observability).
@@ -44,11 +43,12 @@ export function registerLicensingGeneratedTools(registry) {
44
43
  method: { id: "licensing.licenseAssignments.insert", httpMethod: "POST", path: "apps/licensing/v1/product/{productId}/sku/{skuId}/user", baseUrl: "https://licensing.googleapis.com/", requiredParams: ["productId", "skuId"], scopes: S_licensing_v1[0] },
45
44
  params: [{ "field": "productId", "api": "productId", "location": "path" }, { "field": "skuId", "api": "skuId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
46
45
  hasBody: true,
46
+ bodyParams: [{ "field": "userId", "api": "userId" }],
47
47
  shape: {
48
48
  account: accountField(),
49
49
  productId: z.string().describe("A product's unique identifier. For more information about products in this version of the API, see Products and SKUs."),
50
50
  skuId: z.string().describe("A product SKU's unique identifier. For more information about available SKUs in this version of the API, see Products and SKUs."),
51
- body: coerceJson(z.record(z.string(), z.unknown())).describe("LicenseAssignmentInsert JSON request body. Top-level fields: userId."),
51
+ userId: z.string().describe("Email id of the user"),
52
52
  fields: z.string().optional().describe('Response field mask.'),
53
53
  },
54
54
  });
@@ -92,12 +92,20 @@ export function registerLicensingGeneratedTools(registry) {
92
92
  method: { id: "licensing.licenseAssignments.patch", httpMethod: "PATCH", path: "apps/licensing/v1/product/{productId}/sku/{skuId}/user/{userId}", baseUrl: "https://licensing.googleapis.com/", requiredParams: ["productId", "skuId", "userId"], scopes: S_licensing_v1[0] },
93
93
  params: [{ "field": "productId", "api": "productId", "location": "path" }, { "field": "skuId", "api": "skuId", "location": "path" }, { "field": "userId", "api": "userId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
94
94
  hasBody: true,
95
+ bodyParams: [{ "field": "etags", "api": "etags" }, { "field": "kind", "api": "kind" }, { "field": "productId_", "api": "productId" }, { "field": "productName", "api": "productName" }, { "field": "selfLink", "api": "selfLink" }, { "field": "skuId_", "api": "skuId" }, { "field": "skuName", "api": "skuName" }, { "field": "userId_", "api": "userId" }],
95
96
  shape: {
96
97
  account: accountField(),
97
98
  productId: z.string().describe("A product's unique identifier. For more information about products in this version of the API, see Products and SKUs."),
98
99
  skuId: z.string().describe("A product SKU's unique identifier. For more information about available SKUs in this version of the API, see Products and SKUs."),
99
100
  userId: z.string().describe("The user's current primary email address. If the user's email address changes, use the new email address in your API requests. Since a `userId` is subject to change, do not use a `userId` value as a k"),
100
- body: coerceJson(z.record(z.string(), z.unknown())).describe("LicenseAssignment JSON request body. Top-level fields: etags, kind, productId, productName, selfLink, skuId, skuName, userId."),
101
+ etags: z.string().describe("ETag of the resource.").optional(),
102
+ kind: z.string().describe("Identifies the resource as a LicenseAssignment, which is `licensing#licenseAssignment`.").optional(),
103
+ productId_: z.string().describe("A product's unique identifier. For more information about products in this version of the API, see Product and SKU IDs.").optional(),
104
+ productName: z.string().describe("Display Name of the product.").optional(),
105
+ selfLink: z.string().describe("Link to this page.").optional(),
106
+ skuId_: z.string().describe("A product SKU's unique identifier. For more information about available SKUs in this version of the API, see Products and SKUs.").optional(),
107
+ skuName: z.string().describe("Display Name of the sku of the product.").optional(),
108
+ userId_: z.string().describe("The user's current primary email address. If the user's email address changes, use the new email address in your API requests. Since a `userId` is subject to change, do not use a `userId` value as a k").optional(),
101
109
  fields: z.string().optional().describe('Response field mask.'),
102
110
  },
103
111
  });
@@ -108,12 +116,20 @@ export function registerLicensingGeneratedTools(registry) {
108
116
  method: { id: "licensing.licenseAssignments.update", httpMethod: "PUT", path: "apps/licensing/v1/product/{productId}/sku/{skuId}/user/{userId}", baseUrl: "https://licensing.googleapis.com/", requiredParams: ["productId", "skuId", "userId"], scopes: S_licensing_v1[0] },
109
117
  params: [{ "field": "productId", "api": "productId", "location": "path" }, { "field": "skuId", "api": "skuId", "location": "path" }, { "field": "userId", "api": "userId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
110
118
  hasBody: true,
119
+ bodyParams: [{ "field": "productId_", "api": "productId" }, { "field": "skuId_", "api": "skuId" }, { "field": "userId_", "api": "userId" }, { "field": "etags", "api": "etags" }, { "field": "kind", "api": "kind" }, { "field": "productName", "api": "productName" }, { "field": "selfLink", "api": "selfLink" }, { "field": "skuName", "api": "skuName" }],
111
120
  shape: {
112
121
  account: accountField(),
113
122
  productId: z.string().describe("A product's unique identifier. For more information about products in this version of the API, see Products and SKUs."),
114
123
  skuId: z.string().describe("A product SKU's unique identifier. For more information about available SKUs in this version of the API, see Products and SKUs."),
115
124
  userId: z.string().describe("The user's current primary email address. If the user's email address changes, use the new email address in your API requests. Since a `userId` is subject to change, do not use a `userId` value as a k"),
116
- body: coerceJson(z.record(z.string(), z.unknown())).describe("LicenseAssignment JSON request body. Top-level fields: etags, kind, productId, productName, selfLink, skuId, skuName, userId."),
125
+ productId_: z.string().describe("A product's unique identifier. For more information about products in this version of the API, see Product and SKU IDs."),
126
+ skuId_: z.string().describe("A product SKU's unique identifier. For more information about available SKUs in this version of the API, see Products and SKUs."),
127
+ userId_: z.string().describe("The user's current primary email address. If the user's email address changes, use the new email address in your API requests. Since a `userId` is subject to change, do not use a `userId` value as a k"),
128
+ etags: z.string().describe("ETag of the resource.").optional(),
129
+ kind: z.string().describe("Identifies the resource as a LicenseAssignment, which is `licensing#licenseAssignment`.").optional(),
130
+ productName: z.string().describe("Display Name of the product.").optional(),
131
+ selfLink: z.string().describe("Link to this page.").optional(),
132
+ skuName: z.string().describe("Display Name of the sku of the product.").optional(),
117
133
  fields: z.string().optional().describe('Response field mask.'),
118
134
  },
119
135
  });
@@ -155,10 +155,10 @@ export function registerMeetGeneratedTools(registry) {
155
155
  method: { id: "meet.spaces.endActiveConference", httpMethod: "POST", path: "v2/{+name}:endActiveConference", baseUrl: "https://meet.googleapis.com/", requiredParams: ["name"], scopes: S_meet_v2[1] },
156
156
  params: [{ "field": "name", "api": "name", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
157
157
  hasBody: true,
158
+ bodyParams: [],
158
159
  shape: {
159
160
  account: accountField(),
160
161
  name: z.string().describe("Required. Resource name of the space. Format: `spaces/{space}`. `{space}` is the resource identifier for the space. It's a unique, server-generated ID and is case sensitive. For example, `jQCFfuBOdN5z"),
161
- body: coerceJson(z.record(z.string(), z.unknown())).describe("EndActiveConferenceRequest JSON request body."),
162
162
  fields: z.string().optional().describe('Response field mask.'),
163
163
  },
164
164
  });
@@ -137,11 +137,13 @@ export function registerResellerGeneratedTools(registry) {
137
137
  method: { id: "reseller.subscriptions.changeRenewalSettings", httpMethod: "POST", path: "apps/reseller/v1/customers/{customerId}/subscriptions/{subscriptionId}/changeRenewalSettings", baseUrl: "https://reseller.googleapis.com/", requiredParams: ["customerId", "subscriptionId"], scopes: S_reseller_v1[1] },
138
138
  params: [{ "field": "customerId", "api": "customerId", "location": "path" }, { "field": "subscriptionId", "api": "subscriptionId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
139
139
  hasBody: true,
140
+ bodyParams: [{ "field": "kind", "api": "kind" }, { "field": "renewalType", "api": "renewalType" }],
140
141
  shape: {
141
142
  account: accountField(),
142
143
  customerId: z.string().describe("This can be either the customer's primary domain name or the customer's unique identifier. If the domain name for a customer changes, the old domain name cannot be used to access the customer, but the"),
143
144
  subscriptionId: z.string().describe("This is a required property. The `subscriptionId` is the subscription identifier and is unique for each customer. Since a `subscriptionId` changes when a subscription is updated, we recommend to not u"),
144
- body: coerceJson(z.record(z.string(), z.unknown())).describe("RenewalSettings JSON request body. Top-level fields: kind, renewalType."),
145
+ kind: z.string().describe("Identifies the resource as a subscription renewal setting. Value: `subscriptions#renewalSettings`").optional(),
146
+ renewalType: z.string().describe("Renewal settings for the annual commitment plan. For more detailed information, see renewal options in the administrator help center. When renewing a subscription, the `renewalType` is a required prop").optional(),
145
147
  fields: z.string().optional().describe('Response field mask.'),
146
148
  },
147
149
  });
@@ -152,11 +154,15 @@ export function registerResellerGeneratedTools(registry) {
152
154
  method: { id: "reseller.subscriptions.changeSeats", httpMethod: "POST", path: "apps/reseller/v1/customers/{customerId}/subscriptions/{subscriptionId}/changeSeats", baseUrl: "https://reseller.googleapis.com/", requiredParams: ["customerId", "subscriptionId"], scopes: S_reseller_v1[1] },
153
155
  params: [{ "field": "customerId", "api": "customerId", "location": "path" }, { "field": "subscriptionId", "api": "subscriptionId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
154
156
  hasBody: true,
157
+ bodyParams: [{ "field": "kind", "api": "kind" }, { "field": "licensedNumberOfSeats", "api": "licensedNumberOfSeats" }, { "field": "maximumNumberOfSeats", "api": "maximumNumberOfSeats" }, { "field": "numberOfSeats", "api": "numberOfSeats" }],
155
158
  shape: {
156
159
  account: accountField(),
157
160
  customerId: z.string().describe("This can be either the customer's primary domain name or the customer's unique identifier. If the domain name for a customer changes, the old domain name cannot be used to access the customer, but the"),
158
161
  subscriptionId: z.string().describe("This is a required property. The `subscriptionId` is the subscription identifier and is unique for each customer. Since a `subscriptionId` changes when a subscription is updated, we recommend to not u"),
159
- body: coerceJson(z.record(z.string(), z.unknown())).describe("Seats JSON request body. Top-level fields: kind, licensedNumberOfSeats, maximumNumberOfSeats, numberOfSeats."),
162
+ kind: z.string().describe("Identifies the resource as a subscription seat setting. Value: `subscriptions#seats`").optional(),
163
+ licensedNumberOfSeats: z.number().describe("Read-only field containing the current number of users that are assigned a license for the product defined in `skuId`. This field's value is equivalent to the numerical count of users returned by the ").optional(),
164
+ maximumNumberOfSeats: z.number().describe("This is a required property and is exclusive to subscriptions with `FLEXIBLE` or `TRIAL` plans. This property sets the maximum number of licensed users allowed on a subscription. This quantity can be ").optional(),
165
+ numberOfSeats: z.number().describe("This is a required property and is exclusive to subscriptions with `ANNUAL_MONTHLY_PAY` and `ANNUAL_YEARLY_PAY` plans. This property sets the maximum number of licenses assignable to users on a subscr").optional(),
160
166
  fields: z.string().optional().describe('Response field mask.'),
161
167
  },
162
168
  });
@@ -65,9 +65,11 @@ export function registerScriptGeneratedTools(registry) {
65
65
  method: { id: "script.projects.create", httpMethod: "POST", path: "v1/projects", baseUrl: "https://script.googleapis.com/", requiredParams: [], scopes: S_script_v1[1] },
66
66
  params: [{ "field": "fields", "api": "fields", "location": "query" }],
67
67
  hasBody: true,
68
+ bodyParams: [{ "field": "parentId", "api": "parentId" }, { "field": "title", "api": "title" }],
68
69
  shape: {
69
70
  account: accountField(),
70
- body: coerceJson(z.record(z.string(), z.unknown())).describe("CreateProjectRequest JSON request body. Top-level fields: parentId, title."),
71
+ parentId: z.string().describe("The Drive ID of a parent file that the created script project is bound to. This is usually the ID of a Google Doc, Google Sheet, Google Form, or Google Slides file. If not set, a standalone script pro").optional(),
72
+ title: z.string().describe("The title for the project.").optional(),
71
73
  fields: z.string().optional().describe('Response field mask.'),
72
74
  },
73
75
  });
@@ -78,10 +80,14 @@ export function registerScriptGeneratedTools(registry) {
78
80
  method: { id: "script.projects.deployments.create", httpMethod: "POST", path: "v1/projects/{scriptId}/deployments", baseUrl: "https://script.googleapis.com/", requiredParams: ["scriptId"], scopes: S_script_v1[2] },
79
81
  params: [{ "field": "scriptId", "api": "scriptId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
80
82
  hasBody: true,
83
+ bodyParams: [{ "field": "description", "api": "description" }, { "field": "manifestFileName", "api": "manifestFileName" }, { "field": "scriptId_", "api": "scriptId" }, { "field": "versionNumber", "api": "versionNumber" }],
81
84
  shape: {
82
85
  account: accountField(),
83
86
  scriptId: z.string().describe("The script project's Drive ID."),
84
- body: coerceJson(z.record(z.string(), z.unknown())).describe("DeploymentConfig JSON request body. Top-level fields: description, manifestFileName, scriptId, versionNumber."),
87
+ description: z.string().describe("The description for this deployment.").optional(),
88
+ manifestFileName: z.string().describe("The manifest file name for this deployment.").optional(),
89
+ scriptId_: z.string().describe("The script project's Drive ID.").optional(),
90
+ versionNumber: z.number().describe("The version number on which this deployment is based.").optional(),
85
91
  fields: z.string().optional().describe('Response field mask.'),
86
92
  },
87
93
  });
@@ -206,10 +212,14 @@ export function registerScriptGeneratedTools(registry) {
206
212
  method: { id: "script.projects.versions.create", httpMethod: "POST", path: "v1/projects/{scriptId}/versions", baseUrl: "https://script.googleapis.com/", requiredParams: ["scriptId"], scopes: S_script_v1[1] },
207
213
  params: [{ "field": "scriptId", "api": "scriptId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
208
214
  hasBody: true,
215
+ bodyParams: [{ "field": "createTime", "api": "createTime" }, { "field": "description", "api": "description" }, { "field": "scriptId_", "api": "scriptId" }, { "field": "versionNumber", "api": "versionNumber" }],
209
216
  shape: {
210
217
  account: accountField(),
211
218
  scriptId: z.string().describe("The script project's Drive ID."),
212
- body: coerceJson(z.record(z.string(), z.unknown())).describe("Version JSON request body. Top-level fields: createTime, description, scriptId, versionNumber."),
219
+ createTime: z.string().describe("When the version was created.").optional(),
220
+ description: z.string().describe("The description for this version.").optional(),
221
+ scriptId_: z.string().describe("The script project's Drive ID.").optional(),
222
+ versionNumber: z.number().describe("The incremental ID that is created by Apps Script when a version is created. This is system assigned number and is immutable once created.").optional(),
213
223
  fields: z.string().optional().describe('Response field mask.'),
214
224
  },
215
225
  });
@@ -1,6 +1,6 @@
1
1
  // GENERATED by scripts/gen-tools.ts — do not edit. Regenerate: npm run gen:tools
2
2
  import { z } from 'zod';
3
- import { coerceJson } from '../_coerce.js';
3
+ import { coerceBoolean } from '../_coerce.js';
4
4
  import { accountField, registerGeneratedTool } from './_shared.js';
5
5
  export function registerSearchconsoleGeneratedTools(registry) {
6
6
  registerGeneratedTool(registry, {
@@ -10,9 +10,11 @@ export function registerSearchconsoleGeneratedTools(registry) {
10
10
  method: { id: "searchconsole.urlTestingTools.mobileFriendlyTest.run", httpMethod: "POST", path: "v1/urlTestingTools/mobileFriendlyTest:run", baseUrl: "https://searchconsole.googleapis.com/", requiredParams: [] },
11
11
  params: [{ "field": "fields", "api": "fields", "location": "query" }],
12
12
  hasBody: true,
13
+ bodyParams: [{ "field": "requestScreenshot", "api": "requestScreenshot" }, { "field": "url", "api": "url" }],
13
14
  shape: {
14
15
  account: accountField(),
15
- body: coerceJson(z.record(z.string(), z.unknown())).describe("RunMobileFriendlyTestRequest JSON request body. Top-level fields: requestScreenshot, url."),
16
+ requestScreenshot: coerceBoolean.describe("Whether or not screenshot is requested. Default is false.").optional(),
17
+ url: z.string().describe("URL for inspection.").optional(),
16
18
  fields: z.string().optional().describe('Response field mask.'),
17
19
  },
18
20
  });
@@ -56,11 +56,12 @@ export function registerSheetsGeneratedTools(registry) {
56
56
  method: { id: "sheets.spreadsheets.sheets.copyTo", httpMethod: "POST", path: "v4/spreadsheets/{spreadsheetId}/sheets/{sheetId}:copyTo", baseUrl: "https://sheets.googleapis.com/", requiredParams: ["spreadsheetId", "sheetId"], scopes: S_sheets_v4[0] },
57
57
  params: [{ "field": "sheetId", "api": "sheetId", "location": "path" }, { "field": "spreadsheetId", "api": "spreadsheetId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
58
58
  hasBody: true,
59
+ bodyParams: [{ "field": "destinationSpreadsheetId", "api": "destinationSpreadsheetId" }],
59
60
  shape: {
60
61
  account: accountField(),
61
62
  sheetId: z.number().describe("The ID of the sheet to copy."),
62
63
  spreadsheetId: z.string().describe("The ID of the spreadsheet containing the sheet to copy."),
63
- body: coerceJson(z.record(z.string(), z.unknown())).describe("CopySheetToAnotherSpreadsheetRequest JSON request body. Top-level fields: destinationSpreadsheetId."),
64
+ destinationSpreadsheetId: z.string().describe("The ID of the spreadsheet to copy the sheet to.").optional(),
64
65
  fields: z.string().optional().describe('Response field mask.'),
65
66
  },
66
67
  });
@@ -14,10 +14,16 @@ export function registerTasksGeneratedTools(registry) {
14
14
  method: { id: "tasks.tasklists.update", httpMethod: "PUT", path: "tasks/v1/users/@me/lists/{tasklist}", baseUrl: "https://tasks.googleapis.com/", requiredParams: ["tasklist"], scopes: S_tasks_v1[0] },
15
15
  params: [{ "field": "tasklist", "api": "tasklist", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
16
16
  hasBody: true,
17
+ bodyParams: [{ "field": "etag", "api": "etag" }, { "field": "id", "api": "id" }, { "field": "kind", "api": "kind" }, { "field": "selfLink", "api": "selfLink" }, { "field": "title", "api": "title" }, { "field": "updated", "api": "updated" }],
17
18
  shape: {
18
19
  account: accountField(),
19
20
  tasklist: z.string().describe("Task list identifier."),
20
- body: coerceJson(z.record(z.string(), z.unknown())).describe("TaskList JSON request body. Top-level fields: etag, id, kind, selfLink, title, updated."),
21
+ etag: z.string().describe("ETag of the resource.").optional(),
22
+ id: z.string().describe("Task list identifier.").optional(),
23
+ kind: z.string().describe("Output only. Type of the resource. This is always \"tasks#taskList\".").optional(),
24
+ selfLink: z.string().describe("Output only. URL pointing to this task list. Used to retrieve, update, or delete this task list.").optional(),
25
+ title: z.string().describe("Title of the task list. Maximum length allowed: 1024 characters.").optional(),
26
+ updated: z.string().describe("Output only. Last modification time of the task list (as a RFC 3339 timestamp).").optional(),
21
27
  fields: z.string().optional().describe('Response field mask.'),
22
28
  },
23
29
  });
@@ -1,6 +1,6 @@
1
1
  // GENERATED by scripts/gen-tools.ts — do not edit. Regenerate: npm run gen:tools
2
2
  import { z } from 'zod';
3
- import { coerceBoolean, coerceJson } from '../_coerce.js';
3
+ import { coerceArray, coerceBoolean, coerceJson } from '../_coerce.js';
4
4
  import { accountField, registerGeneratedTool } from './_shared.js';
5
5
  export function registerVaultGeneratedTools(registry) {
6
6
  // Interned method scope sets (shared across tools; see scope-observability).
@@ -29,10 +29,10 @@ export function registerVaultGeneratedTools(registry) {
29
29
  method: { id: "vault.matters.close", httpMethod: "POST", path: "v1/matters/{matterId}:close", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId"], scopes: S_vault_v1[0] },
30
30
  params: [{ "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
31
31
  hasBody: true,
32
+ bodyParams: [],
32
33
  shape: {
33
34
  account: accountField(),
34
35
  matterId: z.string().describe("The matter ID."),
35
- body: coerceJson(z.record(z.string(), z.unknown())).describe("CloseMatterRequest JSON request body."),
36
36
  fields: z.string().optional().describe('Response field mask.'),
37
37
  },
38
38
  });
@@ -154,11 +154,16 @@ export function registerVaultGeneratedTools(registry) {
154
154
  method: { id: "vault.matters.holds.accounts.create", httpMethod: "POST", path: "v1/matters/{matterId}/holds/{holdId}/accounts", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId", "holdId"], scopes: S_vault_v1[0] },
155
155
  params: [{ "field": "holdId", "api": "holdId", "location": "path" }, { "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
156
156
  hasBody: true,
157
+ bodyParams: [{ "field": "accountId", "api": "accountId" }, { "field": "email", "api": "email" }, { "field": "firstName", "api": "firstName" }, { "field": "holdTime", "api": "holdTime" }, { "field": "lastName", "api": "lastName" }],
157
158
  shape: {
158
159
  account: accountField(),
159
160
  holdId: z.string().describe("The hold ID."),
160
161
  matterId: z.string().describe("The matter ID."),
161
- body: coerceJson(z.record(z.string(), z.unknown())).describe("HeldAccount JSON request body. Top-level fields: accountId, email, firstName, holdTime, lastName."),
162
+ accountId: z.string().describe("The account ID, as provided by the [Admin SDK](https://developers.google.com/admin-sdk/).").optional(),
163
+ email: z.string().describe("The primary email address of the account. If used as an input, this takes precedence over **accountId**.").optional(),
164
+ firstName: z.string().describe("Output only. The first name of the account holder.").optional(),
165
+ holdTime: z.string().describe("Output only. When the account was put on hold.").optional(),
166
+ lastName: z.string().describe("Output only. The last name of the account holder.").optional(),
162
167
  fields: z.string().optional().describe('Response field mask.'),
163
168
  },
164
169
  });
@@ -198,11 +203,13 @@ export function registerVaultGeneratedTools(registry) {
198
203
  method: { id: "vault.matters.holds.addHeldAccounts", httpMethod: "POST", path: "v1/matters/{matterId}/holds/{holdId}:addHeldAccounts", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId", "holdId"], scopes: S_vault_v1[0] },
199
204
  params: [{ "field": "holdId", "api": "holdId", "location": "path" }, { "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
200
205
  hasBody: true,
206
+ bodyParams: [{ "field": "accountIds", "api": "accountIds" }, { "field": "emails", "api": "emails" }],
201
207
  shape: {
202
208
  account: accountField(),
203
209
  holdId: z.string().describe("The hold ID."),
204
210
  matterId: z.string().describe("The matter ID."),
205
- body: coerceJson(z.record(z.string(), z.unknown())).describe("AddHeldAccountsRequest JSON request body. Top-level fields: accountIds, emails."),
211
+ accountIds: coerceArray(z.string()).describe("A comma-separated list of the account IDs of the accounts to add to the hold. Specify either **emails** or **account_ids**, but not both.").optional(),
212
+ emails: coerceArray(z.string()).describe("A comma-separated list of the emails of the accounts to add to the hold. Specify either **emails** or **account_ids**, but not both.").optional(),
206
213
  fields: z.string().optional().describe('Response field mask.'),
207
214
  },
208
215
  });
@@ -272,11 +279,12 @@ export function registerVaultGeneratedTools(registry) {
272
279
  method: { id: "vault.matters.holds.removeHeldAccounts", httpMethod: "POST", path: "v1/matters/{matterId}/holds/{holdId}:removeHeldAccounts", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId", "holdId"], scopes: S_vault_v1[0] },
273
280
  params: [{ "field": "holdId", "api": "holdId", "location": "path" }, { "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
274
281
  hasBody: true,
282
+ bodyParams: [{ "field": "accountIds", "api": "accountIds" }],
275
283
  shape: {
276
284
  account: accountField(),
277
285
  holdId: z.string().describe("The hold ID."),
278
286
  matterId: z.string().describe("The matter ID."),
279
- body: coerceJson(z.record(z.string(), z.unknown())).describe("RemoveHeldAccountsRequest JSON request body. Top-level fields: accountIds."),
287
+ accountIds: coerceArray(z.string()).describe("The account IDs of the accounts to remove from the hold.").optional(),
280
288
  fields: z.string().optional().describe('Response field mask.'),
281
289
  },
282
290
  });
@@ -318,10 +326,11 @@ export function registerVaultGeneratedTools(registry) {
318
326
  method: { id: "vault.matters.removePermissions", httpMethod: "POST", path: "v1/matters/{matterId}:removePermissions", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId"], scopes: S_vault_v1[0] },
319
327
  params: [{ "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
320
328
  hasBody: true,
329
+ bodyParams: [{ "field": "accountId", "api": "accountId" }],
321
330
  shape: {
322
331
  account: accountField(),
323
332
  matterId: z.string().describe("The matter ID."),
324
- body: coerceJson(z.record(z.string(), z.unknown())).describe("RemoveMatterPermissionsRequest JSON request body. Top-level fields: accountId."),
333
+ accountId: z.string().describe("The account ID.").optional(),
325
334
  fields: z.string().optional().describe('Response field mask.'),
326
335
  },
327
336
  });
@@ -332,10 +341,10 @@ export function registerVaultGeneratedTools(registry) {
332
341
  method: { id: "vault.matters.reopen", httpMethod: "POST", path: "v1/matters/{matterId}:reopen", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId"], scopes: S_vault_v1[0] },
333
342
  params: [{ "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
334
343
  hasBody: true,
344
+ bodyParams: [],
335
345
  shape: {
336
346
  account: accountField(),
337
347
  matterId: z.string().describe("The matter ID."),
338
- body: coerceJson(z.record(z.string(), z.unknown())).describe("ReopenMatterRequest JSON request body."),
339
348
  fields: z.string().optional().describe('Response field mask.'),
340
349
  },
341
350
  });
@@ -403,10 +412,10 @@ export function registerVaultGeneratedTools(registry) {
403
412
  method: { id: "vault.matters.undelete", httpMethod: "POST", path: "v1/matters/{matterId}:undelete", baseUrl: "https://vault.googleapis.com/", requiredParams: ["matterId"], scopes: S_vault_v1[0] },
404
413
  params: [{ "field": "matterId", "api": "matterId", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
405
414
  hasBody: true,
415
+ bodyParams: [],
406
416
  shape: {
407
417
  account: accountField(),
408
418
  matterId: z.string().describe("The matter ID."),
409
- body: coerceJson(z.record(z.string(), z.unknown())).describe("UndeleteMatterRequest JSON request body."),
410
419
  fields: z.string().optional().describe('Response field mask.'),
411
420
  },
412
421
  });
@@ -431,10 +440,10 @@ export function registerVaultGeneratedTools(registry) {
431
440
  method: { id: "vault.operations.cancel", httpMethod: "POST", path: "v1/{+name}:cancel", baseUrl: "https://vault.googleapis.com/", requiredParams: ["name"], scopes: S_vault_v1[0] },
432
441
  params: [{ "field": "name", "api": "name", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
433
442
  hasBody: true,
443
+ bodyParams: [],
434
444
  shape: {
435
445
  account: accountField(),
436
446
  name: z.string().describe("The name of the operation resource to be cancelled."),
437
- body: coerceJson(z.record(z.string(), z.unknown())).describe("CancelOperationRequest JSON request body."),
438
447
  fields: z.string().optional().describe('Response field mask.'),
439
448
  },
440
449
  });
@@ -115,10 +115,10 @@ export function registerWorkspaceeventsGeneratedTools(registry) {
115
115
  method: { id: "workspaceevents.subscriptions.reactivate", httpMethod: "POST", path: "v1/{+name}:reactivate", baseUrl: "https://workspaceevents.googleapis.com/", requiredParams: ["name"], scopes: S_workspaceevents_v1[1] },
116
116
  params: [{ "field": "name", "api": "name", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
117
117
  hasBody: true,
118
+ bodyParams: [],
118
119
  shape: {
119
120
  account: accountField(),
120
121
  name: z.string().describe("Required. Resource name of the subscription. Format: `subscriptions/{subscription}`"),
121
- body: coerceJson(z.record(z.string(), z.unknown())).describe("ReactivateSubscriptionRequest JSON request body."),
122
122
  fields: z.string().optional().describe('Response field mask.'),
123
123
  },
124
124
  });
@@ -129,10 +129,11 @@ export function registerWorkspaceeventsGeneratedTools(registry) {
129
129
  method: { id: "workspaceevents.tasks.cancel", httpMethod: "POST", path: "v1/{+name}:cancel", baseUrl: "https://workspaceevents.googleapis.com/", requiredParams: ["name"] },
130
130
  params: [{ "field": "name", "api": "name", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
131
131
  hasBody: true,
132
+ bodyParams: [{ "field": "tenant", "api": "tenant" }],
132
133
  shape: {
133
134
  account: accountField(),
134
135
  name: z.string().describe("The resource name of the task to cancel. Format: tasks/{task_id}"),
135
- body: coerceJson(z.record(z.string(), z.unknown())).describe("CancelTaskRequest JSON request body. Top-level fields: tenant."),
136
+ tenant: z.string().describe("Optional tenant, provided as a path parameter. Experimental, might still change for 1.0 release.").optional(),
136
137
  fields: z.string().optional().describe('Response field mask.'),
137
138
  },
138
139
  });
@@ -5,6 +5,7 @@ import { accountAliasSchema } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError, mapGoogleError } from './_errors.js';
7
7
  import { buildReplyHeaders, composeRaw, renderMarkdown, htmlToMarkdown, HeaderInjectionError } from './gmail-mime.js';
8
+ import { prepareLocalDest } from './_local-files.js';
8
9
  import addressparser from 'nodemailer/lib/addressparser/index.js';
9
10
  import { lookup as lookupMime } from 'mime-types';
10
11
  import { configDir } from '../config-file.js';
@@ -664,7 +665,7 @@ export function registerGmailTools(server) {
664
665
  messageId: z.string().describe('The Gmail message ID'),
665
666
  attachmentId: z.string().describe('The attachment ID from gmail_read response'),
666
667
  filename: z.string().describe('Filename to save as (e.g. report.xlsx)'),
667
- savePath: z.string().describe('Absolute directory path to save into, e.g. /home/user/Downloads'),
668
+ savePath: z.string().describe('Absolute DIRECTORY path to save into (created if missing, on the machine running the server), e.g. /home/user/Downloads; the file name comes from `filename`'),
668
669
  },
669
670
  }, async ({ account, messageId, attachmentId, filename, savePath }) => {
670
671
  try {
@@ -679,8 +680,7 @@ export function registerGmailTools(server) {
679
680
  if (!data)
680
681
  throw new Error('No attachment data returned');
681
682
  const buffer = Buffer.from(data, 'base64url');
682
- // Strip path components so callers can't escape savePath via "../".
683
- const fullPath = path.join(savePath, path.basename(filename));
683
+ const fullPath = prepareLocalDest(savePath, filename);
684
684
  await fs.promises.writeFile(fullPath, buffer, { mode: 0o600 });
685
685
  return {
686
686
  content: [{ type: 'text', text: `Saved to ${fullPath} (${buffer.length} bytes)` }],
@@ -2,9 +2,12 @@ import type { ToolRegistry } from '../registry.js';
2
2
  import { type Policy } from '../write-control.js';
3
3
  import { getClient } from '../client.js';
4
4
  import { type Toolsets } from '../toolsets.js';
5
- import { type DiscoveryDeps } from '../discovery-client.js';
5
+ import { type DiscoveryDeps, type DiscoveryMethod } from '../discovery-client.js';
6
6
  export interface EscapeDeps extends DiscoveryDeps {
7
7
  getClientFn?: typeof getClient;
8
8
  toolsets?: Toolsets;
9
9
  }
10
+ /** Up to three closest known ids for the unknown_method did-you-mean hint;
11
+ * bounded distance so unrelated ids never masquerade as suggestions. */
12
+ export declare function nearestMethodIds(methodId: string, index: DiscoveryMethod[]): string[];
10
13
  export declare function registerEscapeTools(registry: ToolRegistry, policy: Policy, deps?: EscapeDeps): void;
@@ -4,8 +4,9 @@ import { accountAliasSchema } from '../accounts.js';
4
4
  import { getClient } from '../client.js';
5
5
  import { coerceJson } from './_coerce.js';
6
6
  import { getToolsets, toolsetEnabled } from '../toolsets.js';
7
+ import { editDistance } from '../scope-catalog.js';
7
8
  import { executeApiMethod, jsonResult } from '../executor.js';
8
- import { WORKSPACE_APIS, cudFromMethod, loadMethodIndex, searchMethods, } from '../discovery-client.js';
9
+ import { SUPPORTED_APIS, resolveApiAliases, cudFromMethod, loadMethodIndex, searchMethods, } from '../discovery-client.js';
9
10
  const accountEnum = accountAliasSchema.optional();
10
11
  // Policy/toolset namespace for each API alias must match the NAMED tools' service
11
12
  // names, or user deny globs and GOOGLE_TOOLSETS silently miss escape-hatch calls.
@@ -28,7 +29,20 @@ const SERVICE_FOR_ALIAS = {
28
29
  admin_reports: 'admin',
29
30
  admin_datatransfer: 'admin',
30
31
  groupssettings: 'groupssettings',
32
+ analyticsadmin: 'analytics',
33
+ analyticsdata: 'analytics',
31
34
  };
35
+ /** Up to three closest known ids for the unknown_method did-you-mean hint;
36
+ * bounded distance so unrelated ids never masquerade as suggestions. */
37
+ export function nearestMethodIds(methodId, index) {
38
+ const maxDist = Math.max(3, Math.floor(methodId.length / 3));
39
+ return index
40
+ .map((m) => ({ id: m.id, d: editDistance(methodId.toLowerCase(), m.id.toLowerCase()) }))
41
+ .filter((x) => x.d <= maxDist)
42
+ .sort((a, b) => a.d - b.d)
43
+ .slice(0, 3)
44
+ .map((x) => x.id);
45
+ }
32
46
  function describeMethod(m) {
33
47
  return {
34
48
  api: m.api,
@@ -47,7 +61,7 @@ export function registerEscapeTools(registry, policy, deps = {}) {
47
61
  const apiEnabled = (alias) => {
48
62
  return toolsetEnabled(toolsets, serviceForAlias(alias));
49
63
  };
50
- const enabledApis = Object.keys(WORKSPACE_APIS).filter(apiEnabled);
64
+ const enabledApis = Object.keys(SUPPORTED_APIS).filter(apiEnabled);
51
65
  const apiList = enabledApis.join(', ');
52
66
  const toolsetDisabled = (alias) => jsonResult({
53
67
  error: 'toolset_disabled',
@@ -66,12 +80,20 @@ export function registerEscapeTools(registry, policy, deps = {}) {
66
80
  },
67
81
  annotations: { openWorldHint: true },
68
82
  }, async ({ query, api, maxResults }) => {
69
- if (api && !WORKSPACE_APIS[api]) {
70
- return jsonResult({ error: 'unknown_api', message: `Unknown api "${api}".`, hint: `Known APIs: ${apiList}`, retriable: false }, true);
83
+ // Alias resolution: "analytics" fans out to both GA4 Discovery APIs
84
+ // instead of dead-ending on unknown_api.
85
+ let requested = null;
86
+ if (api) {
87
+ requested = resolveApiAliases(api);
88
+ if (!requested) {
89
+ return jsonResult({ error: 'unknown_api', message: `Unknown api "${api}".`, hint: `Known APIs: ${apiList}`, retriable: false }, true);
90
+ }
91
+ const enabled = requested.filter(apiEnabled);
92
+ if (enabled.length === 0)
93
+ return toolsetDisabled(requested[0]);
94
+ requested = enabled;
71
95
  }
72
- if (api && !apiEnabled(api))
73
- return toolsetDisabled(api);
74
- const apis = api ? [api] : enabledApis;
96
+ const apis = requested ?? enabledApis;
75
97
  const unavailable = [];
76
98
  const indexes = await Promise.all(apis.map(async (a) => {
77
99
  try {
@@ -84,6 +106,9 @@ export function registerEscapeTools(registry, policy, deps = {}) {
84
106
  }));
85
107
  const matches = searchMethods(indexes.flat(), query, maxResults ?? 10);
86
108
  return jsonResult({
109
+ // Teach the resolved keys whenever the input wasn't already one, so the
110
+ // follow-up google_api_call uses a real key.
111
+ ...(requested && (requested.length > 1 || requested[0] !== api) ? { resolvedApi: requested } : {}),
87
112
  methods: matches.map(describeMethod),
88
113
  ...(unavailable.length > 0 ? { unavailableApis: unavailable } : {}),
89
114
  next: 'Invoke with google_api_call({account, api, methodId, pathParams, queryParams, body}).',
@@ -115,30 +140,49 @@ export function registerEscapeTools(registry, policy, deps = {}) {
115
140
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
116
141
  _meta: { 'anthropic/maxResultSizeChars': 100_000 },
117
142
  }, async ({ account, api, methodId, pathParams, queryParams, body }) => {
118
- if (!WORKSPACE_APIS[api]) {
143
+ const resolved = resolveApiAliases(api);
144
+ if (!resolved) {
119
145
  return jsonResult({ error: 'unknown_api', message: `Unknown api "${api}".`, hint: `Known APIs: ${apiList}`, retriable: false, account }, true);
120
146
  }
121
- if (!apiEnabled(api))
122
- return toolsetDisabled(api);
147
+ if (resolved.length > 1) {
148
+ // A call targets exactly one Discovery doc; only search can fan out.
149
+ return jsonResult({ error: 'unknown_api', message: `"${api}" maps to ${resolved.length} APIs.`, hint: `Pass one of: ${resolved.join(', ')} (method ids differ per API; google_api_search({query, api: "${api}"}) finds the right one).`, retriable: false, account }, true);
150
+ }
151
+ const apiKey = resolved[0];
152
+ if (!apiEnabled(apiKey))
153
+ return toolsetDisabled(apiKey);
123
154
  let index;
124
155
  try {
125
- index = await loadMethodIndex(api, deps);
156
+ index = await loadMethodIndex(apiKey, deps);
126
157
  }
127
158
  catch (err) {
128
159
  return jsonResult({ error: 'discovery_unavailable', message: err.message, retriable: true, account }, true);
129
160
  }
130
- const method = index.find((m) => m.id === methodId);
161
+ let method = index.find((m) => m.id === methodId);
162
+ if (!method) {
163
+ // Some discovery docs keep a legacy id prefix (the searchconsole doc's
164
+ // methods are webmasters.*): when the caller prefixed with our api
165
+ // alias, retry under the doc's own prefix before failing.
166
+ const docPrefix = index[0]?.id.split('.')[0];
167
+ const [head, ...rest] = String(methodId).split('.');
168
+ if (docPrefix && (head === apiKey || head === api) && head !== docPrefix && rest.length > 0) {
169
+ const swapped = [docPrefix, ...rest].join('.');
170
+ method = index.find((m) => m.id === swapped);
171
+ }
172
+ }
131
173
  if (!method) {
174
+ const near = nearestMethodIds(String(methodId), index);
132
175
  return jsonResult({
133
176
  error: 'unknown_method',
134
- message: `No method "${methodId}" in ${api}.`,
135
- hint: `Use google_api_search({query: "...", api: "${api}"}) to find the right method id.`,
177
+ message: `No method "${methodId}" in ${apiKey}.`,
178
+ hint: `${near.length ? `Did you mean: ${near.join(', ')}? ` : ''}` +
179
+ `Use google_api_search({query: "...", api: "${apiKey}"}) to find the right method id.`,
136
180
  retriable: false,
137
181
  account,
138
182
  }, true);
139
183
  }
140
184
  const cud = cudFromMethod(method);
141
- const policyService = serviceForAlias(api);
185
+ const policyService = serviceForAlias(apiKey);
142
186
  const lastSegment = method.id.split('.').pop() ?? method.id;
143
187
  const toolRef = { name: `${policyService}_${lastSegment}`, service: policyService, cud };
144
188
  if (cud !== 'read' && !isAllowed(toolRef, policy)) {