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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +5 -5
  2. package/dist/accounts.d.ts +20 -4
  3. package/dist/accounts.js +68 -44
  4. package/dist/api-probe.d.ts +5 -0
  5. package/dist/api-probe.js +20 -15
  6. package/dist/arg-normalize.js +19 -38
  7. package/dist/arg-strict.d.ts +7 -0
  8. package/dist/arg-strict.js +38 -28
  9. package/dist/auth.d.ts +5 -4
  10. package/dist/auth.js +16 -23
  11. package/dist/boot-gates.d.ts +26 -0
  12. package/dist/boot-gates.js +36 -0
  13. package/dist/cimd-cache.d.ts +38 -0
  14. package/dist/cimd-cache.js +111 -0
  15. package/dist/client-config.d.ts +2 -2
  16. package/dist/client-config.js +8 -14
  17. package/dist/client.d.ts +32 -2
  18. package/dist/client.js +0 -0
  19. package/dist/compose.d.ts +18 -0
  20. package/dist/compose.js +109 -0
  21. package/dist/config-file.d.ts +13 -0
  22. package/dist/config-file.js +41 -10
  23. package/dist/discover.js +7 -10
  24. package/dist/discovery-client.d.ts +3 -0
  25. package/dist/discovery-client.js +36 -32
  26. package/dist/doctor.d.ts +9 -5
  27. package/dist/doctor.js +119 -29
  28. package/dist/env-load.js +2 -5
  29. package/dist/executor.d.ts +3 -0
  30. package/dist/executor.js +6 -6
  31. package/dist/fanout.d.ts +9 -1
  32. package/dist/fanout.js +47 -22
  33. package/dist/fs-atomic.d.ts +8 -0
  34. package/dist/fs-atomic.js +216 -29
  35. package/dist/hint-provider.d.ts +129 -0
  36. package/dist/hint-provider.js +58 -0
  37. package/dist/http-config.d.ts +2 -2
  38. package/dist/http-config.js +3 -7
  39. package/dist/http-transport.d.ts +67 -9
  40. package/dist/http-transport.js +318 -52
  41. package/dist/identity.d.ts +6 -3
  42. package/dist/identity.js +24 -8
  43. package/dist/index.js +76 -160
  44. package/dist/master-key.d.ts +3 -3
  45. package/dist/master-key.js +16 -19
  46. package/dist/mcp-token.d.ts +126 -19
  47. package/dist/mcp-token.js +557 -76
  48. package/dist/metrics-cli.d.ts +1 -1
  49. package/dist/metrics-cli.js +1 -1
  50. package/dist/metrics-tap.js +2 -5
  51. package/dist/migrate-config.d.ts +1 -2
  52. package/dist/migrate-config.js +5 -7
  53. package/dist/net-tuning.js +2 -4
  54. package/dist/oauth-as.d.ts +104 -4
  55. package/dist/oauth-as.js +368 -114
  56. package/dist/oauth-consent.d.ts +10 -0
  57. package/dist/oauth-consent.js +20 -10
  58. package/dist/outbound-allowlist.js +9 -11
  59. package/dist/reauth-hint.d.ts +1 -1
  60. package/dist/reauth-hint.js +11 -10
  61. package/dist/registry-transfer.d.ts +5 -0
  62. package/dist/registry-transfer.js +63 -11
  63. package/dist/registry.d.ts +13 -4
  64. package/dist/registry.js +100 -72
  65. package/dist/scope-catalog.d.ts +16 -0
  66. package/dist/scope-catalog.js +40 -13
  67. package/dist/scope-observability.d.ts +10 -9
  68. package/dist/scope-observability.js +34 -24
  69. package/dist/services.d.ts +18 -10
  70. package/dist/services.js +61 -22
  71. package/dist/ssrf-guard.d.ts +23 -9
  72. package/dist/ssrf-guard.js +106 -78
  73. package/dist/tenant-purge.d.ts +26 -0
  74. package/dist/tenant-purge.js +65 -0
  75. package/dist/token-store.d.ts +20 -2
  76. package/dist/token-store.js +0 -0
  77. package/dist/tools/_coerce.d.ts +4 -4
  78. package/dist/tools/_errors.d.ts +3 -0
  79. package/dist/tools/_errors.js +72 -60
  80. package/dist/tools/_local-files.d.ts +9 -0
  81. package/dist/tools/_local-files.js +22 -8
  82. package/dist/tools/account-wizard.d.ts +13 -3
  83. package/dist/tools/account-wizard.js +107 -56
  84. package/dist/tools/accounts-tool.d.ts +8 -1
  85. package/dist/tools/accounts-tool.js +35 -19
  86. package/dist/tools/admin.d.ts +10 -1
  87. package/dist/tools/admin.js +14 -11
  88. package/dist/tools/analytics.d.ts +10 -1
  89. package/dist/tools/analytics.js +14 -10
  90. package/dist/tools/calendar.d.ts +2 -1
  91. package/dist/tools/calendar.js +19 -18
  92. package/dist/tools/chat.d.ts +10 -1
  93. package/dist/tools/chat.js +12 -8
  94. package/dist/tools/contacts.d.ts +2 -1
  95. package/dist/tools/contacts.js +22 -19
  96. package/dist/tools/docs.d.ts +2 -1
  97. package/dist/tools/docs.js +35 -36
  98. package/dist/tools/drive.d.ts +2 -1
  99. package/dist/tools/drive.js +80 -74
  100. package/dist/tools/forms.d.ts +10 -1
  101. package/dist/tools/forms.js +18 -12
  102. package/dist/tools/generated/_shared.d.ts +2 -1
  103. package/dist/tools/generated/_shared.js +7 -3
  104. package/dist/tools/generated/admin.d.ts +2 -1
  105. package/dist/tools/generated/admin.js +265 -265
  106. package/dist/tools/generated/analytics.d.ts +2 -1
  107. package/dist/tools/generated/analytics.js +125 -125
  108. package/dist/tools/generated/appsmarket.d.ts +2 -1
  109. package/dist/tools/generated/appsmarket.js +5 -5
  110. package/dist/tools/generated/calendar.d.ts +2 -1
  111. package/dist/tools/generated/calendar.js +55 -55
  112. package/dist/tools/generated/chat.d.ts +2 -1
  113. package/dist/tools/generated/chat.js +95 -95
  114. package/dist/tools/generated/classroom.d.ts +2 -1
  115. package/dist/tools/generated/classroom.js +209 -209
  116. package/dist/tools/generated/cloudidentity.d.ts +2 -1
  117. package/dist/tools/generated/cloudidentity.js +133 -133
  118. package/dist/tools/generated/cloudsearch.d.ts +2 -1
  119. package/dist/tools/generated/cloudsearch.js +99 -99
  120. package/dist/tools/generated/contacts.d.ts +2 -1
  121. package/dist/tools/generated/contacts.js +29 -29
  122. package/dist/tools/generated/drive.d.ts +2 -1
  123. package/dist/tools/generated/drive.js +71 -71
  124. package/dist/tools/generated/driveactivity.d.ts +2 -1
  125. package/dist/tools/generated/driveactivity.js +3 -3
  126. package/dist/tools/generated/drivelabels.d.ts +2 -1
  127. package/dist/tools/generated/drivelabels.js +53 -53
  128. package/dist/tools/generated/forms.d.ts +2 -1
  129. package/dist/tools/generated/forms.js +7 -7
  130. package/dist/tools/generated/gmail.d.ts +2 -1
  131. package/dist/tools/generated/gmail.js +117 -117
  132. package/dist/tools/generated/groupsmigration.d.ts +2 -1
  133. package/dist/tools/generated/groupsmigration.js +3 -3
  134. package/dist/tools/generated/groupssettings.d.ts +2 -1
  135. package/dist/tools/generated/groupssettings.js +7 -7
  136. package/dist/tools/generated/index.d.ts +2 -1
  137. package/dist/tools/generated/keep.d.ts +2 -1
  138. package/dist/tools/generated/keep.js +15 -15
  139. package/dist/tools/generated/licensing.d.ts +2 -1
  140. package/dist/tools/generated/licensing.js +15 -15
  141. package/dist/tools/generated/meet.d.ts +2 -1
  142. package/dist/tools/generated/meet.js +27 -27
  143. package/dist/tools/generated/postmaster.d.ts +2 -1
  144. package/dist/tools/generated/postmaster.js +9 -9
  145. package/dist/tools/generated/reseller.d.ts +2 -1
  146. package/dist/tools/generated/reseller.js +35 -35
  147. package/dist/tools/generated/script.d.ts +2 -1
  148. package/dist/tools/generated/script.js +33 -33
  149. package/dist/tools/generated/searchconsole.d.ts +2 -1
  150. package/dist/tools/generated/searchconsole.js +3 -3
  151. package/dist/tools/generated/sheets.d.ts +2 -1
  152. package/dist/tools/generated/sheets.js +15 -15
  153. package/dist/tools/generated/tasks.d.ts +2 -1
  154. package/dist/tools/generated/tasks.js +5 -5
  155. package/dist/tools/generated/vault.d.ts +2 -1
  156. package/dist/tools/generated/vault.js +67 -67
  157. package/dist/tools/generated/workspaceevents.d.ts +2 -1
  158. package/dist/tools/generated/workspaceevents.js +31 -31
  159. package/dist/tools/gmail-mime.d.ts +1 -1
  160. package/dist/tools/gmail-mime.js +7 -12
  161. package/dist/tools/gmail.d.ts +4 -3
  162. package/dist/tools/gmail.js +67 -69
  163. package/dist/tools/google-api.d.ts +2 -3
  164. package/dist/tools/google-api.js +34 -22
  165. package/dist/tools/meet.d.ts +10 -1
  166. package/dist/tools/meet.js +12 -9
  167. package/dist/tools/searchconsole.d.ts +2 -1
  168. package/dist/tools/searchconsole.js +16 -13
  169. package/dist/tools/sheets.d.ts +2 -1
  170. package/dist/tools/sheets.js +35 -32
  171. package/dist/tools/slides.d.ts +10 -1
  172. package/dist/tools/slides.js +16 -10
  173. package/dist/tools/tasks.d.ts +2 -1
  174. package/dist/tools/tasks.js +18 -15
  175. package/dist/trim.d.ts +5 -0
  176. package/dist/trim.js +19 -0
  177. package/dist/usage-metrics.d.ts +23 -0
  178. package/dist/usage-metrics.js +67 -56
  179. package/dist/write-control.d.ts +4 -8
  180. package/dist/write-control.js +13 -6
  181. package/package.json +47 -21
