mcp-google-multi 6.0.0-beta.2 → 6.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounts.d.ts +17 -1
- package/dist/accounts.js +52 -15
- package/dist/api-probe.d.ts +5 -0
- package/dist/api-probe.js +20 -15
- package/dist/arg-normalize.js +6 -5
- package/dist/arg-strict.d.ts +7 -0
- package/dist/arg-strict.js +34 -4
- package/dist/auth.d.ts +4 -3
- package/dist/auth.js +11 -13
- package/dist/boot-gates.d.ts +26 -0
- package/dist/boot-gates.js +38 -0
- package/dist/cimd-cache.d.ts +38 -0
- package/dist/cimd-cache.js +117 -0
- package/dist/client.d.ts +32 -2
- package/dist/client.js +0 -0
- package/dist/compose.d.ts +11 -0
- package/dist/compose.js +109 -0
- package/dist/config-file.d.ts +13 -0
- package/dist/config-file.js +37 -1
- package/dist/discover.js +3 -1
- package/dist/discovery-client.d.ts +3 -0
- package/dist/discovery-client.js +25 -2
- package/dist/doctor.d.ts +6 -2
- package/dist/doctor.js +119 -23
- package/dist/executor.d.ts +3 -0
- package/dist/executor.js +4 -2
- package/dist/fanout.d.ts +9 -1
- package/dist/fanout.js +44 -10
- package/dist/fs-atomic.d.ts +11 -0
- package/dist/fs-atomic.js +219 -28
- package/dist/hint-provider.d.ts +129 -0
- package/dist/hint-provider.js +64 -0
- package/dist/http-transport.d.ts +62 -4
- package/dist/http-transport.js +343 -24
- package/dist/identity.d.ts +4 -1
- package/dist/identity.js +25 -6
- package/dist/index.js +66 -138
- package/dist/master-key.js +13 -5
- package/dist/mcp-token.d.ts +136 -19
- package/dist/mcp-token.js +569 -72
- package/dist/migrate-config.js +2 -2
- package/dist/oauth-as.d.ts +103 -3
- package/dist/oauth-as.js +374 -79
- package/dist/oauth-consent.d.ts +10 -0
- package/dist/oauth-consent.js +16 -0
- package/dist/outbound-allowlist.js +5 -2
- package/dist/reauth-hint.d.ts +1 -1
- package/dist/reauth-hint.js +12 -8
- package/dist/registry-transfer.d.ts +5 -0
- package/dist/registry-transfer.js +65 -5
- package/dist/registry.d.ts +18 -1
- package/dist/registry.js +71 -15
- package/dist/scope-catalog.d.ts +11 -0
- package/dist/scope-catalog.js +23 -6
- package/dist/scope-observability.d.ts +8 -6
- package/dist/scope-observability.js +29 -13
- package/dist/services.d.ts +15 -10
- package/dist/services.js +59 -15
- package/dist/ssrf-guard.d.ts +23 -9
- package/dist/ssrf-guard.js +101 -62
- package/dist/tenant-purge.d.ts +26 -0
- package/dist/tenant-purge.js +67 -0
- package/dist/token-store.d.ts +20 -2
- package/dist/token-store.js +0 -0
- package/dist/tools/_errors.d.ts +3 -0
- package/dist/tools/_errors.js +53 -24
- package/dist/tools/_local-files.d.ts +9 -0
- package/dist/tools/_local-files.js +18 -0
- package/dist/tools/account-wizard.d.ts +10 -1
- package/dist/tools/account-wizard.js +74 -17
- package/dist/tools/accounts-tool.d.ts +7 -0
- package/dist/tools/accounts-tool.js +34 -18
- package/dist/tools/admin.d.ts +10 -1
- package/dist/tools/admin.js +15 -11
- package/dist/tools/analytics.d.ts +10 -1
- package/dist/tools/analytics.js +13 -8
- package/dist/tools/calendar.d.ts +2 -1
- package/dist/tools/calendar.js +18 -14
- package/dist/tools/chat.d.ts +10 -1
- package/dist/tools/chat.js +13 -8
- package/dist/tools/contacts.d.ts +2 -1
- package/dist/tools/contacts.js +17 -13
- package/dist/tools/docs.d.ts +2 -1
- package/dist/tools/docs.js +34 -30
- package/dist/tools/drive.d.ts +2 -1
- package/dist/tools/drive.js +65 -50
- package/dist/tools/forms.d.ts +10 -1
- package/dist/tools/forms.js +19 -12
- package/dist/tools/generated/_shared.d.ts +2 -1
- package/dist/tools/generated/_shared.js +7 -3
- package/dist/tools/generated/admin.d.ts +2 -1
- package/dist/tools/generated/admin.js +265 -265
- package/dist/tools/generated/analytics.d.ts +2 -1
- package/dist/tools/generated/analytics.js +125 -125
- package/dist/tools/generated/appsmarket.d.ts +2 -1
- package/dist/tools/generated/appsmarket.js +5 -5
- package/dist/tools/generated/calendar.d.ts +2 -1
- package/dist/tools/generated/calendar.js +55 -55
- package/dist/tools/generated/chat.d.ts +2 -1
- package/dist/tools/generated/chat.js +95 -95
- package/dist/tools/generated/classroom.d.ts +2 -1
- package/dist/tools/generated/classroom.js +209 -209
- package/dist/tools/generated/cloudidentity.d.ts +2 -1
- package/dist/tools/generated/cloudidentity.js +133 -133
- package/dist/tools/generated/cloudsearch.d.ts +2 -1
- package/dist/tools/generated/cloudsearch.js +99 -99
- package/dist/tools/generated/contacts.d.ts +2 -1
- package/dist/tools/generated/contacts.js +29 -29
- package/dist/tools/generated/drive.d.ts +2 -1
- package/dist/tools/generated/drive.js +71 -71
- package/dist/tools/generated/driveactivity.d.ts +2 -1
- package/dist/tools/generated/driveactivity.js +3 -3
- package/dist/tools/generated/drivelabels.d.ts +2 -1
- package/dist/tools/generated/drivelabels.js +53 -53
- package/dist/tools/generated/forms.d.ts +2 -1
- package/dist/tools/generated/forms.js +7 -7
- package/dist/tools/generated/gmail.d.ts +2 -1
- package/dist/tools/generated/gmail.js +117 -117
- package/dist/tools/generated/groupsmigration.d.ts +2 -1
- package/dist/tools/generated/groupsmigration.js +3 -3
- package/dist/tools/generated/groupssettings.d.ts +2 -1
- package/dist/tools/generated/groupssettings.js +7 -7
- package/dist/tools/generated/index.d.ts +2 -1
- package/dist/tools/generated/keep.d.ts +2 -1
- package/dist/tools/generated/keep.js +15 -15
- package/dist/tools/generated/licensing.d.ts +2 -1
- package/dist/tools/generated/licensing.js +15 -15
- package/dist/tools/generated/meet.d.ts +2 -1
- package/dist/tools/generated/meet.js +27 -27
- package/dist/tools/generated/postmaster.d.ts +2 -1
- package/dist/tools/generated/postmaster.js +9 -9
- package/dist/tools/generated/reseller.d.ts +2 -1
- package/dist/tools/generated/reseller.js +35 -35
- package/dist/tools/generated/script.d.ts +2 -1
- package/dist/tools/generated/script.js +33 -33
- package/dist/tools/generated/searchconsole.d.ts +2 -1
- package/dist/tools/generated/searchconsole.js +3 -3
- package/dist/tools/generated/sheets.d.ts +2 -1
- package/dist/tools/generated/sheets.js +15 -15
- package/dist/tools/generated/tasks.d.ts +2 -1
- package/dist/tools/generated/tasks.js +5 -5
- package/dist/tools/generated/vault.d.ts +2 -1
- package/dist/tools/generated/vault.js +67 -67
- package/dist/tools/generated/workspaceevents.d.ts +2 -1
- package/dist/tools/generated/workspaceevents.js +31 -31
- package/dist/tools/gmail.d.ts +2 -1
- package/dist/tools/gmail.js +45 -33
- package/dist/tools/google-api.d.ts +2 -3
- package/dist/tools/google-api.js +30 -16
- package/dist/tools/meet.d.ts +10 -1
- package/dist/tools/meet.js +13 -9
- package/dist/tools/searchconsole.d.ts +2 -1
- package/dist/tools/searchconsole.js +17 -13
- package/dist/tools/sheets.d.ts +2 -1
- package/dist/tools/sheets.js +36 -32
- package/dist/tools/slides.d.ts +10 -1
- package/dist/tools/slides.js +17 -10
- package/dist/tools/tasks.d.ts +2 -1
- package/dist/tools/tasks.js +19 -15
- package/dist/trim.d.ts +5 -0
- package/dist/trim.js +20 -0
- package/dist/usage-metrics.d.ts +23 -0
- package/dist/usage-metrics.js +65 -47
- package/dist/write-control.js +10 -2
- package/package.json +27 -1
package/dist/tools/_errors.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { reauthHint } from '../reauth-hint.js';
|
|
2
2
|
import { sliceClean, sliceEncoded } from '../trim.js';
|
|
3
|
+
import { hints as hintProvider } from '../hint-provider.js';
|
|
3
4
|
const MAX_ERROR_MESSAGE_CHARS = 1000;
|
|
4
5
|
const MAX_ERROR_ENVELOPE_CHARS = 4000;
|
|
5
6
|
const MAX_BODY_PARSE_CHARS = 64_000;
|
|
@@ -175,10 +176,11 @@ function rateLimited(error, message, account, reason) {
|
|
|
175
176
|
};
|
|
176
177
|
}
|
|
177
178
|
if (reason === 'dailyLimitExceeded') {
|
|
179
|
+
const rendered = hintProvider()?.render({ kind: 'daily_quota' });
|
|
178
180
|
return {
|
|
179
181
|
error: 'rate_limited',
|
|
180
182
|
message,
|
|
181
|
-
hint: 'A per-day project quota, not a burst limit: it resets at midnight Pacific. Reduce call volume or raise the quota in the Cloud Console; a tight retry loop will not clear it.',
|
|
183
|
+
hint: rendered?.hint ?? 'A per-day project quota, not a burst limit: it resets at midnight Pacific. Reduce call volume or raise the quota in the Cloud Console; a tight retry loop will not clear it.',
|
|
182
184
|
retriable: true,
|
|
183
185
|
account,
|
|
184
186
|
};
|
|
@@ -222,21 +224,24 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
222
224
|
.filter(Boolean)
|
|
223
225
|
.join(' ');
|
|
224
226
|
if ((status === 400 || status === 401) && /invalid_grant/i.test(grantHaystack)) {
|
|
227
|
+
const rendered = hintProvider()?.render({ kind: 'dead_refresh_token', account });
|
|
225
228
|
return {
|
|
226
229
|
error: 'reauth_required',
|
|
227
230
|
message,
|
|
228
|
-
hint:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
hint: rendered?.hint ??
|
|
232
|
+
(`Refresh token for "${account}" is dead (often the 7-day Testing-mode trap). ` +
|
|
233
|
+
'Set Publishing status to In production at https://console.cloud.google.com/auth/audience, ' +
|
|
234
|
+
`then ${reauthHint(account)}`),
|
|
231
235
|
retriable: false,
|
|
232
236
|
account,
|
|
233
237
|
};
|
|
234
238
|
}
|
|
235
239
|
if (status === 401) {
|
|
240
|
+
const rendered = hintProvider()?.render({ kind: 'reauth', account });
|
|
236
241
|
return {
|
|
237
242
|
error: 'auth_required',
|
|
238
243
|
message: `Authentication failed for account "${account}".`,
|
|
239
|
-
hint: reauthHint(account),
|
|
244
|
+
hint: rendered?.hint ?? reauthHint(account),
|
|
240
245
|
retriable: false,
|
|
241
246
|
account,
|
|
242
247
|
};
|
|
@@ -252,7 +257,14 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
252
257
|
reason === 'SERVICE_DISABLED' ||
|
|
253
258
|
/has not been used in project|accessNotConfigured|SERVICE_DISABLED|it is disabled/i.test(message);
|
|
254
259
|
if (notEnabled) {
|
|
255
|
-
|
|
260
|
+
const rendered = hintProvider()?.render({
|
|
261
|
+
kind: 'api_not_enabled',
|
|
262
|
+
api: disabledApiId(message) ?? undefined,
|
|
263
|
+
service: info?.metadata?.service,
|
|
264
|
+
project: info?.metadata?.consumer?.replace(/^projects\//, ''),
|
|
265
|
+
googleMessage: message,
|
|
266
|
+
});
|
|
267
|
+
return { error: 'api_not_enabled', message, hint: rendered?.hint ?? enableHint(info, message), retriable: false, account };
|
|
256
268
|
}
|
|
257
269
|
// The scope hint fires ONLY on the machine-readable token. A coarse AIP
|
|
258
270
|
// status of PERMISSION_DENIED is equally true of a resource denial, and
|
|
@@ -262,10 +274,13 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
262
274
|
/\bACCESS_TOKEN_SCOPE_INSUFFICIENT\b/.test(message);
|
|
263
275
|
if (scopeIssue) {
|
|
264
276
|
const enriched = scopeContext?.() ?? null;
|
|
277
|
+
const fallback = enriched
|
|
278
|
+
? null
|
|
279
|
+
: hintProvider()?.render({ kind: 'scope_unknown', account, serviceText: hints.scope, bundle: hints.bundle });
|
|
265
280
|
return {
|
|
266
281
|
error: 'insufficient_scope',
|
|
267
282
|
message,
|
|
268
|
-
hint: enriched?.hint ?? hints.scope ?? `Re-auth "${account}" with the scope this operation needs. ${reauthHint(account)}`,
|
|
283
|
+
hint: enriched?.hint ?? fallback?.hint ?? hints.scope ?? `Re-auth "${account}" with the scope this operation needs. ${reauthHint(account)}`,
|
|
269
284
|
retriable: enriched?.retriable ?? false,
|
|
270
285
|
account,
|
|
271
286
|
};
|
|
@@ -274,7 +289,8 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
274
289
|
return { error: 'forbidden', message, hint: `Drive storage for "${account}" is full. Free space or use another account; retrying will not help.`, retriable: false, account };
|
|
275
290
|
}
|
|
276
291
|
if (reason === 'CONSUMER_SUSPENDED' || reason === 'accountSuspended') {
|
|
277
|
-
|
|
292
|
+
const rendered = hintProvider()?.render({ kind: 'project_suspended', account });
|
|
293
|
+
return { error: 'forbidden', message, hint: rendered?.hint ?? `Google has suspended the project or the account behind "${account}". Check the Cloud Console notifications; no change to the request will fix this.`, retriable: false, account };
|
|
278
294
|
}
|
|
279
295
|
if (reason === 'domainPolicy' || reason === 'ORG_RESTRICTION_VIOLATION') {
|
|
280
296
|
return { error: 'forbidden', message, hint: `A Workspace admin policy blocks this operation for the domain of "${account}". A super-admin has to allow the app or the feature; re-auth will not change it.`, retriable: false, account };
|
|
@@ -289,10 +305,11 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
289
305
|
};
|
|
290
306
|
}
|
|
291
307
|
if (status === 400 && /invalid[_ ]scope/i.test(message)) {
|
|
308
|
+
const rendered = hintProvider()?.render({ kind: 'invalid_scope', account });
|
|
292
309
|
return {
|
|
293
310
|
error: 'invalid_scope',
|
|
294
311
|
message,
|
|
295
|
-
hint: 'One of the requested OAuth scopes is malformed or unavailable to this client. Run `config check` to review the account scope profile, fix it, then re-auth.',
|
|
312
|
+
hint: rendered?.hint ?? 'One of the requested OAuth scopes is malformed or unavailable to this client. Run `config check` to review the account scope profile, fix it, then re-auth.',
|
|
296
313
|
retriable: false,
|
|
297
314
|
account,
|
|
298
315
|
};
|
|
@@ -335,13 +352,15 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
335
352
|
// The server's own preconditions, tagged at the throw site in client.ts.
|
|
336
353
|
// Untagged they reached the floor and were reported as Google failures.
|
|
337
354
|
if (error?.code === 'E_NO_TOKEN') {
|
|
338
|
-
|
|
355
|
+
const rendered = hintProvider()?.render({ kind: 'reauth', account });
|
|
356
|
+
return { error: 'auth_required', message, hint: rendered?.hint ?? reauthHint(account), retriable: false, account };
|
|
339
357
|
}
|
|
340
358
|
if (error?.code === 'E_NO_OAUTH_CLIENT') {
|
|
359
|
+
const rendered = hintProvider()?.render({ kind: 'no_oauth_client' });
|
|
341
360
|
return {
|
|
342
361
|
error: 'invalid_client',
|
|
343
362
|
message,
|
|
344
|
-
hint: 'Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET (see docs/google-cloud-setup.md), then restart the server.',
|
|
363
|
+
hint: rendered?.hint ?? 'Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET (see docs/google-cloud-setup.md), then restart the server.',
|
|
345
364
|
retriable: false,
|
|
346
365
|
account,
|
|
347
366
|
};
|
|
@@ -357,23 +376,30 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
357
376
|
}
|
|
358
377
|
const fsCode = typeof error?.code === 'string' && LOCAL_FS_CODES.has(error.code) ? error.code : undefined;
|
|
359
378
|
if (fsCode) {
|
|
360
|
-
const p = typeof error?.path === 'string' ?
|
|
379
|
+
const p = typeof error?.path === 'string' ? error.path : undefined;
|
|
380
|
+
const rendered = hintProvider()?.render({ kind: 'local_path', code: fsCode, path: p });
|
|
361
381
|
return {
|
|
362
382
|
error: 'invalid_params',
|
|
363
|
-
message: `Cannot access local path${p}: ${fsCode}`,
|
|
364
|
-
hint:
|
|
365
|
-
'
|
|
383
|
+
message: rendered?.message ?? `Cannot access local path${p ? ` "${p}"` : ''}: ${fsCode}`,
|
|
384
|
+
hint: rendered?.hint ??
|
|
385
|
+
('The path must exist on the machine running this server and be accessible to it. ' +
|
|
386
|
+
'When the server runs remotely (HTTP transport), paths on your own machine are not visible to it.'),
|
|
366
387
|
retriable: false,
|
|
367
388
|
account,
|
|
368
389
|
};
|
|
369
390
|
}
|
|
370
391
|
const netCode = netCodeOf(error);
|
|
371
392
|
if (netCode) {
|
|
393
|
+
const rendered = hintProvider()?.render({ kind: 'network_error', code: netCode });
|
|
372
394
|
return {
|
|
373
395
|
error: 'network_error',
|
|
374
396
|
message: message.includes(netCode) ? message : message.endsWith('reason: ') ? `${message}${netCode}` : `${message} (${netCode})`,
|
|
375
|
-
hint:
|
|
376
|
-
|
|
397
|
+
hint: rendered?.hint ??
|
|
398
|
+
(`Network failure (${netCode}) before reaching Google - not an auth or API problem. Usually transient: retry. ` +
|
|
399
|
+
'If it persists on a high-latency or broken-IPv6 link, raise the happy-eyeballs budget: NODE_OPTIONS=--network-family-autoselection-attempt-timeout=4000 (server default 2000ms), and check connectivity with curl.'),
|
|
400
|
+
// Not an override site (C5 is the only kind whose retriable the host
|
|
401
|
+
// may flip): a multi-tenant host keeps core's retriable codes for
|
|
402
|
+
// network_error.
|
|
377
403
|
retriable: RETRIABLE_NET_CODES.has(netCode),
|
|
378
404
|
account,
|
|
379
405
|
};
|
|
@@ -392,13 +418,16 @@ export function mapGoogleError(error, account, serviceHints, scopeContext) {
|
|
|
392
418
|
account,
|
|
393
419
|
};
|
|
394
420
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
421
|
+
{
|
|
422
|
+
const rendered = hintProvider()?.render({ kind: 'internal', account });
|
|
423
|
+
return {
|
|
424
|
+
error: 'internal',
|
|
425
|
+
message: rendered?.message ?? message,
|
|
426
|
+
hint: rendered?.hint ?? 'This failed inside the MCP server, before any request was sent. The message above is the signal: it is most often a configuration problem such as a missing key or credential. Retrying the same call will fail the same way.',
|
|
427
|
+
retriable: false,
|
|
428
|
+
account,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
402
431
|
}
|
|
403
432
|
/** Bound the SERIALIZED envelope, not just the message: an authored hint can
|
|
404
433
|
* be long too, and the error path had no cap at all before this. */
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
/** Refusal for a host-file argument on a context that may not touch the
|
|
3
|
+
* server's disk: the host belongs to its operator, not to every caller. */
|
|
4
|
+
export declare function hostFilesRefused(account: string | undefined, what: string): {
|
|
5
|
+
content: {
|
|
6
|
+
type: "text";
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
isError: true;
|
|
10
|
+
};
|
|
2
11
|
export declare function prepareLocalDest(savePath: string, filename: string): string;
|
|
3
12
|
export declare function openLocalReadStream(localPath: string): Promise<fs.ReadStream>;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { stringifyEnvelope } from './_errors.js';
|
|
4
|
+
/** Refusal for a host-file argument on a context that may not touch the
|
|
5
|
+
* server's disk: the host belongs to its operator, not to every caller. */
|
|
6
|
+
export function hostFilesRefused(account, what) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: stringifyEnvelope({
|
|
11
|
+
error: 'forbidden',
|
|
12
|
+
message: `${what}: this caller may not read files on the machine running the server.`,
|
|
13
|
+
hint: 'Put the file in Drive first and work from its Drive id.',
|
|
14
|
+
retriable: false,
|
|
15
|
+
account,
|
|
16
|
+
}),
|
|
17
|
+
}],
|
|
18
|
+
isError: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
3
21
|
// path.basename() is a traversal guard — a caller-supplied filename must never escape savePath.
|
|
4
22
|
export function prepareLocalDest(savePath, filename) {
|
|
5
23
|
const name = path.basename(filename);
|
|
@@ -53,4 +53,13 @@ export declare function argsToAddForm(a: {
|
|
|
53
53
|
/** Scopes requested by the profile but NOT granted at consent (granular
|
|
54
54
|
* consent / unchecked bundles). Pure. */
|
|
55
55
|
export declare function scopeGrantDiff(requested: string[], grantedScope: string | undefined): string[];
|
|
56
|
-
|
|
56
|
+
/** HTTP-transport consent context (S1.20): set by the HTTP bootstrap. When
|
|
57
|
+
* present, runConsent NEVER binds a server-side loopback listener — the
|
|
58
|
+
* user's browser cannot reach the daemon's loopback — and instead hands out
|
|
59
|
+
* a clientless AS consent URL. The injector decides the flow (single-owner =
|
|
60
|
+
* the legacy alias_reauth link). */
|
|
61
|
+
export interface WizardHttpConsent {
|
|
62
|
+
mintConsentUrl: (alias: string) => Promise<string> | string;
|
|
63
|
+
}
|
|
64
|
+
export declare function setWizardHttpConsent(ctx: WizardHttpConsent | null): void;
|
|
65
|
+
export declare function registerAccountWizardTools(registry: ToolRegistry, server: McpServer, awaitingRestart?: () => string[]): void;
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { getAccountSet, invalidateAccountSet } from '../accounts.js';
|
|
3
|
-
import { mutateConfigFile } from '../config-file.js';
|
|
2
|
+
import { getAccountSet, invalidateAccountSet, refreshAccountSetIfStale } from '../accounts.js';
|
|
3
|
+
import { ALIAS_RE, mutateConfigFile } from '../config-file.js';
|
|
4
4
|
import { writeToken } from '../token-store.js';
|
|
5
5
|
import { resolveScopesForAccount } from '../auth.js';
|
|
6
|
-
import { BUNDLE_CATALOG, closestBundle, resolveBundleAliases } from '../scope-catalog.js';
|
|
6
|
+
import { BUNDLE_CATALOG, closestBundle, isKnownBundle, resolveBundleAliases } from '../scope-catalog.js';
|
|
7
7
|
import { openUrl } from '../open-url.js';
|
|
8
8
|
import { coerceBoolean } from './_coerce.js';
|
|
9
9
|
import { safeMessage, stringifyEnvelope } from './_errors.js';
|
|
10
10
|
import { buildConsentClient, openLoopbackConsent, hasClientCredentials, TESTING_MODE_WARNING, } from '../oauth-consent.js';
|
|
11
11
|
import { detectClients, buildServerEntry, renderInstruction, applyFileEntry, resolveMode, DEFAULT_SERVER_NAME, } from '../client-config.js';
|
|
12
|
-
// B7: the elicitation-driven account_add / account_reauth wizard. It rebuilds
|
|
13
|
-
// interactive account management on the mutable config.json registry so a
|
|
14
|
-
// running server can add an account without the deployer editing env + running
|
|
15
|
-
// a CLI. Google consent reuses the loopback flow (leg C); the HTTP-transport
|
|
16
|
-
// consent path (${BASE}/authorize) is owned by the OAuth AS and lands with that
|
|
17
|
-
// cluster. account_add over http is gated behind it.
|
|
18
|
-
const ALIAS_RE = /^[a-zA-Z0-9_-]+$/;
|
|
19
12
|
// The common optional bundles get their own checkbox (MCP elicitation has no
|
|
20
13
|
// multi-select, so a boolean per bundle IS the checklist); the long tail stays
|
|
21
14
|
// a comma-separated field for power users, and `allBundles` grants every one.
|
|
@@ -65,7 +58,7 @@ export function validateAddForm(input, existingAliases) {
|
|
|
65
58
|
if (b === 'admin') {
|
|
66
59
|
return { ok: false, slug: 'E_UNKNOWN_BUNDLE', message: '"admin" is not a scope bundle.', hint: 'Use the admin checkbox instead, or pass "admin": true.', alias };
|
|
67
60
|
}
|
|
68
|
-
if (!(b
|
|
61
|
+
if (!isKnownBundle(b)) {
|
|
69
62
|
const closest = closestBundle(b);
|
|
70
63
|
const known = Object.keys(BUNDLE_CATALOG).filter((n) => n !== 'admin').join(', ');
|
|
71
64
|
return {
|
|
@@ -143,6 +136,25 @@ function errorResult(slug, message, hint, account) {
|
|
|
143
136
|
isError: true,
|
|
144
137
|
};
|
|
145
138
|
}
|
|
139
|
+
let httpConsent = null;
|
|
140
|
+
export function setWizardHttpConsent(ctx) {
|
|
141
|
+
httpConsent = ctx;
|
|
142
|
+
}
|
|
143
|
+
/** Services register their tools at startup, so one first enabled by this
|
|
144
|
+
* account stays unregistered until a restart; say so rather than claim the
|
|
145
|
+
* whole account works without one. */
|
|
146
|
+
function restartNote(pending) {
|
|
147
|
+
if (pending.length === 0)
|
|
148
|
+
return '';
|
|
149
|
+
const names = pending.map((s) => `"${s}"`).join(', ');
|
|
150
|
+
const what = pending.length > 1 ? `services ${names} register their` : `service ${names} registers its`;
|
|
151
|
+
return `\nThe ${what} tools when the server starts: restart the server to use them. Until then, google_api_call can reach the same API.`;
|
|
152
|
+
}
|
|
153
|
+
function pendingText(alias, url, pending) {
|
|
154
|
+
return (`Account "${alias}" is ready to authorize. Open this link in a browser (it belongs to whoever owns the Google account):\n${url}\n` +
|
|
155
|
+
`On completion the token is stored server-side and the account is usable immediately — no restart. ` +
|
|
156
|
+
`If the link expires, run account_reauth for a fresh one.${restartNote(pending)}\n${TESTING_MODE_WARNING}`);
|
|
157
|
+
}
|
|
146
158
|
/** Run Google consent for `alias` and persist the token. Opens the browser via
|
|
147
159
|
* URL-mode elicitation when the client supports it, else server-side + prints
|
|
148
160
|
* the URL. Returns the granted-scope diff for the S4 report. */
|
|
@@ -157,6 +169,29 @@ async function runConsent(server, alias) {
|
|
|
157
169
|
hint: 'Accounts sourced from GOOGLE_ACCOUNTS are not editable here. Run `mcp-google-multi migrate-config` to move them into config.json.',
|
|
158
170
|
};
|
|
159
171
|
}
|
|
172
|
+
// HTTP transport: consent completes OUT OF BAND at the AS /callback. Never
|
|
173
|
+
// bind a loopback listener here; try URL-mode elicitation as a convenience
|
|
174
|
+
// but surface the URL UP FRONT either way (not only on failure).
|
|
175
|
+
const http = httpConsent;
|
|
176
|
+
if (http) {
|
|
177
|
+
let url;
|
|
178
|
+
try {
|
|
179
|
+
url = await http.mintConsentUrl(alias);
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return { ok: false, slug: 'internal', message: safeMessage(e) };
|
|
183
|
+
}
|
|
184
|
+
const caps = server.server.getClientCapabilities?.();
|
|
185
|
+
if (caps?.elicitation?.url !== undefined) {
|
|
186
|
+
try {
|
|
187
|
+
await server.server.elicitInput(urlElicitationParams(alias, url, randomBytes(16).toString('hex')));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// the URL in the pending text is the recovery
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { ok: 'pending', url };
|
|
194
|
+
}
|
|
160
195
|
// Bind the ephemeral loopback listener BEFORE building the auth URL: the
|
|
161
196
|
// redirect URI needs the assigned port, and listening first means the
|
|
162
197
|
// callback can't race the browser.
|
|
@@ -211,14 +246,14 @@ async function runConsent(server, alias) {
|
|
|
211
246
|
writeToken(alias, tokens);
|
|
212
247
|
return { ok: true, missing: scopeGrantDiff(scopes, typeof tokens.scope === 'string' ? tokens.scope : undefined) };
|
|
213
248
|
}
|
|
214
|
-
function s4Text(alias, missing) {
|
|
249
|
+
function s4Text(alias, missing, pending) {
|
|
215
250
|
const outcome = missing.length === 0
|
|
216
251
|
? `✔ "${alias}" authenticated; all requested scopes granted. It is now usable without a restart.`
|
|
217
252
|
: `⚠ "${alias}" authenticated, but ${missing.length} requested scope(s) were NOT granted (E_SCOPE_NOT_GRANTED). You may have unchecked some on the consent screen. Re-run account_reauth to grant them. The account is usable for the granted scopes.`;
|
|
218
|
-
return `${outcome}\n${TESTING_MODE_WARNING}`;
|
|
253
|
+
return `${outcome}${restartNote(pending)}\n${TESTING_MODE_WARNING}`;
|
|
219
254
|
}
|
|
220
255
|
const REQUIRES_INTERACTION = { 'anthropic/requiresUserInteraction': true };
|
|
221
|
-
export function registerAccountWizardTools(registry, server) {
|
|
256
|
+
export function registerAccountWizardTools(registry, server, awaitingRestart = () => []) {
|
|
222
257
|
// Registered as META (always-visible, like account_list) so onboarding tools
|
|
223
258
|
// are reachable in lazy mode without discover_all first; registerMeta still
|
|
224
259
|
// preserves the requiresUserInteraction clientMeta and skips the fan-out path.
|
|
@@ -226,7 +261,7 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
226
261
|
registerMeta('account_add', {
|
|
227
262
|
_meta: REQUIRES_INTERACTION,
|
|
228
263
|
annotations: { openWorldHint: true },
|
|
229
|
-
description: 'Add a new Google account: pass alias + email directly (plus optional bundles/allBundles/admin), or pass nothing for an interactive form where the client supports elicitation. Writes the registry and runs Google consent in the browser. No file editing or restart needed. Requires GOOGLE_CLIENT_ID/SECRET (run the `setup` prompt first if missing).',
|
|
264
|
+
description: 'Add a new Google account: pass alias + email directly (plus optional bundles/allBundles/admin), or pass nothing for an interactive form where the client supports elicitation. Writes the registry and runs Google consent in the browser. No file editing or restart needed to use the account (tools of a service it newly enables register at the next restart). Requires GOOGLE_CLIENT_ID/SECRET (run the `setup` prompt first if missing).',
|
|
230
265
|
inputSchema: {
|
|
231
266
|
alias: z.string().optional().describe('Account alias (letters, digits, _ or -). Pass with email to add directly, skipping the form.'),
|
|
232
267
|
account: z.string().optional().describe('Alias for the new account (same as `alias`; every other tool spells it `account`)'),
|
|
@@ -272,12 +307,23 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
272
307
|
// S2: atomic write + make the alias callable without a restart (BR3).
|
|
273
308
|
writeAccountRow(validated.alias, validated.email, validated.bundles, validated.admin);
|
|
274
309
|
invalidateAccountSet();
|
|
310
|
+
// Live account args validate against the refreshed registry already;
|
|
311
|
+
// the list_changed nudge makes clients re-fetch tools/list, where the
|
|
312
|
+
// advertised account enums are rebuilt from the live set.
|
|
313
|
+
try {
|
|
314
|
+
server.sendToolListChanged();
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// a client that cannot receive notifications loses nothing but the nudge
|
|
318
|
+
}
|
|
275
319
|
// S3 + S4: consent + validate.
|
|
276
320
|
const consent = await runConsent(server, validated.alias);
|
|
321
|
+
if (consent.ok === 'pending')
|
|
322
|
+
return textResult(pendingText(validated.alias, consent.url, awaitingRestart()));
|
|
277
323
|
if (!consent.ok)
|
|
278
324
|
return errorResult(consent.slug, consent.message, consent.hint, validated.alias);
|
|
279
325
|
// B15: offer to register the server with another MCP client.
|
|
280
|
-
return textResult(`${s4Text(validated.alias, consent.missing)}\nTip: run account_write_config to register this server with another MCP client (Claude Desktop / Cursor / Claude Code).`);
|
|
326
|
+
return textResult(`${s4Text(validated.alias, consent.missing, awaitingRestart())}\nTip: run account_write_config to register this server with another MCP client (Claude Desktop / Cursor / Claude Code).`);
|
|
281
327
|
}
|
|
282
328
|
catch (e) {
|
|
283
329
|
// safeMessage, not error.message: an arbitrary throw here can carry a
|
|
@@ -303,6 +349,9 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
303
349
|
try {
|
|
304
350
|
const a = args;
|
|
305
351
|
const alias = a.alias ?? a.account ?? '';
|
|
352
|
+
// No default-account wrapper runs for this tool, so pick up a
|
|
353
|
+
// hand-edited config.json here: that edit is why a re-auth is asked for.
|
|
354
|
+
refreshAccountSetIfStale();
|
|
306
355
|
if (!hasClientCredentials()) {
|
|
307
356
|
return errorResult('invalid_client', 'GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are not set, so there is no OAuth client to run consent against.', 'Run the `setup` prompt (/mcp__google-multi__setup) to create an OAuth client, then set them. See docs/google-cloud-setup.md.');
|
|
308
357
|
}
|
|
@@ -310,9 +359,11 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
310
359
|
return errorResult('validation_error', `Unknown account "${alias}".`, `Pass one of the configured aliases: ${getAccountSet().aliases.join(', ')}. Use account_add to create a new one.`, alias);
|
|
311
360
|
}
|
|
312
361
|
const consent = await runConsent(server, alias);
|
|
362
|
+
if (consent.ok === 'pending')
|
|
363
|
+
return textResult(pendingText(alias, consent.url, awaitingRestart()));
|
|
313
364
|
if (!consent.ok)
|
|
314
365
|
return errorResult(consent.slug, consent.message, consent.hint, alias);
|
|
315
|
-
return textResult(s4Text(alias, consent.missing));
|
|
366
|
+
return textResult(s4Text(alias, consent.missing, awaitingRestart()));
|
|
316
367
|
}
|
|
317
368
|
catch (e) {
|
|
318
369
|
return errorResult('internal', `account_reauth failed: ${safeMessage(e)}`);
|
|
@@ -360,6 +411,12 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
360
411
|
if (client.managed === 'cli') {
|
|
361
412
|
blocks.push(`${client.label}: run\n ${instr.text}`);
|
|
362
413
|
}
|
|
414
|
+
else if (a.write && mode === 'http') {
|
|
415
|
+
// Over HTTP this tool runs on the DAEMON, whose homedir is not the
|
|
416
|
+
// caller's machine: write:true would edit the operator's own client
|
|
417
|
+
// configs (a cross-tenant hazard under tenancy). Snippet only.
|
|
418
|
+
blocks.push(`${client.label}: write:true is ignored over HTTP — this tool runs on the server, not your machine. Add to ${instr.path}\n${instr.text}`);
|
|
419
|
+
}
|
|
363
420
|
else if (a.write) {
|
|
364
421
|
const res = applyFileEntry(client, name, entry);
|
|
365
422
|
blocks.push(res.ok
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
import { type AccountSet } from '../accounts.js';
|
|
2
3
|
import { type ScopesReport } from '../scope-observability.js';
|
|
4
|
+
import { type TokenStore } from '../token-store.js';
|
|
3
5
|
export interface AccountHealthDeps {
|
|
4
6
|
hasToken: (alias: string) => boolean;
|
|
5
7
|
readToken: (alias: string) => {
|
|
@@ -13,7 +15,12 @@ export interface AccountHealthDeps {
|
|
|
13
15
|
* "registered but not requestable" view (B9). account_list stays compact:
|
|
14
16
|
* its report universe is profile ∪ granted only. */
|
|
15
17
|
registeredScopes?: () => string[];
|
|
18
|
+
/** Account view health is derived against; absent = the global registry. */
|
|
19
|
+
accounts?: () => AccountSet;
|
|
16
20
|
}
|
|
21
|
+
/** Health deps over one context's token store (and, optionally, its account
|
|
22
|
+
* view); the module-level functions are the single owner's store. */
|
|
23
|
+
export declare function accountHealthDepsFor(store: Pick<TokenStore, 'hasToken' | 'readToken'>, accounts?: () => AccountSet): AccountHealthDeps;
|
|
17
24
|
export type TokenStatus = 'ok' | 'expired_refreshable' | 'needs_reauth' | 'missing' | 'decrypt_error';
|
|
18
25
|
export interface AccountHealth {
|
|
19
26
|
alias: string;
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
+
import { reauthHint } from '../reauth-hint.js';
|
|
1
2
|
import * as fs from 'node:fs';
|
|
2
3
|
import { getAccountSet, refreshAccountSetIfStale } from '../accounts.js';
|
|
3
4
|
import { buildScopesReport } from '../scope-observability.js';
|
|
4
5
|
import { getAdminAccounts, resolveScopesForAccount } from '../auth.js';
|
|
5
6
|
import { hasToken, readToken } from '../token-store.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import { hints } from '../hint-provider.js';
|
|
8
|
+
/** Health deps over one context's token store (and, optionally, its account
|
|
9
|
+
* view); the module-level functions are the single owner's store. */
|
|
10
|
+
export function accountHealthDepsFor(store, accounts) {
|
|
11
|
+
return {
|
|
12
|
+
hasToken: store.hasToken,
|
|
13
|
+
readToken: store.readToken,
|
|
14
|
+
fileExists: fs.existsSync,
|
|
15
|
+
now: Date.now,
|
|
16
|
+
...(accounts ? { accounts } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_DEPS = accountHealthDepsFor({ hasToken, readToken });
|
|
12
20
|
export function deriveAccountHealth(alias, deps = DEFAULT_DEPS) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
21
|
+
const set = (deps.accounts ?? getAccountSet)();
|
|
22
|
+
const config = set.configs[alias];
|
|
23
|
+
const admin = getAdminAccounts(set).includes(alias);
|
|
24
|
+
const configured = resolveScopesForAccount(alias, set);
|
|
16
25
|
// BR-10: env-sourced accounts are not persisted in config.json, so the
|
|
17
26
|
// account wizard cannot edit them — the deployer edits env instead.
|
|
18
27
|
const base = {
|
|
@@ -29,13 +38,17 @@ export function deriveAccountHealth(alias, deps = DEFAULT_DEPS) {
|
|
|
29
38
|
const noScopes = buildScopesReport(new Set(), profileSet, registered);
|
|
30
39
|
if (!deps.hasToken(alias)) {
|
|
31
40
|
const legacy = deps.fileExists(config.tokenPath);
|
|
41
|
+
const rendered = legacy
|
|
42
|
+
? hints()?.render({ kind: 'legacy_plaintext_token', account: alias })
|
|
43
|
+
: hints()?.render({ kind: 'reauth', account: alias });
|
|
32
44
|
return {
|
|
33
45
|
...base,
|
|
34
46
|
token: {
|
|
35
47
|
status: 'missing',
|
|
36
|
-
hint:
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
hint: rendered?.hint ??
|
|
49
|
+
(legacy
|
|
50
|
+
? 'Plaintext token.json found — run: npx mcp-google-multi migrate-tokens'
|
|
51
|
+
: reauthHint(alias)),
|
|
39
52
|
},
|
|
40
53
|
scopes: noScopes,
|
|
41
54
|
};
|
|
@@ -45,9 +58,10 @@ export function deriveAccountHealth(alias, deps = DEFAULT_DEPS) {
|
|
|
45
58
|
token = deps.readToken(alias);
|
|
46
59
|
}
|
|
47
60
|
catch {
|
|
61
|
+
const rendered = hints()?.render({ kind: 'token_unreadable', account: alias });
|
|
48
62
|
return {
|
|
49
63
|
...base,
|
|
50
|
-
token: { status: 'decrypt_error', hint: 'Wrong MASTER_KEY or corrupt token file — re-auth or restore the key.' },
|
|
64
|
+
token: { status: 'decrypt_error', hint: rendered?.hint ?? 'Wrong MASTER_KEY or corrupt token file — re-auth or restore the key.' },
|
|
51
65
|
scopes: noScopes,
|
|
52
66
|
};
|
|
53
67
|
}
|
|
@@ -69,9 +83,9 @@ export function deriveAccountHealth(alias, deps = DEFAULT_DEPS) {
|
|
|
69
83
|
status,
|
|
70
84
|
expiryDate: expiry !== undefined ? new Date(expiry).toISOString() : undefined,
|
|
71
85
|
hint: status === 'needs_reauth'
|
|
72
|
-
?
|
|
86
|
+
? (hints()?.render({ kind: 'reauth', account: alias })?.hint ?? reauthHint(alias))
|
|
73
87
|
: missing.length > 0
|
|
74
|
-
? 'Re-auth to grant the missing scopes'
|
|
88
|
+
? (hints()?.render({ kind: 'scopes_missing', account: alias })?.hint ?? 'Re-auth to grant the missing scopes')
|
|
75
89
|
: undefined,
|
|
76
90
|
},
|
|
77
91
|
scopes: report,
|
|
@@ -87,14 +101,16 @@ export function registerAccountTools(registry, deps = DEFAULT_DEPS) {
|
|
|
87
101
|
_meta: { 'anthropic/alwaysLoad': true },
|
|
88
102
|
}, async () => {
|
|
89
103
|
refreshAccountSetIfStale();
|
|
104
|
+
const set = registry.accountSet();
|
|
105
|
+
const health = { ...deps, accounts: () => set };
|
|
90
106
|
return {
|
|
91
107
|
content: [
|
|
92
108
|
{
|
|
93
109
|
type: 'text',
|
|
94
110
|
text: JSON.stringify({
|
|
95
|
-
defaultAccount:
|
|
96
|
-
defaultAccountSource:
|
|
97
|
-
accounts:
|
|
111
|
+
defaultAccount: set.defaultAccount ?? null,
|
|
112
|
+
defaultAccountSource: set.defaultAccountSource ?? null,
|
|
113
|
+
accounts: set.aliases.map((alias) => deriveAccountHealth(alias, health)),
|
|
98
114
|
}),
|
|
99
115
|
},
|
|
100
116
|
],
|
package/dist/tools/admin.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { ToolRegistry } from '../registry.js';
|
|
2
|
-
|
|
2
|
+
import type { Account } from '../accounts.js';
|
|
3
|
+
import { type CuratedToolDeps } from '../client.js';
|
|
4
|
+
export declare function registerAdminTools(server: ToolRegistry, deps?: CuratedToolDeps): void;
|
|
5
|
+
export declare function handleAdminError(error: any, account: Account): {
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
isError: true;
|
|
11
|
+
};
|
package/dist/tools/admin.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { coerceBoolean } from './_coerce.js';
|
|
3
3
|
import { admin as adminClient } from '@googleapis/admin';
|
|
4
|
-
import {
|
|
4
|
+
import { accountArgLive } from '../accounts.js';
|
|
5
5
|
import { getClient } from '../client.js';
|
|
6
6
|
import { handleGoogleApiError, invalidParams } from './_errors.js';
|
|
7
|
-
const accountEnum = accountAliasSchema.optional();
|
|
8
7
|
// Admin SDK requires Workspace super-admin (or delegated admin) on the account — personal @gmail.com accounts 403 on every endpoint.
|
|
9
|
-
export function registerAdminTools(server) {
|
|
8
|
+
export function registerAdminTools(server, deps = {}) {
|
|
9
|
+
// Per-registry, LIVE account enum + injectable client (S1.10): the
|
|
10
|
+
// schema follows the registry's account view at parse time, and the
|
|
11
|
+
// custody path is the context's, not the process global.
|
|
12
|
+
const accountEnum = accountArgLive(() => server.accountAliases()).optional();
|
|
13
|
+
const getClientFn = deps.getClientFn ?? getClient;
|
|
10
14
|
// ─── Reports / audit log ───────────────────────────────────────────────
|
|
11
15
|
server.registerTool('reports_activities_list', {
|
|
12
16
|
description: 'List Admin Activity audit log entries. Filter by application (login, drive, gmail, admin, token, etc.), user, date range, event. The single most useful admin endpoint for SMB visibility.',
|
|
@@ -34,7 +38,7 @@ export function registerAdminTools(server) {
|
|
|
34
38
|
},
|
|
35
39
|
}, async ({ account, applicationName, userKey, startTime, endTime, eventName, actorIpAddress, filters, orgUnitID, groupIdFilter, customerId, maxResults, pageToken }) => {
|
|
36
40
|
try {
|
|
37
|
-
const auth = await
|
|
41
|
+
const auth = await getClientFn(account);
|
|
38
42
|
const reports = adminClient({ version: 'reports_v1', auth });
|
|
39
43
|
const res = await reports.activities.list({
|
|
40
44
|
applicationName,
|
|
@@ -74,7 +78,7 @@ export function registerAdminTools(server) {
|
|
|
74
78
|
},
|
|
75
79
|
}, async ({ account, customer, domain, query, maxResults, pageToken, orderBy, showDeleted, projection }) => {
|
|
76
80
|
try {
|
|
77
|
-
const auth = await
|
|
81
|
+
const auth = await getClientFn(account);
|
|
78
82
|
const directory = adminClient({ version: 'directory_v1', auth });
|
|
79
83
|
const res = await directory.users.list({
|
|
80
84
|
customer: customer ?? 'my_customer',
|
|
@@ -103,7 +107,7 @@ export function registerAdminTools(server) {
|
|
|
103
107
|
},
|
|
104
108
|
}, async ({ account, userKey, projection }) => {
|
|
105
109
|
try {
|
|
106
|
-
const auth = await
|
|
110
|
+
const auth = await getClientFn(account);
|
|
107
111
|
const directory = adminClient({ version: 'directory_v1', auth });
|
|
108
112
|
const res = await directory.users.get({
|
|
109
113
|
userKey,
|
|
@@ -131,7 +135,7 @@ export function registerAdminTools(server) {
|
|
|
131
135
|
},
|
|
132
136
|
}, async ({ account, userKey, givenName, familyName, suspended, password, changePasswordAtNextLogin, orgUnitPath }) => {
|
|
133
137
|
try {
|
|
134
|
-
const auth = await
|
|
138
|
+
const auth = await getClientFn(account);
|
|
135
139
|
const directory = adminClient({ version: 'directory_v1', auth });
|
|
136
140
|
const requestBody = {};
|
|
137
141
|
if (givenName !== undefined || familyName !== undefined) {
|
|
@@ -175,7 +179,7 @@ export function registerAdminTools(server) {
|
|
|
175
179
|
},
|
|
176
180
|
}, async ({ account, customer, domain, userKey, query, maxResults, pageToken }) => {
|
|
177
181
|
try {
|
|
178
|
-
const auth = await
|
|
182
|
+
const auth = await getClientFn(account);
|
|
179
183
|
const directory = adminClient({ version: 'directory_v1', auth });
|
|
180
184
|
const res = await directory.groups.list({
|
|
181
185
|
customer: customer ?? 'my_customer',
|
|
@@ -205,7 +209,7 @@ export function registerAdminTools(server) {
|
|
|
205
209
|
},
|
|
206
210
|
}, async ({ account, groupKey, roles, includeDerivedMembership, maxResults, pageToken }) => {
|
|
207
211
|
try {
|
|
208
|
-
const auth = await
|
|
212
|
+
const auth = await getClientFn(account);
|
|
209
213
|
const directory = adminClient({ version: 'directory_v1', auth });
|
|
210
214
|
const res = await directory.members.list({
|
|
211
215
|
groupKey,
|
|
@@ -223,7 +227,7 @@ export function registerAdminTools(server) {
|
|
|
223
227
|
}
|
|
224
228
|
});
|
|
225
229
|
}
|
|
226
|
-
function handleAdminError(error, account) {
|
|
230
|
+
export function handleAdminError(error, account) {
|
|
227
231
|
const admin = 'Admin tools require Workspace super-admin privileges AND the account must be listed as an admin account (then re-authenticated). Personal Gmail accounts cannot use these endpoints.';
|
|
228
|
-
return handleGoogleApiError(error, account, { scope: admin, resource: admin });
|
|
232
|
+
return handleGoogleApiError(error, account, { scope: admin, resource: admin, bundle: 'admin' });
|
|
229
233
|
}
|