mcp-google-multi 6.0.0-alpha.71 → 6.0.0-alpha.72

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 (69) hide show
  1. package/README.md +5 -5
  2. package/dist/accounts.d.ts +3 -3
  3. package/dist/accounts.js +11 -11
  4. package/dist/arg-strict.js +1 -1
  5. package/dist/auth.d.ts +1 -1
  6. package/dist/auth.js +3 -3
  7. package/dist/boot-gates.d.ts +1 -1
  8. package/dist/boot-gates.js +2 -2
  9. package/dist/cimd-cache.js +1 -1
  10. package/dist/client-config.d.ts +2 -2
  11. package/dist/client-config.js +4 -4
  12. package/dist/compose.js +1 -1
  13. package/dist/discover.js +2 -2
  14. package/dist/doctor.d.ts +2 -2
  15. package/dist/doctor.js +8 -9
  16. package/dist/http-config.d.ts +2 -2
  17. package/dist/http-config.js +4 -5
  18. package/dist/http-transport.d.ts +5 -5
  19. package/dist/http-transport.js +7 -7
  20. package/dist/identity.d.ts +2 -2
  21. package/dist/identity.js +4 -4
  22. package/dist/index.js +9 -9
  23. package/dist/master-key.d.ts +3 -3
  24. package/dist/master-key.js +5 -5
  25. package/dist/mcp-token.d.ts +3 -3
  26. package/dist/mcp-token.js +6 -6
  27. package/dist/migrate-config.js +1 -1
  28. package/dist/oauth-as.d.ts +1 -1
  29. package/dist/oauth-as.js +11 -11
  30. package/dist/oauth-consent.js +3 -3
  31. package/dist/outbound-allowlist.js +2 -2
  32. package/dist/reauth-hint.js +3 -3
  33. package/dist/registry-transfer.d.ts +1 -1
  34. package/dist/registry-transfer.js +2 -2
  35. package/dist/registry.d.ts +1 -1
  36. package/dist/registry.js +4 -4
  37. package/dist/scope-catalog.d.ts +5 -0
  38. package/dist/scope-catalog.js +13 -0
  39. package/dist/scope-observability.d.ts +1 -1
  40. package/dist/scope-observability.js +1 -1
  41. package/dist/ssrf-guard.js +3 -4
  42. package/dist/tenant-purge.js +1 -1
  43. package/dist/tools/_errors.js +4 -4
  44. package/dist/tools/account-wizard.d.ts +4 -3
  45. package/dist/tools/account-wizard.js +27 -12
  46. package/dist/tools/accounts-tool.d.ts +1 -1
  47. package/dist/tools/accounts-tool.js +1 -1
  48. package/dist/tools/admin.js +1 -1
  49. package/dist/tools/analytics.js +1 -1
  50. package/dist/tools/calendar.js +1 -1
  51. package/dist/tools/chat.js +1 -1
  52. package/dist/tools/contacts.js +4 -4
  53. package/dist/tools/docs.js +1 -1
  54. package/dist/tools/drive.js +1 -1
  55. package/dist/tools/forms.js +1 -1
  56. package/dist/tools/gmail-mime.d.ts +1 -1
  57. package/dist/tools/gmail-mime.js +3 -3
  58. package/dist/tools/gmail.d.ts +2 -2
  59. package/dist/tools/gmail.js +8 -8
  60. package/dist/tools/google-api.js +1 -1
  61. package/dist/tools/meet.js +1 -1
  62. package/dist/tools/searchconsole.js +1 -1
  63. package/dist/tools/sheets.js +1 -1
  64. package/dist/tools/slides.js +1 -1
  65. package/dist/tools/tasks.js +1 -1
  66. package/dist/usage-metrics.js +1 -1
  67. package/dist/write-control.d.ts +4 -5
  68. package/dist/write-control.js +1 -1
  69. package/package.json +1 -1
package/README.md CHANGED
@@ -44,7 +44,7 @@ New to all this? It's written for someone who just installed Claude Code and has
44
44
  5. **Add it to Claude Code, then restart Claude Code:**
45
45
 
46
46
  ```bash
47
- claude mcp add google-multi -s user -- npx -y mcp-google-multi
47
+ claude mcp add mcp-google-multi -s user -- npx -y mcp-google-multi
48
48
  ```
49
49
 
50
50
  **Stuck at any point? Run `mcp-google-multi doctor`.** It inspects every part and prints the exact fix for anything wrong (a missing sign-in, a Google API you still need to switch on, and so on). Once it reads all-green, just talk to Claude: *"summarize my unread email."*
@@ -55,14 +55,14 @@ New to all this? It's written for someone who just installed Claude Code and has
55
55
 
