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.
- package/README.md +5 -5
- package/dist/accounts.d.ts +20 -4
- package/dist/accounts.js +68 -44
- package/dist/api-probe.d.ts +5 -0
- package/dist/api-probe.js +20 -15
- package/dist/arg-normalize.js +19 -38
- package/dist/arg-strict.d.ts +7 -0
- package/dist/arg-strict.js +38 -28
- package/dist/auth.d.ts +5 -4
- package/dist/auth.js +16 -23
- package/dist/boot-gates.d.ts +26 -0
- package/dist/boot-gates.js +36 -0
- package/dist/cimd-cache.d.ts +38 -0
- package/dist/cimd-cache.js +111 -0
- package/dist/client-config.d.ts +2 -2
- package/dist/client-config.js +8 -14
- package/dist/client.d.ts +32 -2
- package/dist/client.js +0 -0
- package/dist/compose.d.ts +18 -0
- package/dist/compose.js +109 -0
- package/dist/config-file.d.ts +13 -0
- package/dist/config-file.js +41 -10
- package/dist/discover.js +7 -10
- package/dist/discovery-client.d.ts +3 -0
- package/dist/discovery-client.js +36 -32
- package/dist/doctor.d.ts +9 -5
- package/dist/doctor.js +119 -29
- package/dist/env-load.js +2 -5
- package/dist/executor.d.ts +3 -0
- package/dist/executor.js +6 -6
- package/dist/fanout.d.ts +9 -1
- package/dist/fanout.js +47 -22
- package/dist/fs-atomic.d.ts +8 -0
- package/dist/fs-atomic.js +216 -29
- package/dist/hint-provider.d.ts +129 -0
- package/dist/hint-provider.js +58 -0
- package/dist/http-config.d.ts +2 -2
- package/dist/http-config.js +3 -7
- package/dist/http-transport.d.ts +67 -9
- package/dist/http-transport.js +318 -52
- package/dist/identity.d.ts +6 -3
- package/dist/identity.js +24 -8
- package/dist/index.js +76 -160
- package/dist/master-key.d.ts +3 -3
- package/dist/master-key.js +16 -19
- package/dist/mcp-token.d.ts +126 -19
- package/dist/mcp-token.js +557 -76
- package/dist/metrics-cli.d.ts +1 -1
- package/dist/metrics-cli.js +1 -1
- package/dist/metrics-tap.js +2 -5
- package/dist/migrate-config.d.ts +1 -2
- package/dist/migrate-config.js +5 -7
- package/dist/net-tuning.js +2 -4
- package/dist/oauth-as.d.ts +104 -4
- package/dist/oauth-as.js +368 -114
- package/dist/oauth-consent.d.ts +10 -0
- package/dist/oauth-consent.js +20 -10
- package/dist/outbound-allowlist.js +9 -11
- package/dist/reauth-hint.d.ts +1 -1
- package/dist/reauth-hint.js +11 -10
- package/dist/registry-transfer.d.ts +5 -0
- package/dist/registry-transfer.js +63 -11
- package/dist/registry.d.ts +13 -4
- package/dist/registry.js +100 -72
- package/dist/scope-catalog.d.ts +16 -0
- package/dist/scope-catalog.js +40 -13
- package/dist/scope-observability.d.ts +10 -9
- package/dist/scope-observability.js +34 -24
- package/dist/services.d.ts +18 -10
- package/dist/services.js +61 -22
- package/dist/ssrf-guard.d.ts +23 -9
- package/dist/ssrf-guard.js +106 -78
- package/dist/tenant-purge.d.ts +26 -0
- package/dist/tenant-purge.js +65 -0
- package/dist/token-store.d.ts +20 -2
- package/dist/token-store.js +0 -0
- package/dist/tools/_coerce.d.ts +4 -4
- package/dist/tools/_errors.d.ts +3 -0
- package/dist/tools/_errors.js +72 -60
- package/dist/tools/_local-files.d.ts +9 -0
- package/dist/tools/_local-files.js +22 -8
- package/dist/tools/account-wizard.d.ts +13 -3
- package/dist/tools/account-wizard.js +107 -56
- package/dist/tools/accounts-tool.d.ts +8 -1
- package/dist/tools/accounts-tool.js +35 -19
- package/dist/tools/admin.d.ts +10 -1
- package/dist/tools/admin.js +14 -11
- package/dist/tools/analytics.d.ts +10 -1
- package/dist/tools/analytics.js +14 -10
- package/dist/tools/calendar.d.ts +2 -1
- package/dist/tools/calendar.js +19 -18
- package/dist/tools/chat.d.ts +10 -1
- package/dist/tools/chat.js +12 -8
- package/dist/tools/contacts.d.ts +2 -1
- package/dist/tools/contacts.js +22 -19
- package/dist/tools/docs.d.ts +2 -1
- package/dist/tools/docs.js +35 -36
- package/dist/tools/drive.d.ts +2 -1
- package/dist/tools/drive.js +80 -74
- package/dist/tools/forms.d.ts +10 -1
- package/dist/tools/forms.js +18 -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-mime.d.ts +1 -1
- package/dist/tools/gmail-mime.js +7 -12
- package/dist/tools/gmail.d.ts +4 -3
- package/dist/tools/gmail.js +67 -69
- package/dist/tools/google-api.d.ts +2 -3
- package/dist/tools/google-api.js +34 -22
- package/dist/tools/meet.d.ts +10 -1
- package/dist/tools/meet.js +12 -9
- package/dist/tools/searchconsole.d.ts +2 -1
- package/dist/tools/searchconsole.js +16 -13
- package/dist/tools/sheets.d.ts +2 -1
- package/dist/tools/sheets.js +35 -32
- package/dist/tools/slides.d.ts +10 -1
- package/dist/tools/slides.js +16 -10
- package/dist/tools/tasks.d.ts +2 -1
- package/dist/tools/tasks.js +18 -15
- package/dist/trim.d.ts +5 -0
- package/dist/trim.js +19 -0
- package/dist/usage-metrics.d.ts +23 -0
- package/dist/usage-metrics.js +67 -56
- package/dist/write-control.d.ts +4 -8
- package/dist/write-control.js +13 -6
- package/package.json +47 -21
package/dist/usage-metrics.d.ts
CHANGED
|
@@ -86,6 +86,26 @@ export interface InitOptions {
|
|
|
86
86
|
now?: () => number;
|
|
87
87
|
monotonic?: () => number;
|
|
88
88
|
log?: (line: string) => void;
|
|
89
|
+
/** Default true. Three chains correlate CONSECUTIVE calls, which is only
|
|
90
|
+
* meaningful when one caller owns the whole process: the retry and bigram
|
|
91
|
+
* chains, and pendingMid (the escape hatch's resolved methodId, attached to
|
|
92
|
+
* the next google_api_call event's `mid`). A host serving several callers
|
|
93
|
+
* sets false so none of the three ever cross-link a call between tenants.
|
|
94
|
+
* DayAgg shape is unchanged (retries/bigrams stay empty maps);
|
|
95
|
+
* escape.methods still counts every resolved call either way. */
|
|
96
|
+
sequencingEnabled?: boolean;
|
|
97
|
+
/** Default true. When false, no events.jsonl / events.1.jsonl is ever
|
|
98
|
+
* created or appended to; only agg/ exists. A host serving several callers
|
|
99
|
+
* sets false: each event line carries a minute timestamp, the boot id and a
|
|
100
|
+
* per-process sequence number, which can be joined against a per-caller log
|
|
101
|
+
* such as an audit trail; the day aggregate keeps the same counters without
|
|
102
|
+
* order. */
|
|
103
|
+
eventLog?: boolean;
|
|
104
|
+
/** Default none. A host's own closed slug set, unioned with
|
|
105
|
+
* KNOWN_ERROR_SLUGS for this instance only; each entry must match
|
|
106
|
+
* /^[A-Za-z][A-Za-z0-9_]{0,63}$/ or it is dropped silently. Absent,
|
|
107
|
+
* behavior is byte-identical to today. */
|
|
108
|
+
extraErrorSlugs?: readonly string[];
|
|
89
109
|
}
|
|
90
110
|
/** Deep-add b into a for the DayAgg shape (numbers add, maps union). */
|
|
91
111
|
export declare function mergeDay(a: DayAgg, b: DayAgg): DayAgg;
|
|
@@ -108,6 +128,9 @@ export declare class Metrics {
|
|
|
108
128
|
private dirty;
|
|
109
129
|
private failures;
|
|
110
130
|
private disabled;
|
|
131
|
+
private readonly sequencing;
|
|
132
|
+
private readonly eventLog;
|
|
133
|
+
private readonly errorSlugs;
|
|
111
134
|
constructor(opts: InitOptions);
|
|
112
135
|
private day;
|
|
113
136
|
private initStorage;
|
package/dist/usage-metrics.js
CHANGED
|
@@ -4,14 +4,8 @@ import * as os from 'node:os';
|
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { performance } from 'node:perf_hooks';
|
|
6
6
|
import { withFileLock, atomicWriteFileSync } from './fs-atomic.js';
|
|
7
|
-
// Local usage metrics
|
|
8
|
-
//
|
|
9
|
-
// Zero network egress is structural: this module's import set is a tested
|
|
10
|
-
// allowlist (fs/path/os/crypto/perf_hooks/fs-atomic), so egress code cannot
|
|
11
|
-
// even be expressed here. Every string persisted is a member of a server-owned
|
|
12
|
-
// closed vocabulary, matches a pinned shape regex, or is other/unknown_*/
|
|
13
|
-
// _overflow; free text is unrepresentable in the file format. No identity
|
|
14
|
-
// dimension of any kind: no aliases, hashes, emails, or exact account counts.
|
|
7
|
+
// Local usage metrics, default OFF; zero network egress is structural (a
|
|
8
|
+
// tested import allowlist). See docs/usage-metrics.md.
|
|
15
9
|
export const METRICS_DIR_NAME = 'metrics';
|
|
16
10
|
const FLUSH_MS = 60_000;
|
|
17
11
|
const RETRY_WINDOW_MS = 10 * 60_000;
|
|
@@ -26,17 +20,18 @@ const WRITE_FAILURE_LIMIT = 3;
|
|
|
26
20
|
const LAT_BOUNDS = [100, 250, 500, 1000, 2500, 5000, 10000, 30000];
|
|
27
21
|
const CHARS_BOUNDS = [64, 256, 1024, 4096, 16384, 65536];
|
|
28
22
|
// methodId backstop shape gate: even a tampered discovery cache cannot turn
|
|
29
|
-
// arbitrary strings into recorded values
|
|
23
|
+
// arbitrary strings into recorded values.
|
|
30
24
|
const METHOD_ID_RE = /^[a-z][a-zA-Z0-9]*(\.[a-zA-Z0-9]+)+$/;
|
|
31
25
|
const METHOD_ID_MAX = 128;
|
|
32
26
|
const TOOL_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
33
27
|
// Declared argument keys are camelCase identifiers, some dotted
|
|
34
28
|
// (groupKey.id, debugOptions.enableDebugging).
|
|
35
29
|
const ARG_KEY_RE = /^[A-Za-z][A-Za-z0-9_.]{0,63}$/;
|
|
30
|
+
const EXTRA_ERROR_SLUG_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
36
31
|
/** Union of every `error:` slug literal emitted anywhere in src/ (kept honest
|
|
37
32
|
* by a set-equality grep test). Anything outside buckets to `other`. */
|
|
38
33
|
export const KNOWN_ERROR_SLUGS = new Set([
|
|
39
|
-
'E_ALIAS_EXISTS', 'E_CIMD_INVALID', 'E_ENV_ACCOUNTS_MODE', 'E_MCP_TOKEN_INVALID',
|
|
34
|
+
'E_ALIAS_EXISTS', 'E_CIMD_INVALID', 'E_CIMD_SSRF_BLOCKED', 'E_ENV_ACCOUNTS_MODE', 'E_MCP_TOKEN_INVALID',
|
|
40
35
|
'E_NO_DEFAULT_ACCOUNT', 'E_UNKNOWN_BUNDLE', 'E_VALIDATION', 'ambiguous',
|
|
41
36
|
'api_not_enabled', 'auth_required', 'bad_request', 'binary', 'binary_unsupported',
|
|
42
37
|
'confirmation_declined', 'diagnose_failed', 'discovery_unavailable', 'dispatch_timeout',
|
|
@@ -44,7 +39,7 @@ export const KNOWN_ERROR_SLUGS = new Set([
|
|
|
44
39
|
'insufficient_scope', 'internal', 'invalid_client', 'invalid_client_metadata',
|
|
45
40
|
'invalid_grant', 'invalid_params', 'invalid_query', 'invalid_request',
|
|
46
41
|
'invalid_scope', 'network_error', 'not_found', 'rate_limited',
|
|
47
|
-
'reauth_required', 'recipient_not_allowed', 'too_large', 'toolset_disabled', 'unknown_api',
|
|
42
|
+
'reauth_required', 'recipient_not_allowed', 'temporarily_unavailable', 'tenant_not_found', 'too_large', 'toolset_disabled', 'unknown_api',
|
|
48
43
|
'unknown_argument', 'unknown_method', 'unsupported_grant_type', 'unsupported_type',
|
|
49
44
|
'untrusted_host', 'upstream_error', 'validation_error', 'write_disabled',
|
|
50
45
|
]);
|
|
@@ -221,7 +216,14 @@ export class Metrics {
|
|
|
221
216
|
dirty = false;
|
|
222
217
|
failures = 0;
|
|
223
218
|
disabled = false;
|
|
219
|
+
sequencing;
|
|
220
|
+
eventLog;
|
|
221
|
+
errorSlugs;
|
|
224
222
|
constructor(opts) {
|
|
223
|
+
this.sequencing = opts.sequencingEnabled !== false;
|
|
224
|
+
this.eventLog = opts.eventLog !== false;
|
|
225
|
+
const extras = (opts.extraErrorSlugs ?? []).filter((s) => EXTRA_ERROR_SLUG_RE.test(s));
|
|
226
|
+
this.errorSlugs = extras.length ? new Set([...KNOWN_ERROR_SLUGS, ...extras]) : KNOWN_ERROR_SLUGS;
|
|
225
227
|
this.dir = opts.dir ?? path.join(stateDir(opts.env), METRICS_DIR_NAME);
|
|
226
228
|
this.aggDir = path.join(this.dir, 'agg');
|
|
227
229
|
this.eventsFile = path.join(this.dir, 'events.jsonl');
|
|
@@ -250,7 +252,8 @@ export class Metrics {
|
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
this.pruneAggs();
|
|
253
|
-
|
|
255
|
+
if (this.eventLog)
|
|
256
|
+
this.pruneEventAge(this.eventsFile);
|
|
254
257
|
}
|
|
255
258
|
catch (e) {
|
|
256
259
|
this.log(`local usage metrics: init failed (${e.message}); disabled for this process\n`);
|
|
@@ -318,7 +321,7 @@ export class Metrics {
|
|
|
318
321
|
if (typeof first === 'string' && first.length < 65_536) {
|
|
319
322
|
const env = JSON.parse(first);
|
|
320
323
|
if (typeof env.error === 'string')
|
|
321
|
-
slug =
|
|
324
|
+
slug = this.errorSlugs.has(env.error) ? env.error : 'other';
|
|
322
325
|
hinted = typeof env.hint === 'string' && env.hint.length > 0;
|
|
323
326
|
}
|
|
324
327
|
}
|
|
@@ -357,45 +360,50 @@ export class Metrics {
|
|
|
357
360
|
f.calls += 1;
|
|
358
361
|
addInto(f, fan);
|
|
359
362
|
}
|
|
360
|
-
// retry self-correction chain
|
|
363
|
+
// retry self-correction chain, in-memory only; links consecutive calls,
|
|
364
|
+
// so it only runs when one caller owns the process (sequencingEnabled).
|
|
361
365
|
const nowM = this.mono();
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
if (this.sequencing) {
|
|
367
|
+
const prev = this.lastError.get(entry.name);
|
|
368
|
+
if (prev && nowM - prev.at <= RETRY_WINDOW_MS) {
|
|
369
|
+
const rr = (this.deltas.retries[prev.slug] ??= { hintedOk: 0, hintedFail: 0, unhintedOk: 0, unhintedFail: 0 });
|
|
370
|
+
const key = `${prev.hinted ? 'hinted' : 'unhinted'}${ok ? 'Ok' : 'Fail'}`;
|
|
371
|
+
rr[key] += 1;
|
|
372
|
+
this.lastError.delete(entry.name);
|
|
373
|
+
}
|
|
374
|
+
if (!ok && slug)
|
|
375
|
+
this.lastError.set(entry.name, { slug, hinted: hinted === true, at: nowM });
|
|
376
|
+
// bigrams: ordered pairs within the process, 5-minute gap breaks the chain.
|
|
377
|
+
if (this.lastTool && nowM - this.lastTool.at <= BIGRAM_GAP_MS) {
|
|
378
|
+
addInto(this.deltas.bigrams, `${this.lastTool.name}>${entry.name}`);
|
|
379
|
+
}
|
|
380
|
+
this.lastTool = { name: entry.name, at: nowM };
|
|
374
381
|
}
|
|
375
|
-
this.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
382
|
+
if (this.eventLog) {
|
|
383
|
+
const event = {
|
|
384
|
+
ts: new Date(this.now()).toISOString().slice(0, 16) + ':00Z',
|
|
385
|
+
boot: this.bootId,
|
|
386
|
+
seq: this.seq++,
|
|
387
|
+
tool: entry.name,
|
|
388
|
+
ok,
|
|
389
|
+
ms: Math.round(ms),
|
|
390
|
+
chars: charsBucket(chars),
|
|
391
|
+
src: 'handler',
|
|
392
|
+
};
|
|
393
|
+
if (slug)
|
|
394
|
+
event.err = slug;
|
|
395
|
+
if (hinted !== undefined)
|
|
396
|
+
event.hint = hinted;
|
|
397
|
+
if (fan)
|
|
398
|
+
event.fan = fan;
|
|
399
|
+
// mid: set mid-dispatch by recordEscapeMethod, attached to the escape
|
|
400
|
+
// call's own event line (per-event field, never a synthetic event).
|
|
401
|
+
if (this.sequencing && entry.name === 'google_api_call' && this.pendingMid) {
|
|
402
|
+
event.mid = this.pendingMid;
|
|
403
|
+
this.pendingMid = undefined;
|
|
404
|
+
}
|
|
405
|
+
this.events.push(JSON.stringify(event));
|
|
397
406
|
}
|
|
398
|
-
this.events.push(JSON.stringify(event));
|
|
399
407
|
this.dirty = true;
|
|
400
408
|
}
|
|
401
409
|
/** Escape hatch: only the RESOLVED methodId (double-gated) or a miss. */
|
|
@@ -407,7 +415,8 @@ export class Metrics {
|
|
|
407
415
|
}
|
|
408
416
|
else if (idOrNull.length <= METHOD_ID_MAX && METHOD_ID_RE.test(idOrNull)) {
|
|
409
417
|
addInto(this.deltas.escape.methods, idOrNull);
|
|
410
|
-
this.
|
|
418
|
+
if (this.sequencing)
|
|
419
|
+
this.pendingMid = idOrNull;
|
|
411
420
|
}
|
|
412
421
|
this.dirty = true;
|
|
413
422
|
}
|
|
@@ -475,12 +484,14 @@ export class Metrics {
|
|
|
475
484
|
else {
|
|
476
485
|
addInto(this.deltas.rpc, RPC_CODES.has(kind) ? `rpc_error_${kind}` : 'rpc_error_other');
|
|
477
486
|
}
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
487
|
+
if (this.eventLog) {
|
|
488
|
+
this.events.push(JSON.stringify({
|
|
489
|
+
ts: new Date(this.now()).toISOString().slice(0, 16) + ':00Z',
|
|
490
|
+
boot: this.bootId, seq: this.seq++,
|
|
491
|
+
tool: tool && TOOL_NAME_RE.test(tool) ? tool : 'other',
|
|
492
|
+
ok: false, ms: 0, chars: 'le64', src: 'rpc',
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
484
495
|
this.dirty = true;
|
|
485
496
|
}
|
|
486
497
|
catch { /* never throws outward */ }
|
package/dist/write-control.d.ts
CHANGED
|
@@ -7,12 +7,11 @@ export interface Policy {
|
|
|
7
7
|
allow: string[];
|
|
8
8
|
deny: string[];
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* policy for the EE/future seam, but in v6 alpha it does NOT stiffen the
|
|
10
|
+
* Reserved seam (HTTP posture is "annotations only"): the dispatch
|
|
11
|
+
* transport is threaded into the resolved policy for a future seam, but in v6 alpha it does NOT stiffen the
|
|
13
12
|
* profile or alter any `isAllowed` verdict. "Stricter on HTTP" is achieved
|
|
14
13
|
* entirely by the `anthropic/requiresUserInteraction` annotation on the
|
|
15
|
-
* irreversible set
|
|
14
|
+
* irreversible set, emitted identically on both transports.
|
|
16
15
|
* Optional so existing Policy literals (codegen/tests) need no change;
|
|
17
16
|
* resolvePolicy always populates it.
|
|
18
17
|
*/
|
|
@@ -22,12 +21,9 @@ interface ToolRef {
|
|
|
22
21
|
name: string;
|
|
23
22
|
service: string;
|
|
24
23
|
cud: Cud;
|
|
25
|
-
/** The method's required OAuth scopes, when the caller knows them. Absent
|
|
26
|
-
* means "unknown", which is treated as not privileged: a missing scope list
|
|
27
|
-
* must not silently widen the gate OR silently break an ordinary write. */
|
|
28
24
|
scopes?: readonly string[];
|
|
29
25
|
}
|
|
30
|
-
/** The irreversible set (frozen
|
|
26
|
+
/** The irreversible set (frozen): real sends leave the
|
|
31
27
|
* user's (or org's) identity unrecallably; permanent deletes bypass Trash.
|
|
32
28
|
* Includes the callable GENERATED twins of the curated ops — the surface is
|
|
33
29
|
* always callable by name, so a prompt-free twin would defeat the layer.
|
package/dist/write-control.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { hints } from './hint-provider.js';
|
|
1
2
|
const PROFILES = ['read-only', 'safe-writes', 'full-writes'];
|
|
2
|
-
/** The irreversible set (frozen
|
|
3
|
+
/** The irreversible set (frozen): real sends leave the
|
|
3
4
|
* user's (or org's) identity unrecallably; permanent deletes bypass Trash.
|
|
4
5
|
* Includes the callable GENERATED twins of the curated ops — the surface is
|
|
5
6
|
* always callable by name, so a prompt-free twin would defeat the layer.
|
|
@@ -63,9 +64,8 @@ function firstMatch(tool, globs) {
|
|
|
63
64
|
function matchesAny(tool, globs) {
|
|
64
65
|
return firstMatch(tool, globs) !== undefined;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* write in any useful sense of the word, whatever its HTTP verb. */
|
|
67
|
+
// Scopes that authorize acting on the whole organization, legal holds,
|
|
68
|
+
// billing, or deployed code: not a "safe" write, whatever its HTTP verb.
|
|
69
69
|
const PRIVILEGED_SCOPE_RE = /\/auth\/(admin\.directory|admin\.datatransfer|cloud-identity|apps\.licensing|apps\.order|apps\.groups\.settings|apps\.groups\.migration|ediscovery|script\.projects|script\.deployments)/;
|
|
70
70
|
/** Registering a push channel hands this account's activity to an external URL.
|
|
71
71
|
* It is shaped like a create and behaves like an export, so the verb hides it. */
|
|
@@ -155,10 +155,17 @@ export function writeDisabledResult(tool, policy, account) {
|
|
|
155
155
|
// Recomputed rather than passed in: every call site has already run
|
|
156
156
|
// isAllowed, and a hint that disagrees with the verdict is worse than none.
|
|
157
157
|
const reason = denyReason(tool, policy) ?? { rule: 'profile', profile: policy.profile };
|
|
158
|
+
const rendered = hints()?.render({
|
|
159
|
+
kind: 'write_denied',
|
|
160
|
+
tool: { name: tool.name, service: tool.service, cud: tool.cud },
|
|
161
|
+
reason,
|
|
162
|
+
policy,
|
|
163
|
+
});
|
|
158
164
|
const envelope = {
|
|
159
165
|
error: 'write_disabled',
|
|
160
|
-
message:
|
|
161
|
-
|
|
166
|
+
message: rendered?.message ??
|
|
167
|
+
`"${tool.name}" (${tool.cud}) is disabled by the current write-control policy (profile: ${policy.profile}${policy.readOnly ? ', GOOGLE_READ_ONLY=true' : ''}).`,
|
|
168
|
+
hint: rendered?.hint ?? denyHint(tool, reason),
|
|
162
169
|
retriable: false,
|
|
163
170
|
...(account ? { account } : {}),
|
|
164
171
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.4",
|
|
4
4
|
"mcpName": "io.github.bakissation/mcp-google-multi",
|
|
5
5
|
"description": "Local MCP server for Google Workspace (Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console, +Forms/Chat/Admin) across multiple accounts — OAuth-only, encrypted token storage, deny-by-default writes.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,32 @@
|
|
|
9
9
|
"mcp-google-multi": "dist/index.js"
|
|
10
10
|
},
|
|
11
11
|
"main": "dist/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./identity": "./dist/identity.js",
|
|
15
|
+
"./compose": "./dist/compose.js",
|
|
16
|
+
"./registry": "./dist/registry.js",
|
|
17
|
+
"./oauth-as": "./dist/oauth-as.js",
|
|
18
|
+
"./accounts": "./dist/accounts.js",
|
|
19
|
+
"./token-store": "./dist/token-store.js",
|
|
20
|
+
"./http-transport": "./dist/http-transport.js",
|
|
21
|
+
"./http-config": "./dist/http-config.js",
|
|
22
|
+
"./client": "./dist/client.js",
|
|
23
|
+
"./config-file": "./dist/config-file.js",
|
|
24
|
+
"./fs-atomic": "./dist/fs-atomic.js",
|
|
25
|
+
"./master-key": "./dist/master-key.js",
|
|
26
|
+
"./mcp-token": "./dist/mcp-token.js",
|
|
27
|
+
"./boot-gates": "./dist/boot-gates.js",
|
|
28
|
+
"./tenant-purge": "./dist/tenant-purge.js",
|
|
29
|
+
"./doctor": "./dist/doctor.js",
|
|
30
|
+
"./write-control": "./dist/write-control.js",
|
|
31
|
+
"./scope-catalog": "./dist/scope-catalog.js",
|
|
32
|
+
"./auth": "./dist/auth.js",
|
|
33
|
+
"./usage-metrics": "./dist/usage-metrics.js",
|
|
34
|
+
"./metrics-tap": "./dist/metrics-tap.js",
|
|
35
|
+
"./hint-provider": "./dist/hint-provider.js",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
12
38
|
"files": [
|
|
13
39
|
"dist"
|
|
14
40
|
],
|
|
@@ -65,29 +91,29 @@
|
|
|
65
91
|
"bundle": "esbuild dist/index.js --bundle --platform=node --format=esm --outfile=bundle/index.js --external:@napi-rs/keyring --banner:js=\"import{createRequire as __dockerCR}from'node:module';var require=__dockerCR(import.meta.url);\" --log-level=warning"
|
|
66
92
|
},
|
|
67
93
|
"dependencies": {
|
|
68
|
-
"@googleapis/admin": "^37.0.
|
|
69
|
-
"@googleapis/analyticsadmin": "^22.0.
|
|
70
|
-
"@googleapis/analyticsdata": "^10.
|
|
71
|
-
"@googleapis/calendar": "^20.
|
|
72
|
-
"@googleapis/chat": "^51.
|
|
73
|
-
"@googleapis/docs": "^14.
|
|
74
|
-
"@googleapis/drive": "^26.0.
|
|
75
|
-
"@googleapis/forms": "^11.0.
|
|
76
|
-
"@googleapis/gmail": "^22.0.
|
|
77
|
-
"@googleapis/meet": "^9.
|
|
78
|
-
"@googleapis/people": "^12.0.
|
|
79
|
-
"@googleapis/searchconsole": "^11.0.
|
|
80
|
-
"@googleapis/sheets": "^18.
|
|
81
|
-
"@googleapis/slides": "^10.
|
|
82
|
-
"@googleapis/tasks": "^17.0.
|
|
83
|
-
"@googleapis/webmasters": "^9.0.
|
|
94
|
+
"@googleapis/admin": "^37.0.2",
|
|
95
|
+
"@googleapis/analyticsadmin": "^22.0.1",
|
|
96
|
+
"@googleapis/analyticsdata": "^10.1.0",
|
|
97
|
+
"@googleapis/calendar": "^20.1.0",
|
|
98
|
+
"@googleapis/chat": "^51.2.0",
|
|
99
|
+
"@googleapis/docs": "^14.1.0",
|
|
100
|
+
"@googleapis/drive": "^26.0.2",
|
|
101
|
+
"@googleapis/forms": "^11.0.1",
|
|
102
|
+
"@googleapis/gmail": "^22.0.1",
|
|
103
|
+
"@googleapis/meet": "^9.1.0",
|
|
104
|
+
"@googleapis/people": "^12.0.1",
|
|
105
|
+
"@googleapis/searchconsole": "^11.0.1",
|
|
106
|
+
"@googleapis/sheets": "^18.1.1",
|
|
107
|
+
"@googleapis/slides": "^10.1.1",
|
|
108
|
+
"@googleapis/tasks": "^17.0.1",
|
|
109
|
+
"@googleapis/webmasters": "^9.0.1",
|
|
84
110
|
"@modelcontextprotocol/node": "2.0.0",
|
|
85
111
|
"@modelcontextprotocol/server": "2.0.0",
|
|
86
|
-
"googleapis-common": "^9.
|
|
112
|
+
"googleapis-common": "^9.3.0",
|
|
87
113
|
"jose": "^6.2.8",
|
|
88
|
-
"markdown-it": "15.0.
|
|
114
|
+
"markdown-it": "15.0.2",
|
|
89
115
|
"mime-types": "^3.0.2",
|
|
90
|
-
"nodemailer": "
|
|
116
|
+
"nodemailer": "10.1.0",
|
|
91
117
|
"turndown": "7.2.4",
|
|
92
118
|
"turndown-plugin-gfm": "1.0.2",
|
|
93
119
|
"zod": "^4.3.6"
|
|
@@ -109,7 +135,7 @@
|
|
|
109
135
|
"tsx": "^4.23.1",
|
|
110
136
|
"typescript": "^6.0.3",
|
|
111
137
|
"typescript-eslint": "^8.59.1",
|
|
112
|
-
"vitest": "^
|
|
138
|
+
"vitest": "^5.0.4"
|
|
113
139
|
},
|
|
114
140
|
"optionalDependencies": {
|
|
115
141
|
"@napi-rs/keyring": "1.3.0"
|