@@ -1,4 +1,14 @@
1
1
  import { OAuth2Client } from 'googleapis-common';
2
+ /** Google authorize-URL options for the owner's HTTP authorization server.
3
+ * alias_reauth asks for the alias's scopes plus openid/email, which /callback
4
+ * needs to bind the returned identity to the alias. It carries no login_hint:
5
+ * the link works without authentication, so a hint would hand the alias's
6
+ * address to anyone who opens it. Every other flow is identity-only. */
7
+ export declare function ownerAuthUrlOptions(opts: {
8
+ flow: string;
9
+ alias?: string;
10
+ state: string;
11
+ }, scopesFor: (alias: string) => string[]): Parameters<OAuth2Client['generateAuthUrl']>[0];
2
12
  /** GOOGLE_CLIENT_ID/SECRET absent — caller maps to E_CLIENT_CREDENTIALS_MISSING. */
3
13
  export declare class ClientCredentialsMissingError extends Error {
4
14
  constructor();
@@ -1,12 +1,24 @@
1
1
  import { OAuth2Client } from 'googleapis-common';
2
2
  import http from 'node:http';
3
3
  import { URL } from 'node:url';
4
- // Shared loopback OAuth consent (leg C of cc-auth), used by the account wizard
5
- // (B7) and the `auth --account` CLI. Live-server-safe: typed errors instead of
6
- // process.exit, and a timeout so a never-completed consent can't wedge a tool
7
- // call forever. The listener binds an EPHEMERAL loopback port (RFC 8252 §7.3;
8
- // Google Desktop clients accept any http://localhost:<port> redirect), so a
9
- // second local process on a fixed port can never break auth.
4
+ // Shared loopback OAuth consent, used by the account wizard and `auth
5
+ // --account`; a timeout keeps a never-completed consent from wedging a tool call forever.
6
+ /** Google authorize-URL options for the owner's HTTP authorization server.
7
+ * alias_reauth asks for the alias's scopes plus openid/email, which /callback
8
+ * needs to bind the returned identity to the alias. It carries no login_hint:
9
+ * the link works without authentication, so a hint would hand the alias's
10
+ * address to anyone who opens it. Every other flow is identity-only. */
11
+ export function ownerAuthUrlOptions(opts, scopesFor) {
12
+ if (opts.flow === 'alias_reauth' && opts.alias) {
13
+ return {
14
+ access_type: 'offline',
15
+ prompt: 'select_account consent',
16
+ scope: [...new Set([...scopesFor(opts.alias), 'openid', 'email'])],
17
+ state: opts.state,
18
+ };
19
+ }
20
+ return { scope: ['openid', 'email'], prompt: 'select_account', state: opts.state };
21
+ }
10
22
  /** GOOGLE_CLIENT_ID/SECRET absent — caller maps to E_CLIENT_CREDENTIALS_MISSING. */
11
23
  export class ClientCredentialsMissingError extends Error {
12
24
  constructor() {
@@ -28,10 +40,8 @@ export class ConsentDeniedError extends Error {
28
40
  super(`E_CONSENT_DENIED: ${reason}`);
29
41
  }
30
42
  }
31
- // Surfaced after every successful consent: the expiry is invisible until the
32
- // token dies a week later as reauth_required, so the moment of success is the
33
- // one place the warning is guaranteed to be seen. Full walkthrough in
34
- // docs/google-cloud-setup.md.
43
+ // Surfaced after every successful consent: the moment of success is the one
44
+ // place this warning is guaranteed to be seen, before the token dies a week later as reauth_required.
35
45
  export const TESTING_MODE_WARNING = 'Heads-up: while your OAuth client\'s Publishing status is "Testing", Google expires refresh tokens after 7 days (weekly re-auth for every account). ' +
36
46
  'When your setup works, set it to "In production" at https://console.cloud.google.com/auth/audience. ' +
37
47
  'No verification review is needed for personal use; see docs/google-cloud-setup.md.';
@@ -1,8 +1,6 @@
1
- // Opt-in outbound recipient allowlist for unattended deployments: a
2
- // prompt-injection blast-radius control (a hijacked agent cannot mail, invite
3
- // or share outside the operator's list), a safety feature first and an
4
- // enterprise checkbox second. FREE-core forever (ee-definition-v0 handoff).
5
- // Off by default: an unset/empty GOOGLE_OUTBOUND_ALLOWLIST gates nothing.
1
+ // Opt-in outbound recipient allowlist: a prompt-injection blast-radius
2
+ // control (a hijacked agent cannot mail/invite/share outside the operator's list). Off by default.
3
+ import { hints } from './hint-provider.js';
6
4
  /** Parse GOOGLE_OUTBOUND_ALLOWLIST: comma-separated addresses and @domain
7
5
  * suffixes ("a@b.com, @company.com"). null = feature off (nothing gated). */
8
6
  export function resolveOutboundAllowlist(env = process.env) {
@@ -34,6 +32,7 @@ export function outboundViolations(emails, env = process.env) {
34
32
  /** The standard error envelope for a blocked outbound target. */
35
33
  export function outboundDeniedEnvelope(kind, blocked, account, env = process.env) {
36
34
  const list = resolveOutboundAllowlist(env);
35
+ const rendered = hints()?.render({ kind: 'outbound_denied', what: kind, blocked, entries: list?.entries ?? [] });
37
36
  return {
38
37
  content: [
39
38
  {
@@ -41,8 +40,9 @@ export function outboundDeniedEnvelope(kind, blocked, account, env = process.env
41
40
  text: JSON.stringify({
42
41
  error: 'recipient_not_allowed',
43
42
  message: `${kind} blocked by the outbound allowlist: ${blocked.join(', ')}.`,
44
- hint: `GOOGLE_OUTBOUND_ALLOWLIST is active (${(list?.entries ?? []).join(', ')}). ` +
45
- 'The operator must add the address (or its @domain) to the list, or unset the variable, to allow this target.',
43
+ hint: rendered?.hint ??
44
+ (`GOOGLE_OUTBOUND_ALLOWLIST is active (${(list?.entries ?? []).join(', ')}). ` +
45
+ 'The operator must add the address (or its @domain) to the list, or unset the variable, to allow this target.'),
46
46
  retriable: false,
47
47
  account,
48
48
  }),
@@ -56,10 +56,8 @@ export function checkOutbound(kind, emails, account, env = process.env) {
56
56
  const blocked = outboundViolations(emails, env);
57
57
  return blocked.length > 0 ? outboundDeniedEnvelope(kind, blocked, account, env) : null;
58
58
  }
59
- // Escape-hatch / generated-tool enforcement. Structured bodies are inspected
60
- // for the known recipient fields; RAW compose methods cannot be inspected
61
- // (base64 RFC 822), so with the allowlist active they are refused outright in
62
- // favor of the curated tools that enforce it.
59
+ // RAW compose methods can't be inspected (base64 RFC 822), so with the
60
+ // allowlist active they're refused outright in favor of the curated tools that enforce it.
63
61
  const RAW_SEND_METHODS = new Set(['gmail.users.messages.send', 'gmail.users.drafts.send', 'gmail.users.drafts.create', 'gmail.users.messages.insert', 'gmail.users.messages.import']);
64
62
  export function collectBodyRecipients(body) {
65
63
  const out = [];
@@ -1,2 +1,2 @@
1
- export declare function setHttpReauthBase(base: string | null): void;
1
+ export declare function setHttpReauthLink(linker: ((alias: string) => string) | null): void;
2
2
  export declare function reauthHint(account: string): string;
@@ -1,15 +1,16 @@
1
- // B13 in-call re-auth (BV-1): a dead/missing per-account Google token is a
2
- // leg-C failure. Over stdio the fix is the `auth --account` CLI; over HTTP the
3
- // user cannot run a CLI, so the hint becomes a clickable link into the AS's
4
- // clientless alias_reauth flow (${BASE}/authorize?flow=alias_reauth&alias=…).
5
- // The HTTP bootstrap sets the base when the AS starts; stdio never does.
6
- let httpReauthBase = null;
7
- export function setHttpReauthBase(base) {
8
- httpReauthBase = base;
1
+ // Over stdio the fix is the `auth --account` CLI; over HTTP it's a clickable,
2
+ // server-signed link instead, installed by the AS bootstrap. See docs/internals.md.
3
+ import { hints } from './hint-provider.js';
4
+ let httpReauthLink = null;
5
+ export function setHttpReauthLink(linker) {
6
+ httpReauthLink = linker;
9
7
  }
10
8
  export function reauthHint(account) {
11
- if (httpReauthBase) {
12
- return `Re-authenticate "${account}" in your browser, then retry: ${httpReauthBase}/authorize?flow=alias_reauth&alias=${encodeURIComponent(account)}`;
9
+ const rendered = hints()?.render({ kind: 'reauth', account });
10
+ if (rendered)
11
+ return rendered.hint;
12
+ if (httpReauthLink) {
13
+ return `Re-authenticate "${account}" in your browser, then retry: ${httpReauthLink(account)}`;
13
14
  }
14
15
  return `Run: npx mcp-google-multi auth --account ${account}`;
15
16
  }
@@ -1,3 +1,8 @@
1
+ /** Refuse-only for v1: export/import operate on the FLAT single-owner
2
+ * store. A tenant-namespaced layout means the bundle would either miss every
3
+ * tenant's tokens or exfiltrate all of them in one file — refuse instead.
4
+ * Returns the offending dir, or null when the store is flat. */
5
+ export declare function refuseIfMultiTenant(dir?: string): string | null;
1
6
  export interface TransferBundle {
2
7
  manifest: {
3
8
  v: 1;
@@ -1,17 +1,26 @@
1
- // B19: encrypted registry export / import (account-registry.md). Moves a whole
2
- // setup to another machine. The bundle is encrypted under a user PASSPHRASE
3
- // (portable across machines with different MASTER_KEYs) via the token-store AES
4
- // primitives; the bundled <alias>.enc token files stay encrypted under the
5
- // SOURCE MASTER_KEY (double-locked), so the target needs the same MASTER_KEY to
6
- // use them. Secrets (MASTER_KEY / client secret) are never in the bundle.
1
+ // Encrypted registry export/import: the bundle is passphrase-encrypted, but
2
+ // the bundled <alias>.enc files stay double-locked under the SOURCE MASTER_KEY.
7
3
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
8
4
  import * as readline from 'node:readline';
9
5
  import path from 'node:path';
10
- import { encryptToken, decryptToken } from './token-store.js';
6
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
11
7
  import { atomicWriteFileSync, atomicWriteWithLock } from './fs-atomic.js';
12
- import { configFilePath, CONFIG_VERSION } from './config-file.js';
8
+ import { ALIAS_RE, configFilePath, CONFIG_VERSION, tenantsDir } from './config-file.js';
13
9
  import { getTokenDir } from './accounts.js';
14
- const ALIAS_RE = /^[a-zA-Z0-9_-]+$/;
10
+ /** Refuse-only for v1: export/import operate on the FLAT single-owner
11
+ * store. A tenant-namespaced layout means the bundle would either miss every
12
+ * tenant's tokens or exfiltrate all of them in one file — refuse instead.
13
+ * Returns the offending dir, or null when the store is flat. */
14
+ export function refuseIfMultiTenant(dir = tenantsDir()) {
15
+ try {
16
+ if (readdirSync(dir).length > 0)
17
+ return dir;
18
+ }
19
+ catch {
20
+ // absent or unreadable tenants/ = flat store
21
+ }
22
+ return null;
23
+ }
15
24
  function resolveDeps(deps) {
16
25
  return {
17
26
  configPath: deps.configPath ?? configFilePath(),
@@ -48,15 +57,48 @@ export function buildTransferBundle(exportedAt, deps = {}) {
48
57
  }
49
58
  return { manifest: { v: 1, exportedAt, aliases: [...aliasSet].sort() }, config, tokens };
50
59
  }
60
+ // v2: a REAL passphrase KDF (scrypt, per-bundle salt). v1 reused the token
61
+ // store's unsalted-sha256 passphrase branch (CodeQL js/insufficient-password-hash) and is refused outright.
62
+ const BUNDLE_VERSION = 2;
63
+ const SCRYPT_PARAMS = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
64
+ function bundleKey(passphrase, salt) {
65
+ return scryptSync(passphrase, salt, 32, SCRYPT_PARAMS);
66
+ }
51
67
  export function encryptBundle(bundle, passphrase) {
52
- return encryptToken(bundle, passphrase);
68
+ const salt = randomBytes(16);
69
+ const iv = randomBytes(12);
70
+ const cipher = createCipheriv('aes-256-gcm', bundleKey(passphrase, salt), iv);
71
+ const data = Buffer.concat([cipher.update(Buffer.from(JSON.stringify(bundle), 'utf8')), cipher.final()]);
72
+ const file = {
73
+ bv: BUNDLE_VERSION,
74
+ salt: salt.toString('base64'),
75
+ iv: iv.toString('base64'),
76
+ tag: cipher.getAuthTag().toString('base64'),
77
+ data: data.toString('base64'),
78
+ };
79
+ return JSON.stringify(file);
53
80
  }
54
81
  export class BundleDecryptError extends Error {
55
82
  }
56
83
  export function decryptBundle(contents, passphrase) {
84
+ let file;
85
+ try {
86
+ file = JSON.parse(contents);
87
+ }
88
+ catch {
89
+ throw new BundleDecryptError('The file is not a registry export bundle.');
90
+ }
91
+ if (file.v !== undefined && file.bv === undefined) {
92
+ throw new BundleDecryptError('This bundle uses the retired v1 prerelease format (weak passphrase derivation); re-export it from the source machine with the current version.');
93
+ }
94
+ if (file.bv !== BUNDLE_VERSION || !file.salt || !file.iv || !file.tag || !file.data) {
95
+ throw new BundleDecryptError('The file is not a valid registry export (bad envelope).');
96
+ }
57
97
  let obj;
58
98
  try {
59
- obj = decryptToken(contents, passphrase);
99
+ const decipher = createDecipheriv('aes-256-gcm', bundleKey(passphrase, Buffer.from(file.salt, 'base64')), Buffer.from(file.iv, 'base64'));
100
+ decipher.setAuthTag(Buffer.from(file.tag, 'base64'));
101
+ obj = JSON.parse(Buffer.concat([decipher.update(Buffer.from(file.data, 'base64')), decipher.final()]).toString('utf8'));
60
102
  }
61
103
  catch {
62
104
  throw new BundleDecryptError('Could not decrypt the bundle — wrong passphrase or the file is corrupt.');
@@ -153,6 +195,11 @@ async function resolvePassphrase(prompt) {
153
195
  }
154
196
  }
155
197
  export async function runExportCli(argv) {
198
+ const mtDir = refuseIfMultiTenant();
199
+ if (mtDir) {
200
+ console.error(`E_MULTI_TENANT_EXPORT_REFUSED: tenant-namespaced token stores exist under ${mtDir}; account export only operates on a flat single-owner store.`);
201
+ return 2;
202
+ }
156
203
  const out = argFlag(argv, '--out');
157
204
  if (!out) {
158
205
  console.error('Usage: mcp-google-multi account export --out <bundle.enc>');
@@ -170,6 +217,11 @@ export async function runExportCli(argv) {
170
217
  return 0;
171
218
  }
172
219
  export async function runImportCli(argv) {
220
+ const mtDir = refuseIfMultiTenant();
221
+ if (mtDir) {
222
+ console.error(`E_MULTI_TENANT_EXPORT_REFUSED: tenant-namespaced token stores exist under ${mtDir}; account import only operates on a flat single-owner store.`);
223
+ return 2;
224
+ }
173
225
  const file = argv[argv.indexOf('import') + 1];
174
226
  if (!file || file.startsWith('--')) {
175
227
  console.error('Usage: mcp-google-multi account import <bundle.enc> [--replace]');
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server";
2
2
  import { z } from 'zod';
3
3
  import { type Policy } from './write-control.js';
4
+ import { type AccountSet } from './accounts.js';
4
5
  import type { ArgShape } from './arg-normalize.js';
5
6
  import type { Metrics } from './usage-metrics.js';
6
7
  export type Cud = 'read' | 'create' | 'update' | 'delete';
@@ -37,18 +38,19 @@ export interface CatalogOperation {
37
38
  * real scope error rather than a bare "not found". */
38
39
  unreachable?: true;
39
40
  }
41
+ /** The service a tool name belongs to: its prefix, unless overridden. */
42
+ export declare function serviceOf(name: string): string;
40
43
  export declare function inferCud(name: string): Cud;
41
44
  export declare class ToolRegistry {
42
45
  private readonly server;
43
46
  private readonly metrics;
47
+ private readonly accounts;
44
48
  readonly tools: ToolEntry[];
45
49
  readonly policy: Policy;
46
50
  readonly registerTool: McpServer['registerTool'];
47
51
  private readonly revealed;
48
52
  private readonly jsonSchemaCache;
49
53
  private readonly argShapeCache;
50
- /** Memoized isUngrantable verdict per tool: the answer depends on the frozen
51
- * bundle catalog only, so it never changes within a process. */
52
54
  private readonly ungrantable;
53
55
  private readonly compactOutput;
54
56
  private registeringMeta;
@@ -57,9 +59,15 @@ export declare class ToolRegistry {
57
59
  /** Agent-toggled runtime overlay (discover_all / discover_reset): lifts a
58
60
  * lazy surface to curated without touching the configured mode. */
59
61
  private expanded;
60
- constructor(server: McpServer, policy: Policy, mode?: DiscoveryMode, metrics?: Metrics | null);
62
+ constructor(server: McpServer, policy: Policy, mode?: DiscoveryMode, metrics?: Metrics | null, accounts?: () => AccountSet);
61
63
  registerMeta: McpServer['registerTool'];
62
64
  services(): string[];
65
+ /** The registry's OWN alias list: registration-time schema builders (the
66
+ * generated account enums) read this, never the process global. */
67
+ accountAliases(): readonly string[];
68
+ /** The registry's OWN account set: handlers needing an account's email or
69
+ * profile read this, never the process global. */
70
+ accountSet(): AccountSet;
63
71
  /** Declared input-schema keys + scalar kinds for one tool (tools/call arg
64
72
  * normalization; the kind drives value coercion on renamed keys). */
65
73
  argShape(name: string): ArgShape | undefined;
@@ -96,7 +104,7 @@ export declare class ToolRegistry {
96
104
  /** discover_all: advertise the full curated set at once. Idempotent. */
97
105
  expand(): boolean;
98
106
  /** discover_reset: back to the lean meta-only surface. Clears reveals too.
99
- * BV-8: if a client mishandles a SHRINKING tools/list, this degrades to a
107
+ * If a client mishandles a SHRINKING tools/list, this degrades to a
100
108
  * no-op for that session — tools stay callable regardless (graceful
101
109
  * dispatch), zero correctness impact. */
102
110
  collapse(): boolean;
@@ -118,4 +126,5 @@ export declare class ToolRegistry {
118
126
  };
119
127
  installListHandler(): void;
120
128
  private toToolJson;
129
+ private withLiveAccountEnum;
121
130
  }