56
56
  **Go deeper:** [Configuration reference](./docs/configuration.md) · [What's covered](./COVERAGE.md) · [Features tour](./docs/features.md) · [Remote / HTTP setup](./docs/http-setup.md) · [Secrets in a vault](./docs/secrets.md) · [Local usage metrics](./docs/usage-metrics.md) · [Migrating to v6](./MIGRATION-v6.md) · [Security policy](./SECURITY.md) · [Roadmap](https://github.com/bakissation/mcp-google-multi/milestones)
57
57
 
58
- ## Maintainer & credits
58
+ ## Maintainer, sponsor & license
59
59
 
60
60
  Built and maintained by **Abdelbaki Berkati** — [berkati.xyz](https://berkati.xyz) · [@bakissation](https://github.com/bakissation). [Read the case study →](https://berkati.xyz/case-studies/mcp-google-multi/)
61
61
 
62
62
  Development is **funded by [IdeaCrafters](https://ideacrafters.com)** ([@IdeaCraftersHQ](https://github.com/IdeaCraftersHQ)) — the studio that pays for this OSS to exist.
63
63
 
64
- Thanks to contributors [@obatried](https://github.com/obatried), [@trevor-commits](https://github.com/trevor-commits), and [@mjreddy](https://github.com/mjreddy). The project is maintainer-led (roadmap on [Milestones](https://github.com/bakissation/mcp-google-multi/milestones); bug reports welcome, feature PRs by prior agreement — see [CONTRIBUTING.md](./CONTRIBUTING.md)). **Feedback shapes the roadmap: [open an issue](https://github.com/bakissation/mcp-google-multi/issues/new/choose) with bugs, pain points, or what you wish it did.** Security reports go to [SECURITY.md](./SECURITY.md), never a public issue.
64
+ An Enterprise Edition (hosted, multi-tenant) is available: ask through [berkati.xyz](https://berkati.xyz) (email or LinkedIn there).
65
65
 
66
- ## License
66
+ Thanks to contributors [@obatried](https://github.com/obatried), [@trevor-commits](https://github.com/trevor-commits), and [@mjreddy](https://github.com/mjreddy). The project is maintainer-led (roadmap on [Milestones](https://github.com/bakissation/mcp-google-multi/milestones); bug reports welcome, feature PRs by prior agreement — see [CONTRIBUTING.md](./CONTRIBUTING.md)). **Feedback shapes the roadmap: [open an issue](https://github.com/bakissation/mcp-google-multi/issues/new/choose) with bugs, pain points, or what you wish it did.** Security reports go to [SECURITY.md](./SECURITY.md), never a public issue.
67
67
 
68
- [MIT](./LICENSE)
68
+ Licensed under [MIT](./LICENSE).
@@ -28,7 +28,7 @@ export declare function getAccountSet(): AccountSet;
28
28
  /** Re-resolve after a config.json mutation and swap the live set. */
29
29
  export declare function invalidateAccountSet(): AccountSet;
30
30
  export declare function refreshAccountSetIfStale(): void;
31
- /** Cross-process staleness probe (BR-7): one stat, compared against the stamp. */
31
+ /** Cross-process staleness probe: one stat, compared against the stamp. */
32
32
  export declare function isAccountSetStale(): boolean;
33
33
  /** Account aliases (possibly empty on a fresh install).
34
34
  * Snapshot from the initial load; enums widen only when the registry is
@@ -43,7 +43,7 @@ export declare function unknownAliasMessage(aliases: readonly string[], selector
43
43
  * The `account` param schema, empty-registry-safe. A `z.enum` requires at least
44
44
  * one value, so a fresh install (zero aliases) would throw at schema-build time
45
45
  * (module load) and take down every CLI — including `doctor`, which is meant to
46
- * REPORT the empty registry (gap #23). Fall back to a plain string when there
46
+ * REPORT the empty registry. Fall back to a plain string when there
47
47
  * are no aliases: no alias exists to enumerate, the server refuses to boot empty
48
48
  * anyway, and dispatch validates the account against the live set. */
49
49
  export declare function accountAliasSchemaFor(aliases: readonly string[]): z.ZodType<string>;
@@ -62,7 +62,7 @@ export declare function liveAccountCheck(aliases: () => readonly string[], selec
62
62
  /** True for a field built on liveAccountCheck, optional or not. */
63
63
  export declare function isLiveAccountField(field: unknown): boolean;
64
64
  /**
65
- * BR-4: the stdio/http SERVER never boots with an empty registry — a fresh user
65
+ * The stdio/http SERVER never boots with an empty registry — a fresh user
66
66
  * bootstraps via env / `migrate-config` / `account import` / `auth` first. The
67
67
  * bootstrap and diagnostic CLIs return before this guard, so it gates only the
68
68
  * server path (called from index.ts after the CLI branches). */
package/dist/accounts.js CHANGED
@@ -67,7 +67,7 @@ function noAccountsMessage() {
67
67
  `or set GOOGLE_ACCOUNTS=work:user@company.com,personal:user@gmail.com in the environment.${envHint}`);
68
68
  }
69
69
  /**
70
- * Registry resolution (BR-2): a non-empty GOOGLE_ACCOUNTS env takes the WHOLE
70
+ * Registry resolution: a non-empty GOOGLE_ACCOUNTS env takes the WHOLE
71
71
  * registry from env (12-factor override, never merged key-by-key); otherwise
72
72
  * the registry comes from config.json. GOOGLE_ADMIN_ACCOUNTS, when NON-EMPTY,
73
73
  * overrides per-account admin flags from the file (env var > config field);
@@ -88,7 +88,7 @@ opts = {}) {
88
88
  };
89
89
  // Validate the legacy global override AT BOOT, whatever GOOGLE_TOOLSETS
90
90
  // selects — otherwise a typo only surfaces at dispatch time inside
91
- // account_list instead of the promised startup error (BR3).
91
+ // account_list instead of the promised startup error.
92
92
  const legacyNames = parseCsv(env.GOOGLE_OPTIONAL_SCOPES);
93
93
  if (legacyNames.length > 0) {
94
94
  for (const bundle of resolveBundleAliases(legacyNames)) {
@@ -130,9 +130,9 @@ opts = {}) {
130
130
  // Gap #23: an empty registry is NOT fatal at resolve time — the bootstrap and
131
131
  // diagnostic CLIs (doctor/reset/account import/migrate-config/config check)
132
132
  // must run on a fresh install. Only the stdio/http SERVER refuses to boot
133
- // empty (BR-4), enforced by assertServerAccountsConfigured() in index.ts.
133
+ // empty, enforced by assertServerAccountsConfigured() in index.ts.
134
134
  // The dispatch-path reload ('throw') still throws so a mid-session emptied
135
- // config.json keeps the last-good registry (BR-7) instead of dropping tools.
135
+ // config.json keeps the last-good registry instead of dropping tools.
136
136
  if (onInvalid === 'throw' && !opts.emptyOk) {
137
137
  throw new Error(`E_NO_ACCOUNTS_CONFIGURED: ${noAccountsMessage()}`);
138
138
  }
@@ -144,7 +144,7 @@ opts = {}) {
144
144
  stamp: `${config?.version ?? CONFIG_VERSION}:${preStamp.split(':')[1]}`,
145
145
  };
146
146
  }
147
- // BR3: unknown bundle names fail loudly (a mis-scoped token is worse than a
147
+ // Unknown bundle names fail loudly (a mis-scoped token is worse than a
148
148
  // clear error); v5 silently filtered them. Null prototype: profile names are
149
149
  // user input and must never collide with Object.prototype members.
150
150
  const scopeProfiles = Object.create(null);
@@ -187,7 +187,7 @@ opts = {}) {
187
187
  ...def,
188
188
  };
189
189
  }
190
- /** A2: env GOOGLE_DEFAULT_ACCOUNT > config.defaultAccount > unset. A configured
190
+ /** env GOOGLE_DEFAULT_ACCOUNT > config.defaultAccount > unset. A configured
191
191
  * default naming an unknown alias refuses to start (E_DEFAULT_ACCOUNT_UNKNOWN —
192
192
  * deliberately NOT E_CONFIG_INVALID: the config is schema-valid). */
193
193
  function resolveDefaultAccount(env, fromConfig, aliases, fail) {
@@ -208,7 +208,7 @@ export function fileStamp(filePath, version) {
208
208
  return `${version}:0`;
209
209
  }
210
210
  }
211
- // First-run shim (BC6): env is set and no config.json exists yet — materialize
211
+ // First-run shim: env is set and no config.json exists yet — materialize
212
212
  // the file so the wizard has something to edit. Env still wins this session;
213
213
  // a write failure must never block boot (warn on stderr and continue).
214
214
  function materializeFirstRun(aliases, configs, filePath) {
@@ -251,7 +251,7 @@ export function invalidateAccountSet() {
251
251
  return current;
252
252
  }
253
253
  /**
254
- * Dispatch-path reload (BR-7): NEVER exits and never throws — a mid-edit,
254
+ * Dispatch-path reload: NEVER exits and never throws — a mid-edit,
255
255
  * corrupt, or deleted config.json keeps the last-good set and warns once per
256
256
  * distinct failure on stderr. failStartup semantics are boot/CLI-only.
257
257
  */
@@ -271,7 +271,7 @@ export function refreshAccountSetIfStale() {
271
271
  }
272
272
  }
273
273
  }
274
- /** Cross-process staleness probe (BR-7): one stat, compared against the stamp. */
274
+ /** Cross-process staleness probe: one stat, compared against the stamp. */
275
275
  export function isAccountSetStale() {
276
276
  if (current.source !== 'file')
277
277
  return false;
@@ -297,7 +297,7 @@ export function unknownAliasMessage(aliases, selectors = false) {
297
297
  * The `account` param schema, empty-registry-safe. A `z.enum` requires at least
298
298
  * one value, so a fresh install (zero aliases) would throw at schema-build time
299
299
  * (module load) and take down every CLI — including `doctor`, which is meant to
300
- * REPORT the empty registry (gap #23). Fall back to a plain string when there
300
+ * REPORT the empty registry. Fall back to a plain string when there
301
301
  * are no aliases: no alias exists to enumerate, the server refuses to boot empty
302
302
  * anyway, and dispatch validates the account against the live set. */
303
303
  export function accountAliasSchemaFor(aliases) {
@@ -339,7 +339,7 @@ export function isLiveAccountField(field) {
339
339
  return def?.checks?.some((c) => LIVE_ACCOUNT_CHECKS.has(c)) ?? false;
340
340
  }
341
341
  /**
342
- * BR-4: the stdio/http SERVER never boots with an empty registry — a fresh user
342
+ * The stdio/http SERVER never boots with an empty registry — a fresh user
343
343
  * bootstraps via env / `migrate-config` / `account import` / `auth` first. The
344
344
  * bootstrap and diagnostic CLIs return before this guard, so it gates only the
345
345
  * server path (called from index.ts after the CLI branches). */
@@ -43,7 +43,7 @@ export const STRICT_EXEMPT_TOOLS = new Set();
43
43
  * no arguments; the others are call-plumbing that bridges and proxies have
44
44
  * been observed to fold into `arguments` instead of `params._meta`, where the
45
45
  * spec puts them. Rejecting these would fail a call the model got right.
46
- * Measured against the full 978-tool surface: none is a declared key. */
46
+ * Measured against the full tool surface: none is a declared key. */
47
47
  const CLIENT_ARTIFACT_KEYS = new Set([
48
48
  'random_string',
49
49
  'toolCallId',
package/dist/auth.d.ts CHANGED
@@ -4,7 +4,7 @@ export declare const BASE_SCOPES: string[];
4
4
  export declare const OPTIONAL_SCOPE_BUNDLES: Record<string, string[]>;
5
5
  export { ADMIN_SCOPES };
6
6
  /** Union of every account's resolved bundles: a service registers if ANY
7
- * account can authorize it; per-account authz happens at call time (BR2). */
7
+ * account can authorize it; per-account authz happens at call time. */
8
8
  export declare function getOptionalBundles(set?: AccountSet): string[];
9
9
  /** Aliases granted ADMIN_SCOPES: per-account admin flag (env
10
10
  * GOOGLE_ADMIN_ACCOUNTS overrides config.json at resolve) OR the account's
package/dist/auth.js CHANGED
@@ -32,9 +32,9 @@ function parseCsvEnv(name) {
32
32
  .filter(Boolean);
33
33
  }
34
34
  /**
35
- * Legacy global override (BC7): a set GOOGLE_OPTIONAL_SCOPES acts as an
35
+ * Legacy global override: a set GOOGLE_OPTIONAL_SCOPES acts as an
36
36
  * implicit "legacy-global" profile applied to every account (env wins over
37
- * file profiles, cc-config R2). Unknown names now fail loudly (BR3) where v5
37
+ * file profiles). Unknown names now fail loudly where v5
38
38
  * silently dropped them — the intended migration signal.
39
39
  */
40
40
  function legacyGlobalProfile() {
@@ -65,7 +65,7 @@ function profileForAccount(alias, set = getAccountSet()) {
65
65
  return Object.hasOwn(set.scopeProfiles, name) ? set.scopeProfiles[name] : { bundles: [] };
66
66
  }
67
67
  /** Union of every account's resolved bundles: a service registers if ANY
68
- * account can authorize it; per-account authz happens at call time (BR2). */
68
+ * account can authorize it; per-account authz happens at call time. */
69
69
  export function getOptionalBundles(set = getAccountSet()) {
70
70
  const legacy = legacyGlobalProfile();
71
71
  if (legacy)
@@ -11,7 +11,7 @@ export interface MtBootGates {
11
11
  export declare function setMtBootGates(g: MtBootGates | null): void;
12
12
  export declare function mtBootGates(): MtBootGates | null;
13
13
  export declare function isMultiTenantBoot(): boolean;
14
- /** BR-4 applies to stdio always, and to HTTP only when single-owner: a
14
+ /** The empty-registry guard applies to stdio always, and to HTTP only when single-owner: a
15
15
  * multi-tenant HTTP deploy starts empty by design. */
16
16
  export declare function accountsAssertRequired(wantStdio: boolean, multiTenant: boolean): boolean;
17
17
  /** A set GOOGLE_ACCOUNTS on a multi-tenant boot is refused at DEPLOY time:
@@ -1,4 +1,4 @@
1
- // S1.16: multi-tenant boot gates. The free core never sets these — every
1
+ // Multi-tenant boot gates. The free core never sets these — every
2
2
  // helper reduces to today's single-owner behavior when no gates are
3
3
  // installed. Deliberately a side-effect-free module (accounts.ts/auth.ts
4
4
  // resolve env at import time) so the tenancy module can import it cheaply.
@@ -13,7 +13,7 @@ export function mtBootGates() {
13
13
  export function isMultiTenantBoot() {
14
14
  return gates?.multiTenant === true;
15
15
  }
16
- /** BR-4 applies to stdio always, and to HTTP only when single-owner: a
16
+ /** The empty-registry guard applies to stdio always, and to HTTP only when single-owner: a
17
17
  * multi-tenant HTTP deploy starts empty by design. */
18
18
  export function accountsAssertRequired(wantStdio, multiTenant) {
19
19
  return wantStdio || !multiTenant;
@@ -1,4 +1,4 @@
1
- // H4 F6: GET /authorize is unauthenticated, and a client_id on an allowlisted
1
+ // GET /authorize is unauthenticated, and a client_id on an allowlisted
2
2
  // issuer with a path never seen before forces an outbound fetch. So the
3
3
  // fetches running at once are capped, one client_id is fetched once however
4
4
  // many requests wait on it, and only a document that passed validation is
@@ -38,7 +38,7 @@ export interface EntryOptions {
38
38
  }
39
39
  /**
40
40
  * The client `mcpServers` entry. stdio needs no secret env: the server loads
41
- * its own .env from ~/.config/mcp-google-multi (cc-config R1), so we never
41
+ * its own .env from ~/.config/mcp-google-multi, so we never
42
42
  * inline MASTER_KEY / GOOGLE_CLIENT_SECRET into a world-readable client config.
43
43
  */
44
44
  export declare function buildServerEntry(opts: EntryOptions): ServerEntry;
@@ -73,7 +73,7 @@ export type ApplyResult = {
73
73
  * the prior file, update-in-place if the key exists.
74
74
  */
75
75
  export declare function applyFileEntry(client: ClientInfo, name: string, entry: ServerEntry, deps?: ClientDeps): ApplyResult;
76
- /** Resolve stdio-vs-http mode from the current transport config (B11).
76
+ /** Resolve stdio-vs-http mode from the current transport config.
77
77
  * http/both → a `url` entry; stdio → a `command` entry. */
78
78
  export declare function resolveMode(httpConfig?: {
79
79
  transport: 'stdio' | 'http' | 'both';
@@ -1,9 +1,9 @@
1
- // B15: after the setup wizard finalizes an account, offer to register the
1
+ // After the setup wizard finalizes an account, offer to register the
2
2
  // server with the user's MCP client so they never hand-edit JSON. Pure core
3
3
  // (detection + entry building + idempotent apply), deps-injectable for tests.
4
4
  // Consent + non-TTY behavior live in the CLI/tool surfaces; secrets are NEVER
5
5
  // inlined — the server self-loads its .env from ~/.config/mcp-google-multi, so
6
- // a stdio entry needs no secret env at all (distribution.md B15).
6
+ // a stdio entry needs no secret env at all.
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
8
8
  import * as readline from 'node:readline';
9
9
  import path from 'node:path';
@@ -78,7 +78,7 @@ export function detectClients(deps = {}) {
78
78
  }
79
79
  /**
80
80
  * The client `mcpServers` entry. stdio needs no secret env: the server loads
81
- * its own .env from ~/.config/mcp-google-multi (cc-config R1), so we never
81
+ * its own .env from ~/.config/mcp-google-multi, so we never
82
82
  * inline MASTER_KEY / GOOGLE_CLIENT_SECRET into a world-readable client config.
83
83
  */
84
84
  export function buildServerEntry(opts) {
@@ -166,7 +166,7 @@ export function applyFileEntry(client, name, entry, deps = {}) {
166
166
  r.writeFile(p, `${JSON.stringify(next, null, 2)}\n`);
167
167
  return { ok: true, client: client.id, action: had ? 'updated' : 'added', path: p, backup };
168
168
  }
169
- /** Resolve stdio-vs-http mode from the current transport config (B11).
169
+ /** Resolve stdio-vs-http mode from the current transport config.
170
170
  * http/both → a `url` entry; stdio → a `command` entry. */
171
171
  export function resolveMode(httpConfig) {
172
172
  if (httpConfig && (httpConfig.transport === 'http' || httpConfig.transport === 'both')) {
package/dist/compose.js CHANGED
@@ -1,4 +1,4 @@
1
- // S1.12: registry composition, extracted from index.ts so the tenancy module
1
+ // Registry composition, extracted from index.ts so the tenancy module
2
2
  // (and tests) can build registries without touching the CLI entrypoint. This
3
3
  // module must stay free of import-time side effects of its own: no argv
4
4
  // parsing, no process.exit, no prints (accounts.js module-load resolution is
package/dist/discover.js CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { describePolicy } from './write-control.js';
3
3
  import { hints } from './hint-provider.js';
4
4
  // The description budget is the whole point of lazy mode: a full op list per
5
- // service put lazy tools/list at ~11.5k tokens (RQ2), so descriptions carry a
5
+ // service put lazy tools/list at ~11.5k tokens, so descriptions carry a
6
6
  // CAPPED vocabulary and the complete catalog stays in this tool's RESULT.
7
7
  const CURATED_OPS_CAP = 10;
8
8
  const GENERATED_GROUPS_CAP = 6;
@@ -31,7 +31,7 @@ function opVocabulary(registry, service) {
31
31
  }
32
32
  export function registerDiscoverTools(registry, policy) {
33
33
  const registerMeta = registry.registerMeta;
34
- // Agent-controllable runtime expansion (D3 refinement): reveal the whole
34
+ // Agent-controllable runtime expansion: reveal the whole
35
35
  // curated quality layer for heavy Google work, collapse to reclaim the
36
36
  // name/schema context budget after. stdio-only semantics — over stateless
37
37
  // HTTP the mode is forced curated and these are no-ops.
package/dist/doctor.d.ts CHANGED
@@ -52,7 +52,7 @@ export interface HttpProbeResult {
52
52
  unreachable?: boolean;
53
53
  problem?: string;
54
54
  }
55
- /** Console deep-link to enable one API (section-6 hint, error taxonomy B10). */
55
+ /** Console deep-link to enable one API (section-6 hint). */
56
56
  export declare function apiEnableLink(api: string): string;
57
57
  /** One line, state AND source, so the people being measured can see both
58
58
  * here and in `diagnose` (metrics spec section 2). Read-only. */
@@ -64,7 +64,7 @@ export declare function runDiagnostics(deps?: DiagnosticsDeps, opts?: {
64
64
  }): Promise<DiagnosticsReport>;
65
65
  /** doctor CLI (brew-doctor style): glyph-coded human report. */
66
66
  export declare function renderDoctorText(report: DiagnosticsReport): string;
67
- /** Redacted, paste-ready bug report (BR8): verdicts + provenance labels + token
67
+ /** Redacted, paste-ready bug report: verdicts + provenance labels + token
68
68
  * statuses + slugs; NO token values, secrets, keys, or full email addresses. */
69
69
  export declare function renderReport(report: DiagnosticsReport, version: string): string;
70
70
  /** Exit non-zero when any section FAILs; with strict, WARN counts too. */
package/dist/doctor.js CHANGED
@@ -37,7 +37,7 @@ const DEFAULT_DEPS = {
37
37
  };
38
38
  /** §7 live check: the advertised OAuth metadata must derive from MCP_PUBLIC_URL
39
39
  * exactly — one mismatch between PRM `resource` / AS `issuer` and what clients
40
- * compute from the public URL is the perpetual-401 interop bug (BR4). */
40
+ * compute from the public URL is the perpetual-401 interop bug. */
41
41
  async function probeHttpEndpoints(cfg) {
42
42
  try {
43
43
  const prmRes = await fetch(`${cfg.publicUrl}/.well-known/oauth-protected-resource`, {
@@ -66,7 +66,7 @@ async function probeHttpEndpoints(cfg) {
66
66
  return { ok: false, unreachable: true };
67
67
  }
68
68
  }
69
- /** Console deep-link to enable one API (section-6 hint, error taxonomy B10). */
69
+ /** Console deep-link to enable one API (section-6 hint). */
70
70
  export function apiEnableLink(api) {
71
71
  return `https://console.cloud.google.com/apis/library/${api}.googleapis.com`;
72
72
  }
@@ -75,8 +75,8 @@ function sectionRuntime(deps, tenantScope = false) {
75
75
  const major = Number.parseInt(deps.nodeVersion.split('.')[0] ?? '0', 10);
76
76
  if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
77
77
  if (tenantScope) {
78
- // The host version is operator infrastructure, not the tenant's (E7
79
- // residue): no version string, and the fix is the operator's to apply.
78
+ // The host version is operator infrastructure, not the tenant's: no
79
+ // version string, and the fix is the operator's to apply.
80
80
  const rendered = hints()?.render({ kind: 'operator_only', slug: 'E_NODE_TOO_OLD' });
81
81
  return {
82
82
  id: 1,
@@ -240,8 +240,7 @@ async function sectionApiEnablement(deps, aliases, tenantScope = false) {
240
240
  }
241
241
  catch (e) {
242
242
  // Network / transient: WARN with the target, never crash the report. The
243
- // raw text can name an egress or proxy host (E7 residue): tenant scope
244
- // drops it.
243
+ // raw text can name an egress or proxy host: tenant scope drops it.
245
244
  return {
246
245
  id: 6,
247
246
  title: 'API enablement',
@@ -381,8 +380,8 @@ export function renderDoctorText(report) {
381
380
  }
382
381
  const EMAIL_LOCAL_CHAR = /[A-Za-z0-9._%+-]/;
383
382
  const EMAIL_DOMAIN_CHAR = /[A-Za-z0-9.-]/;
384
- /** Mask the local-part of an email so a report never carries a full address
385
- * (BR8). A linear scan, not a regex: the `local* @domain` pattern backtracks
383
+ /** Mask the local-part of an email so a report never carries a full address.
384
+ * A linear scan, not a regex: the `local* @domain` pattern backtracks
386
385
  * polynomially on long local-charset runs without an @ (CodeQL
387
386
  * js/polynomial-redos), and report text can embed arbitrary error strings. */
388
387
  function maskEmails(text) {
@@ -409,7 +408,7 @@ function maskEmails(text) {
409
408
  }
410
409
  return out;
411
410
  }
412
- /** Redacted, paste-ready bug report (BR8): verdicts + provenance labels + token
411
+ /** Redacted, paste-ready bug report: verdicts + provenance labels + token
413
412
  * statuses + slugs; NO token values, secrets, keys, or full email addresses. */
414
413
  export function renderReport(report, version) {
415
414
  const out = [`mcp-google-multi doctor report (v${version})`, `overall: ${report.verdict}`, ''];
@@ -11,7 +11,7 @@ export interface HttpConfig {
11
11
  transport: Transport;
12
12
  host: string;
13
13
  port: number;
14
- /** Canonical public base URL (BR4): lowercase scheme+host, no default port,
14
+ /** Canonical public base URL: lowercase scheme+host, no default port,
15
15
  * no trailing slash, no fragment/query. The issuer and every advertised
16
16
  * endpoint derive from this. */
17
17
  publicUrl: string;
@@ -27,7 +27,7 @@ export interface HttpConfig {
27
27
  export declare function transportIncludesHttp(t: Transport): boolean;
28
28
  export declare function resolveTransport(env?: NodeJS.ProcessEnv): Transport;
29
29
  /**
30
- * Canonicalize a public base URL per BR4. WHATWG `URL` already lowercases the
30
+ * Canonicalize a public base URL. WHATWG `URL` already lowercases the
31
31
  * scheme+host and omits a default port from `.host`; this adds the HTTPS-only
32
32
  * scheme check, strips fragment/query/userinfo, and removes a trailing slash.
33
33
  */
@@ -1,7 +1,6 @@
1
- // Transport selection + public-base-URL / Origin / host config plumbing (B11).
2
- // Pure, resolved once at boot; the HTTP host that consumes this lands in B12
3
- // (cc-transport-hosting) and the AS metadata in B13 (oauth-authorization-server).
4
- // Owns the canonicalization rule (cc-transport-hosting BR4): a single mismatch
1
+ // Transport selection + public-base-URL / Origin / host config plumbing.
2
+ // Pure, resolved once at boot; consumed by the HTTP host and the AS metadata.
3
+ // Owns the canonicalization rule: a single mismatch
5
4
  // between the advertised resource URI and the client's `resource`/`aud` = a
6
5
  // perpetual 401 loop (the #1 interop bug), so it is normalized exactly once.
7
6
  export const DEFAULT_HTTP_HOST = '127.0.0.1';
@@ -30,7 +29,7 @@ export function resolveTransport(env = process.env) {
30
29
  throw new HttpConfigError('E_INVALID_TRANSPORT', `MCP_TRANSPORT must be one of stdio|http|both (got "${env.MCP_TRANSPORT}")`);
31
30
  }
32
31
  /**
33
- * Canonicalize a public base URL per BR4. WHATWG `URL` already lowercases the
32
+ * Canonicalize a public base URL. WHATWG `URL` already lowercases the
34
33
  * scheme+host and omits a default port from `.host`; this adds the HTTPS-only
35
34
  * scheme check, strips fragment/query/userinfo, and removes a trailing slash.
36
35
  */
@@ -12,9 +12,9 @@ export type AuthOutcome = {
12
12
  body: string;
13
13
  headers?: Record<string, string>;
14
14
  };
15
- /** Bearer / owner check for POST /mcp. B12 default = loopback-owner; B13 swaps in JWT verify. */
15
+ /** Bearer / owner check for POST /mcp. Default = loopback-owner; the AS swaps in JWT verify. */
16
16
  export type Authenticator = (req: IncomingMessage) => AuthOutcome | Promise<AuthOutcome>;
17
- /** A mounted extra route (the AS endpoints, B13). Return true if it wrote a response. */
17
+ /** A mounted extra route (the AS endpoints). Return true if it wrote a response. */
18
18
  export type RouteHandler = (req: IncomingMessage, res: ServerResponse, url: URL) => boolean | Promise<boolean>;
19
19
  /** What a verified subject resolves to: ITS server plus the per-request hooks
20
20
  * bound to that server's registry (tool shapes, declared keys, the default
@@ -27,7 +27,7 @@ export interface ServerTarget {
27
27
  validationEnvelope?: ValidationEnvelopeOptions;
28
28
  }
29
29
  export interface HttpHostOptions {
30
- /** The ONE McpServer + registry built at boot (P1 / BV gap #4: never per request). */
30
+ /** The ONE McpServer + registry built at boot (never per request). */
31
31
  server: McpServer;
32
32
  config: HttpConfig;
33
33
  version: string;
@@ -53,7 +53,7 @@ export interface HttpHostOptions {
53
53
  message: string;
54
54
  };
55
55
  };
56
- /** Extra routes keyed by exact pathname (AS endpoints mount here in B13). */
56
+ /** Extra routes keyed by exact pathname (AS endpoints mount here). */
57
57
  routes?: Record<string, RouteHandler>;
58
58
  log?: (line: string) => void;
59
59
  /** Max /mcp JSON body bytes (DoS guard). */
@@ -81,7 +81,7 @@ export interface HttpHostOptions {
81
81
  export declare function parseOwnerEmails(env?: NodeJS.ProcessEnv): string[];
82
82
  /** Front guard: an Origin, if present, must be allowlisted; a Host must be
83
83
  * allowlisted. Absent Origin passes (native/CLI/backend clients — the claude.ai
84
- * connector calls /mcp server-to-server with no Origin, BV-4). */
84
+ * connector calls /mcp server-to-server with no Origin). */
85
85
  export declare function originAllowed(origin: string | undefined, allowed: string[]): boolean;
86
86
  export declare function hostAllowed(host: string | undefined, allowed: string[]): boolean;
87
87
  /** JSON-RPC method name(s) for an observability log line: no params, no PII.
@@ -1,8 +1,8 @@
1
1
  import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
2
2
  // node:http server, the route table, the front guard (Host / Origin / DNS-rebind),
3
3
  // and the stateless per-request /mcp dispatch. The OAuth AS endpoints and Bearer
4
- // verification are a seam filled by B13 (oauth-authorization-server); B12 ships a
5
- // loopback-owner authenticator so the local-HTTP model works before the AS lands.
4
+ // verification are a seam filled by oauth-as.ts; the default is a loopback-owner
5
+ // authenticator so the local-HTTP model works without the AS.
6
6
  import { createServer } from 'node:http';
7
7
  import { withArgNormalization, withValidationEnvelope } from './arg-normalize.js';
8
8
  import { logSafe } from './trim.js';
@@ -16,7 +16,7 @@ const DISPATCH_TIMEOUT_DEFAULT = 120_000;
16
16
  * of a batch synchronously in one tick: without a cap one body multiplies the
17
17
  * per-call work by however many frames fit in it. */
18
18
  const MAX_BATCH_FRAMES = 16;
19
- // A resolveServer refusal's body reaches the wire verbatim (H3 PR1): validated
19
+ // A resolveServer refusal's body reaches the wire verbatim: validated
20
20
  // so a host cannot accidentally leak an operator path/env var through a wrongly
21
21
  // shaped slug or an overlong message. No new slug literal enters core itself —
22
22
  // the slug is always the host's own value.
@@ -42,7 +42,7 @@ export function parseOwnerEmails(env = process.env) {
42
42
  }
43
43
  /** Front guard: an Origin, if present, must be allowlisted; a Host must be
44
44
  * allowlisted. Absent Origin passes (native/CLI/backend clients — the claude.ai
45
- * connector calls /mcp server-to-server with no Origin, BV-4). */
45
+ * connector calls /mcp server-to-server with no Origin). */
46
46
  export function originAllowed(origin, allowed) {
47
47
  if (!origin)
48
48
  return true;
@@ -375,8 +375,8 @@ export class HttpTransportHost {
375
375
  return;
376
376
  }
377
377
  const sub = auth.sub ?? 'owner';
378
- // Tenant resolution (the seam EE fills): map the verified subject to ITS
379
- // server. Absent resolver = the one boot server for every subject.
378
+ // Tenant resolution (a multi-tenant host fills this seam): map the
379
+ // verified subject to ITS server. Absent resolver = the one boot server for every subject.
380
380
  let target = {
381
381
  server: this.opts.server,
382
382
  argShapeFor: this.opts.argShapeFor,
@@ -538,7 +538,7 @@ export class HttpTransportHost {
538
538
  health(res) {
539
539
  const { config, ownerConfigured, version } = this.opts;
540
540
  res.writeHead(200, { 'Content-Type': 'application/json' });
541
- // No secrets, no owner emails (cc-transport-hosting /health rule).
541
+ // No secrets, no owner emails.
542
542
  res.end(JSON.stringify({
543
543
  status: 'ok',
544
544
  transport: config.transport,
@@ -4,8 +4,8 @@ import { hasToken, readToken, updateToken, writeToken } from './token-store.js';
4
4
  import { type Policy, type Transport } from './write-control.js';
5
5
  /**
6
6
  * The one forward-compat seam (frozen public API): the free core builds
7
- * exactly one context with subject "owner"; EE later instantiates N contexts
8
- * from N registries. Nothing here may assume a global current-account.
7
+ * exactly one context with subject "owner"; a multi-tenant host instantiates
8
+ * N contexts from N registries. Nothing here may assume a global current-account.
9
9
  * `subject` is a string (not the literal 'owner') so a context can carry a
10
10
  * tenant id; the free core still always builds 'owner'.
11
11
  */
package/dist/identity.js CHANGED
@@ -11,8 +11,8 @@ export function isOwnerContext(ctx) {
11
11
  return ctx !== undefined && ownerContexts.has(ctx);
12
12
  }
13
13
  export function buildIdentityContext(env = process.env, opts = {}) {
14
- // Real closures over the single-owner state (the SAME factories EE
15
- // instantiates per tenant), bound here to the global registry + token dir —
14
+ // Real closures over the single-owner state (the SAME factories a
15
+ // multi-tenant host instantiates per tenant), bound here to the global registry + token dir —
16
16
  // behavior-identical to the module-level functions for the free core.
17
17
  const tokenStore = createTokenStore(getTokenDir());
18
18
  const ctx = {
@@ -22,8 +22,8 @@ export function buildIdentityContext(env = process.env, opts = {}) {
22
22
  get accounts() {
23
23
  return getAccountSet();
24
24
  },
25
- // The dispatch transport rides into the resolved policy as a reserved seam
26
- // (cc-write-control B14); it does not change any write-control verdict.
25
+ // The dispatch transport rides into the resolved policy as a reserved seam;
26
+ // it does not change any write-control verdict.
27
27
  policy: resolvePolicy(env, { transport: opts.transport }),
28
28
  getClient: makeGetClient('owner', {
29
29
  accounts: getAccountSet,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // First import: triggers accounts.js module load (env files + registry) before
3
- // anything else. Named to also pull the server-only empty-registry guard (BR-4).
3
+ // anything else. Named to also pull the server-only empty-registry guard.
4
4
  import { assertServerAccountsConfigured } from './accounts.js';
5
5
  import { accountsAssertRequired, assertNoEnvAccountsMode, assertNoEnvOptionalScopesMode, isMultiTenantBoot, mtBootGates } from './boot-gates.js';
6
6
  import { readFileSync } from 'node:fs';
@@ -139,7 +139,7 @@ async function main() {
139
139
  }
140
140
  const wantStdio = httpCfg.transport === 'stdio' || httpCfg.transport === 'both';
141
141
  const wantHttp = transportIncludesHttp(httpCfg.transport);
142
- // BR-4: the SERVER never boots with an empty registry — stdio always, HTTP
142
+ // The SERVER never boots with an empty registry — stdio always, HTTP
143
143
  // when single-owner. The bootstrap and diagnostic CLIs above already
144
144
  // returned. A multi-tenant boot instead refuses the two process-wide env
145
145
  // vectors that would leak one operator's accounts/scopes into every tenant.
@@ -186,8 +186,8 @@ async function main() {
186
186
  });
187
187
  }
188
188
  }
189
- // Build one McpServer + registry per transport at boot (P1 / BV gap #4:
190
- // never rebuilt per request); `both` runs the two concurrently.
189
+ // Build one McpServer + registry per transport at boot (never
190
+ // rebuilt per request); `both` runs the two concurrently.
191
191
  if (wantStdio) {
192
192
  const server = createMcpServer(pkg.version);
193
193
  const stdioMetrics = initMetricsFor('stdio', resolveDiscoveryMode());
@@ -226,14 +226,14 @@ async function main() {
226
226
  gates.assertProvisioningGate();
227
227
  }
228
228
  else {
229
- // BR3 / C13: the owner allowlist is the entire multi-tenant collapse;
229
+ // The owner allowlist is the entire multi-tenant collapse;
230
230
  // refuse to open an ungated HTTP endpoint.
231
231
  if (owners.length === 0) {
232
232
  process.stderr.write('E_OWNER_EMAILS_REQUIRED: MCP_TRANSPORT includes http but MCP_OWNER_EMAILS is empty. Set MCP_OWNER_EMAILS to the Google email(s) allowed to authenticate.\n');
233
233
  process.exit(1);
234
234
  }
235
235
  }
236
- // BR7: stateless HTTP cannot push tools/list_changed, so it forces curated.
236
+ // Stateless HTTP cannot push tools/list_changed, so it forces curated.
237
237
  const configuredMode = (process.env.GOOGLE_DISCOVERY ?? '').trim().toLowerCase();
238
238
  if (configuredMode && configuredMode !== 'curated') {
239
239
  process.stderr.write(`GOOGLE_DISCOVERY="${configuredMode}" is ignored over HTTP; the stateless transport forces "curated".\n`);
@@ -244,7 +244,7 @@ async function main() {
244
244
  const registry = buildRegistry(httpServer, httpCtx, 'curated', httpMetrics);
245
245
  registry.installListHandler();
246
246
  registerSetupPrompt(httpServer);
247
- // B13: mount the OAuth 2.1 AS (legs A + B) + the Bearer authenticator.
247
+ // Mount the OAuth 2.1 AS (MCP-client tokens + owner login) + the Bearer authenticator.
248
248
  const { buildAuthServer, verifiedEmailFromIdToken } = await import('./oauth-as.js');
249
249
  const { jwtSecretFrom } = await import('./mcp-token.js');
250
250
  const { resolveJwtKey, resolveMasterKeyForDispatch } = await import('./master-key.js');
@@ -287,7 +287,7 @@ async function main() {
287
287
  aliasEmail: (alias) => getAccountSet().configs[alias]?.email,
288
288
  log: (l) => process.stderr.write(`[as] ${l}\n`),
289
289
  });
290
- // BV-1: over HTTP, a dead/missing per-account token surfaces a clickable,
290
+ // Over HTTP, a dead/missing per-account token surfaces a clickable,
291
291
  // signed re-auth link into the AS's alias_reauth flow instead of a CLI hint.
292
292
  const { setHttpReauthLink } = await import('./reauth-hint.js');
293
293
  setHttpReauthLink((alias) => authServer.reauthLink(alias));
@@ -313,7 +313,7 @@ async function main() {
313
313
  });
314
314
  await host.start();
315
315
  process.stderr.write(`HTTP transport listening on http://${httpCfg.host}:${httpCfg.port} (public ${httpCfg.publicUrl})\n`);
316
- // Graceful shutdown so `docker run --init` (B16) forwards SIGTERM cleanly.
316
+ // Graceful shutdown so `docker run --init` forwards SIGTERM cleanly.
317
317
  const shutdown = () => {
318
318
  host.close().finally(() => process.exit(0));
319
319
  };