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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +5 -5
  2. package/dist/accounts.d.ts +20 -4
  3. package/dist/accounts.js +68 -44
  4. package/dist/api-probe.d.ts +5 -0
  5. package/dist/api-probe.js +20 -15
  6. package/dist/arg-normalize.js +19 -38
  7. package/dist/arg-strict.d.ts +7 -0
  8. package/dist/arg-strict.js +38 -28
  9. package/dist/auth.d.ts +5 -4
  10. package/dist/auth.js +16 -23
  11. package/dist/boot-gates.d.ts +26 -0
  12. package/dist/boot-gates.js +36 -0
  13. package/dist/cimd-cache.d.ts +38 -0
  14. package/dist/cimd-cache.js +111 -0
  15. package/dist/client-config.d.ts +2 -2
  16. package/dist/client-config.js +8 -14
  17. package/dist/client.d.ts +32 -2
  18. package/dist/client.js +0 -0
  19. package/dist/compose.d.ts +18 -0
  20. package/dist/compose.js +109 -0
  21. package/dist/config-file.d.ts +13 -0
  22. package/dist/config-file.js +41 -10
  23. package/dist/discover.js +7 -10
  24. package/dist/discovery-client.d.ts +3 -0
  25. package/dist/discovery-client.js +36 -32
  26. package/dist/doctor.d.ts +9 -5
  27. package/dist/doctor.js +119 -29
  28. package/dist/env-load.js +2 -5
  29. package/dist/executor.d.ts +3 -0
  30. package/dist/executor.js +6 -6
  31. package/dist/fanout.d.ts +9 -1
  32. package/dist/fanout.js +47 -22
  33. package/dist/fs-atomic.d.ts +8 -0
  34. package/dist/fs-atomic.js +216 -29
  35. package/dist/hint-provider.d.ts +129 -0
  36. package/dist/hint-provider.js +58 -0
  37. package/dist/http-config.d.ts +2 -2
  38. package/dist/http-config.js +3 -7
  39. package/dist/http-transport.d.ts +67 -9
  40. package/dist/http-transport.js +318 -52
  41. package/dist/identity.d.ts +6 -3
  42. package/dist/identity.js +24 -8
  43. package/dist/index.js +76 -160
  44. package/dist/master-key.d.ts +3 -3
  45. package/dist/master-key.js +16 -19
  46. package/dist/mcp-token.d.ts +126 -19
  47. package/dist/mcp-token.js +557 -76
  48. package/dist/metrics-cli.d.ts +1 -1
  49. package/dist/metrics-cli.js +1 -1
  50. package/dist/metrics-tap.js +2 -5
  51. package/dist/migrate-config.d.ts +1 -2
  52. package/dist/migrate-config.js +5 -7
  53. package/dist/net-tuning.js +2 -4
  54. package/dist/oauth-as.d.ts +104 -4
  55. package/dist/oauth-as.js +368 -114
  56. package/dist/oauth-consent.d.ts +10 -0
  57. package/dist/oauth-consent.js +20 -10
  58. package/dist/outbound-allowlist.js +9 -11
  59. package/dist/reauth-hint.d.ts +1 -1
  60. package/dist/reauth-hint.js +11 -10
  61. package/dist/registry-transfer.d.ts +5 -0
  62. package/dist/registry-transfer.js +63 -11
  63. package/dist/registry.d.ts +13 -4
  64. package/dist/registry.js +100 -72
  65. package/dist/scope-catalog.d.ts +16 -0
  66. package/dist/scope-catalog.js +40 -13
  67. package/dist/scope-observability.d.ts +10 -9
  68. package/dist/scope-observability.js +34 -24
  69. package/dist/services.d.ts +18 -10
  70. package/dist/services.js +61 -22
  71. package/dist/ssrf-guard.d.ts +23 -9
  72. package/dist/ssrf-guard.js +106 -78
  73. package/dist/tenant-purge.d.ts +26 -0
  74. package/dist/tenant-purge.js +65 -0
  75. package/dist/token-store.d.ts +20 -2
  76. package/dist/token-store.js +0 -0
  77. package/dist/tools/_coerce.d.ts +4 -4
  78. package/dist/tools/_errors.d.ts +3 -0
  79. package/dist/tools/_errors.js +72 -60
  80. package/dist/tools/_local-files.d.ts +9 -0
  81. package/dist/tools/_local-files.js +22 -8
  82. package/dist/tools/account-wizard.d.ts +13 -3
  83. package/dist/tools/account-wizard.js +107 -56
  84. package/dist/tools/accounts-tool.d.ts +8 -1
  85. package/dist/tools/accounts-tool.js +35 -19
  86. package/dist/tools/admin.d.ts +10 -1
  87. package/dist/tools/admin.js +14 -11
  88. package/dist/tools/analytics.d.ts +10 -1
  89. package/dist/tools/analytics.js +14 -10
  90. package/dist/tools/calendar.d.ts +2 -1
  91. package/dist/tools/calendar.js +19 -18
  92. package/dist/tools/chat.d.ts +10 -1
  93. package/dist/tools/chat.js +12 -8
  94. package/dist/tools/contacts.d.ts +2 -1
  95. package/dist/tools/contacts.js +22 -19
  96. package/dist/tools/docs.d.ts +2 -1
  97. package/dist/tools/docs.js +35 -36
  98. package/dist/tools/drive.d.ts +2 -1
  99. package/dist/tools/drive.js +80 -74
  100. package/dist/tools/forms.d.ts +10 -1
  101. package/dist/tools/forms.js +18 -12
  102. package/dist/tools/generated/_shared.d.ts +2 -1
  103. package/dist/tools/generated/_shared.js +7 -3
  104. package/dist/tools/generated/admin.d.ts +2 -1
  105. package/dist/tools/generated/admin.js +265 -265
  106. package/dist/tools/generated/analytics.d.ts +2 -1
  107. package/dist/tools/generated/analytics.js +125 -125
  108. package/dist/tools/generated/appsmarket.d.ts +2 -1
  109. package/dist/tools/generated/appsmarket.js +5 -5
  110. package/dist/tools/generated/calendar.d.ts +2 -1
  111. package/dist/tools/generated/calendar.js +55 -55
  112. package/dist/tools/generated/chat.d.ts +2 -1
  113. package/dist/tools/generated/chat.js +95 -95
  114. package/dist/tools/generated/classroom.d.ts +2 -1
  115. package/dist/tools/generated/classroom.js +209 -209
  116. package/dist/tools/generated/cloudidentity.d.ts +2 -1
  117. package/dist/tools/generated/cloudidentity.js +133 -133
  118. package/dist/tools/generated/cloudsearch.d.ts +2 -1
  119. package/dist/tools/generated/cloudsearch.js +99 -99
  120. package/dist/tools/generated/contacts.d.ts +2 -1
  121. package/dist/tools/generated/contacts.js +29 -29
  122. package/dist/tools/generated/drive.d.ts +2 -1
  123. package/dist/tools/generated/drive.js +71 -71
  124. package/dist/tools/generated/driveactivity.d.ts +2 -1
  125. package/dist/tools/generated/driveactivity.js +3 -3
  126. package/dist/tools/generated/drivelabels.d.ts +2 -1
  127. package/dist/tools/generated/drivelabels.js +53 -53
  128. package/dist/tools/generated/forms.d.ts +2 -1
  129. package/dist/tools/generated/forms.js +7 -7
  130. package/dist/tools/generated/gmail.d.ts +2 -1
  131. package/dist/tools/generated/gmail.js +117 -117
  132. package/dist/tools/generated/groupsmigration.d.ts +2 -1
  133. package/dist/tools/generated/groupsmigration.js +3 -3
  134. package/dist/tools/generated/groupssettings.d.ts +2 -1
  135. package/dist/tools/generated/groupssettings.js +7 -7
  136. package/dist/tools/generated/index.d.ts +2 -1
  137. package/dist/tools/generated/keep.d.ts +2 -1
  138. package/dist/tools/generated/keep.js +15 -15
  139. package/dist/tools/generated/licensing.d.ts +2 -1
  140. package/dist/tools/generated/licensing.js +15 -15
  141. package/dist/tools/generated/meet.d.ts +2 -1
  142. package/dist/tools/generated/meet.js +27 -27
  143. package/dist/tools/generated/postmaster.d.ts +2 -1
  144. package/dist/tools/generated/postmaster.js +9 -9
  145. package/dist/tools/generated/reseller.d.ts +2 -1
  146. package/dist/tools/generated/reseller.js +35 -35
  147. package/dist/tools/generated/script.d.ts +2 -1
  148. package/dist/tools/generated/script.js +33 -33
  149. package/dist/tools/generated/searchconsole.d.ts +2 -1
  150. package/dist/tools/generated/searchconsole.js +3 -3
  151. package/dist/tools/generated/sheets.d.ts +2 -1
  152. package/dist/tools/generated/sheets.js +15 -15
  153. package/dist/tools/generated/tasks.d.ts +2 -1
  154. package/dist/tools/generated/tasks.js +5 -5
  155. package/dist/tools/generated/vault.d.ts +2 -1
  156. package/dist/tools/generated/vault.js +67 -67
  157. package/dist/tools/generated/workspaceevents.d.ts +2 -1
  158. package/dist/tools/generated/workspaceevents.js +31 -31
  159. package/dist/tools/gmail-mime.d.ts +1 -1
  160. package/dist/tools/gmail-mime.js +7 -12
  161. package/dist/tools/gmail.d.ts +4 -3
  162. package/dist/tools/gmail.js +67 -69
  163. package/dist/tools/google-api.d.ts +2 -3
  164. package/dist/tools/google-api.js +34 -22
  165. package/dist/tools/meet.d.ts +10 -1
  166. package/dist/tools/meet.js +12 -9
  167. package/dist/tools/searchconsole.d.ts +2 -1
  168. package/dist/tools/searchconsole.js +16 -13
  169. package/dist/tools/sheets.d.ts +2 -1
  170. package/dist/tools/sheets.js +35 -32
  171. package/dist/tools/slides.d.ts +10 -1
  172. package/dist/tools/slides.js +16 -10
  173. package/dist/tools/tasks.d.ts +2 -1
  174. package/dist/tools/tasks.js +18 -15
  175. package/dist/trim.d.ts +5 -0
  176. package/dist/trim.js +19 -0
  177. package/dist/usage-metrics.d.ts +23 -0
  178. package/dist/usage-metrics.js +67 -56
  179. package/dist/write-control.d.ts +4 -8
  180. package/dist/write-control.js +13 -6
  181. package/package.json +47 -21
@@ -15,7 +15,7 @@ interface PromotionRow {
15
15
  calls: number;
16
16
  kind: 'curation-candidate' | 'visibility-failure' | 'generation-gap';
17
17
  }
18
- /** The promotion-queue view: ranked evidence, a human decides (spec section 5). */
18
+ /** The promotion-queue view: ranked evidence, a human decides. */
19
19
  export declare function classifyPromotion(merged: DayAgg, data: PromotionData): PromotionRow[];
20
20
  export declare function runMetricsCli(argv: string[], deps: MetricsCliDeps): number;
21
21
  export {};
@@ -87,7 +87,7 @@ function renderReport(merged, days, out) {
87
87
  }
88
88
  }
89
89
  const tail = (id) => id.split('.').slice(1).join('.');
90
- /** The promotion-queue view: ranked evidence, a human decides (spec section 5). */
90
+ /** The promotion-queue view: ranked evidence, a human decides. */
91
91
  export function classifyPromotion(merged, data) {
92
92
  const curated = new Set();
93
93
  for (const id of data.curatedIds) {
@@ -1,10 +1,7 @@
1
1
  import { isSchemaValidationText } from './arg-normalize.js';
2
2
  const PENDING_CAP = 1_000;
3
- // The detector lives next to the rewriter that consumes the same frames, so
4
- // the two can never drift apart. This tap sees the ORIGINAL prose:
5
- // withValidationEnvelope sits BELOW it and converts that same frame into an
6
- // envelope for the client, so classification here and the contract on the wire
7
- // stay independent.
3
+ // This tap sees the ORIGINAL prose; withValidationEnvelope sits BELOW it and
4
+ // converts the same frame for the client, so the two stay independent.
8
5
  export { isSchemaValidationText };
9
6
  /**
10
7
  * @param isKnownTool membership test against the REGISTERED tool set. The
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Synthesize config.json accounts{} from the v5 env (GOOGLE_ACCOUNTS +
3
3
  * GOOGLE_ADMIN_ACCOUNTS). Never deletes or edits env; idempotent (a re-run
4
- * that changes nothing says so). Scope-profile and preference fields are
5
- * migrated by their own slices' commands.
4
+ * that changes nothing says so).
6
5
  */
7
6
  export declare function runMigrateConfig(env?: NodeJS.ProcessEnv): void;
@@ -1,13 +1,12 @@
1
1
  import { CONFIG_VERSION, configFilePath, loadConfigFile, mutateConfigFile } from './config-file.js';
2
- import { BUNDLE_CATALOG, closestBundle, resolveBundleAliases } from './scope-catalog.js';
2
+ import { closestBundle, isKnownBundle, resolveBundleAliases } from './scope-catalog.js';
3
3
  function parseCsv(value) {
4
4
  return (value ?? '').split(',').map((s) => s.trim()).filter(Boolean);
5
5
  }
6
6
  /**
7
7
  * Synthesize config.json accounts{} from the v5 env (GOOGLE_ACCOUNTS +
8
8
  * GOOGLE_ADMIN_ACCOUNTS). Never deletes or edits env; idempotent (a re-run
9
- * that changes nothing says so). Scope-profile and preference fields are
10
- * migrated by their own slices' commands.
9
+ * that changes nothing says so).
11
10
  */
12
11
  export function runMigrateConfig(env = process.env) {
13
12
  const raw = env.GOOGLE_ACCOUNTS;
@@ -35,7 +34,7 @@ export function runMigrateConfig(env = process.env) {
35
34
  }
36
35
  desired[alias] = { email };
37
36
  }
38
- // Legacy global scopes (BC7) become an explicit named profile every account
37
+ // Legacy global scopes become an explicit named profile every account
39
38
  // points at, so removing the env var later changes nothing.
40
39
  const legacyNames = parseCsv(env.GOOGLE_OPTIONAL_SCOPES);
41
40
  let legacyProfile;
@@ -47,7 +46,7 @@ export function runMigrateConfig(env = process.env) {
47
46
  process.exitCode = 1;
48
47
  return;
49
48
  }
50
- if (!(bundle in BUNDLE_CATALOG)) {
49
+ if (!isKnownBundle(bundle)) {
51
50
  const hint = closestBundle(bundle);
52
51
  console.error(`E_UNKNOWN_BUNDLE: unknown bundle "${bundle}" in GOOGLE_OPTIONAL_SCOPES${hint ? ` — did you mean "${hint}"?` : ''}. Nothing written.`);
53
52
  process.exitCode = 1;
@@ -59,8 +58,7 @@ export function runMigrateConfig(env = process.env) {
59
58
  const filePath = configFilePath(env);
60
59
  const before = loadConfigFile(filePath);
61
60
  // Merge per-alias: env owns the alias set and emails, but file-only fields
62
- // (scopeProfile, admin) survive for retained aliases — mirroring runtime
63
- // precedence, where unset GOOGLE_ADMIN_ACCOUNTS preserves file admin flags.
61
+ // (scopeProfile, admin) survive for retained aliases.
64
62
  const existing = before?.accounts ?? {};
65
63
  const dropped = Object.keys(existing).filter((a) => !(a in desired));
66
64
  for (const [alias, entry] of Object.entries(desired)) {
@@ -1,8 +1,6 @@
1
1
  import net from 'node:net';
2
- // Node's happy-eyeballs gives each address family 250ms per connect attempt on
3
- // every LTS line (raised to 500ms only in v25.2+); on high-latency or
4
- // broken-IPv6 links that aborts EVERY Google call while curl works. Raise the
5
- // process default unless the user tuned it themselves. Why: docs/internals.md.
2
+ // Node's happy-eyeballs 250ms-per-attempt default aborts EVERY Google call on
3
+ // a high-latency or broken-IPv6 link while curl works. See docs/internals.md.
6
4
  export const CONNECT_ATTEMPT_TIMEOUT_MS = 2000;
7
5
  const USER_FLAGS = ['--network-family-autoselection-attempt-timeout', '--no-network-family-autoselection'];
8
6
  /** Pure decision: the timeout to apply, or null to leave Node's setting alone. */
@@ -6,6 +6,10 @@ export declare const DCR_MAX_REDIRECT_URIS = 10;
6
6
  export interface GoogleExchangeResult {
7
7
  tokens: Record<string, unknown>;
8
8
  email?: string;
9
+ /** Google's stable account id; unlike the address it is never reassigned. */
10
+ sub?: string;
11
+ /** Google Workspace hosted domain; absent for a consumer account. */
12
+ hd?: string;
9
13
  }
10
14
  export interface AuthServerConfig {
11
15
  base: string;
@@ -16,21 +20,72 @@ export interface AuthServerConfig {
16
20
  accessTtlSec?: number;
17
21
  masterKey: string;
18
22
  refreshStorePath: string;
23
+ /** `maxAgeSec` of the refresh store buildAuthServer creates: sessions end
24
+ * this long after their sign-in. Unset: unlimited. */
25
+ refreshMaxAgeSec?: number;
26
+ /** `idleSec` of that store: sessions end this long after their last
27
+ * refresh. Unset: unlimited. */
28
+ refreshIdleSec?: number;
29
+ /** `maxFamiliesPerSub` of that store: bounds how many live refresh-token
30
+ * families one subject may hold, dropping its least-recently-used one to
31
+ * make room. Unset: unlimited. */
32
+ refreshMaxFamiliesPerSub?: number;
33
+ /** Default true. The clientless `flow=alias_reauth` link names an alias with
34
+ * no tenant binding, so a multi-tenant deployment sets false: with several
35
+ * tenants an alias name alone is ambiguous and the branch becomes a
36
+ * cross-tenant re-auth vector. Server-minted alias_add flows are unaffected. */
37
+ legacyAliasReauth?: boolean;
19
38
  }
39
+ /** What /callback hands the alias_add binder once Google returned a verified
40
+ * identity for a server-minted link. */
41
+ export interface TenantAliasBind {
42
+ tenantId: string;
43
+ alias: string;
44
+ email: string;
45
+ bundles?: string[];
46
+ nonce?: string;
47
+ tokens: Record<string, unknown>;
48
+ sub?: string;
49
+ }
50
+ /** A binder's deliberate refusal (policy, not failure): /callback answers 403
51
+ * with this slug and message. The message reaches an unauthenticated browser. */
52
+ export interface AliasBindRefusal {
53
+ refused: {
54
+ slug: string;
55
+ message: string;
56
+ };
57
+ }
58
+ export interface GoogleIdentity {
59
+ email?: string;
60
+ sub?: string;
61
+ hd?: string;
62
+ }
63
+ /** Only trust the id_token identity when Google marks the email verified.
64
+ * The token comes straight from Google's token endpoint, so its claims are
65
+ * read unsigned. A malformed sub or hd is dropped, never passed on. */
66
+ export declare function verifiedIdentityFromIdToken(idToken?: string | null): GoogleIdentity | undefined;
67
+ export declare function verifiedEmailFromIdToken(idToken?: string | null): string | undefined;
20
68
  export interface AuthServerDeps {
21
69
  fetchCimd?: (clientId: string) => Promise<Record<string, unknown>>;
22
70
  /** Exchange a Google auth code at `${base}/callback` for tokens (+ owner email). */
23
71
  exchangeCode?: (code: string, flow: StatePayload['flow']) => Promise<GoogleExchangeResult>;
24
72
  /** Build the Google authorization URL (redirect back to `${base}/callback`).
25
- * The impl chooses scope/prompt/login_hint from the flow: owner_gate =
26
- * `openid email` + select_account; alias_reauth = the alias's resource scopes
27
- * + consent. */
73
+ * The impl chooses scope/prompt from the flow: owner_gate = `openid email`
74
+ * + select_account; alias_reauth = the alias's resource scopes + select_account
75
+ * consent, and no login_hint (the link needs no authentication). */
28
76
  buildGoogleAuthUrl?: (opts: {
29
77
  flow: StatePayload['flow'];
30
78
  alias?: string;
31
79
  state: string;
80
+ bundles?: string[];
32
81
  }) => string;
33
82
  writeToken?: (alias: string, tokens: Record<string, unknown>) => void;
83
+ /** Scopes the alias asked for that `grantedScope` lacks, so the alias_reauth
84
+ * completion page can say what Google's granular consent left out. */
85
+ missingScopes?: (alias: string, grantedScope: string | undefined) => string[];
86
+ /** Whether a readable token is stored for the alias; a narrower re-auth
87
+ * grant keeps it rather than replace it. */
88
+ hasToken?: (alias: string) => boolean;
34
89
  registeredClients?: Map<string, {
35
90
  redirect_uris: string[];
36
91
  }>;
@@ -41,12 +96,57 @@ export interface AuthServerDeps {
41
96
  log?: (line: string) => void;
42
97
  /** The alias's configured Google email, for the alias_reauth identity binding. */
43
98
  aliasEmail?: (alias: string) => string | undefined;
99
+ /** Map a verified sign-in email to the subject its tokens are minted under.
100
+ * Default = the MCP_OWNER_EMAILS allowlist -> {sub:'owner'}; null = denied.
101
+ * The tenant resolver (invite claim / email->tenant lookup) plugs in here.
102
+ * `identity` carries Google's `sub` and `hd` when the exchange returned
103
+ * them, so a resolver can key on the stable account id rather than an
104
+ * address that can be reassigned. */
105
+ resolveSubject?: (email: string, identity?: {
106
+ sub?: string;
107
+ hd?: string;
108
+ }) => {
109
+ sub: string;
110
+ } | null | {
111
+ denied: {
112
+ description: string;
113
+ };
114
+ };
115
+ /** alias_add completion: persist the new alias's config row and tokens under
116
+ * its tenant. /callback awaits it only after a successful Google exchange.
117
+ * A returned refusal answers 403 with its slug; a throw or rejection answers
118
+ * 500 E_ALIAS_ADD_FAILED. */
119
+ bindTenantAlias?: (bind: TenantAliasBind) => void | AliasBindRefusal | Promise<void | AliasBindRefusal>;
120
+ /** False for a subject this host no longer serves (a multi-tenant host that
121
+ * removed or suspended it): /token then issues and rotates nothing for it
122
+ * (400 invalid_grant; a rotated sub's refresh tokens are dropped). A throw
123
+ * means the answer is unknown right now: 503 temporarily_unavailable, and
124
+ * the presented refresh token is not spent. Absent: every subject is active. */
125
+ subjectActive?: (sub: string) => boolean;
44
126
  }
45
127
  export interface AuthServer {
46
128
  routes: Record<string, RouteHandler>;
47
129
  authenticate: (req: IncomingMessage) => Promise<AuthOutcome>;
130
+ /** Mint a signed, single-use, TTL-bound clientless flow URL
131
+ * (`${base}/authorize?flow=...&state=<signed>`). The tenantId/bundles ride
132
+ * INSIDE the signed state, so nothing at /authorize or /callback trusts a
133
+ * caller-supplied identity. The URL works only on this AuthServer, and only
134
+ * until its process restarts: sign-in artifacts are signed with a key each
135
+ * buildAuthServer call generates. Inert unless something calls it. */
136
+ mintFlowState: (opts: {
137
+ flow: 'alias_add';
138
+ alias: string;
139
+ tenantId?: string;
140
+ bundles?: string[];
141
+ nonce?: string;
142
+ }) => Promise<string>;
143
+ /** The owner's re-auth link for `alias`: HMAC-signed, expiring, not single
144
+ * use (a chat client may prefetch it). /authorize refuses any alias_reauth
145
+ * request without a valid one, so an unauthenticated caller can neither
146
+ * start a re-auth nor learn which aliases exist or what they hold. */
147
+ reauthLink: (alias: string) => string;
48
148
  }
49
- /** C6: exact redirect_uri match, with RFC 8252 §7.3 port-agnostic loopback and
149
+ /** Exact redirect_uri match, with RFC 8252 §7.3 port-agnostic loopback and
50
150
  * the fixed claude.ai callback. */
51
151
  export declare function redirectAllowed(redirectUri: string, docRedirectUris: string[]): boolean;
52
152
  export declare function buildAuthServer(config: AuthServerConfig, deps?: AuthServerDeps): AuthServer;