mcp-google-multi 6.0.0-alpha.44 → 6.0.0-alpha.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -381,6 +381,13 @@ async function main() {
381
381
  // re-auth link into the AS's alias_reauth flow instead of a stdio CLI hint.
382
382
  const { setHttpReauthBase } = await import('./reauth-hint.js');
383
383
  setHttpReauthBase(httpCfg.publicUrl);
384
+ // Wizard consent over HTTP: hand out the clientless AS link instead of
385
+ // binding a loopback listener (single-owner flow; a tenancy host installs
386
+ // its own signed-mint context here).
387
+ const { setWizardHttpConsent } = await import('./tools/account-wizard.js');
388
+ setWizardHttpConsent({
389
+ mintConsentUrl: (alias) => `${httpCfg.publicUrl}/authorize?flow=alias_reauth&alias=${encodeURIComponent(alias)}`,
390
+ });
384
391
  let httpTap;
385
392
  if (httpMetrics) {
386
393
  const { tapUsageMetrics } = await import('./metrics-tap.js');
@@ -53,4 +53,13 @@ export declare function argsToAddForm(a: {
53
53
  /** Scopes requested by the profile but NOT granted at consent (granular
54
54
  * consent / unchecked bundles). Pure. */
55
55
  export declare function scopeGrantDiff(requested: string[], grantedScope: string | undefined): string[];
56
+ /** HTTP-transport consent context (S1.20): set by the HTTP bootstrap. When
57
+ * present, runConsent NEVER binds a server-side loopback listener — the
58
+ * user's browser cannot reach the daemon's loopback — and instead hands out
59
+ * a clientless AS consent URL. The injector decides the flow (single-owner =
60
+ * the legacy alias_reauth link; a tenancy host mints a signed alias_add URL). */
61
+ export interface WizardHttpConsent {
62
+ mintConsentUrl: (alias: string) => Promise<string> | string;
63
+ }
64
+ export declare function setWizardHttpConsent(ctx: WizardHttpConsent | null): void;
56
65
  export declare function registerAccountWizardTools(registry: ToolRegistry, server: McpServer): void;
@@ -136,6 +136,15 @@ function errorResult(slug, message, hint, account) {
136
136
  isError: true,
137
137
  };
138
138
  }
139
+ let httpConsent = null;
140
+ export function setWizardHttpConsent(ctx) {
141
+ httpConsent = ctx;
142
+ }
143
+ function pendingText(alias, url) {
144
+ return (`Account "${alias}" is ready to authorize. Open this link in a browser (it belongs to whoever owns the Google account):\n${url}\n` +
145
+ `On completion the token is stored server-side and the account is usable immediately — no restart. ` +
146
+ `If the link expires, run account_reauth for a fresh one.\n${TESTING_MODE_WARNING}`);
147
+ }
139
148
  /** Run Google consent for `alias` and persist the token. Opens the browser via
140
149
  * URL-mode elicitation when the client supports it, else server-side + prints
141
150
  * the URL. Returns the granted-scope diff for the S4 report. */
@@ -150,6 +159,29 @@ async function runConsent(server, alias) {
150
159
  hint: 'Accounts sourced from GOOGLE_ACCOUNTS are not editable here. Run `mcp-google-multi migrate-config` to move them into config.json.',
151
160
  };
152
161
  }
162
+ // HTTP transport: consent completes OUT OF BAND at the AS /callback. Never
163
+ // bind a loopback listener here; try URL-mode elicitation as a convenience
164
+ // but surface the URL UP FRONT either way (not only on failure).
165
+ const http = httpConsent;
166
+ if (http) {
167
+ let url;
168
+ try {
169
+ url = await http.mintConsentUrl(alias);
170
+ }
171
+ catch (e) {
172
+ return { ok: false, slug: 'internal', message: safeMessage(e) };
173
+ }
174
+ const caps = server.server.getClientCapabilities?.();
175
+ if (caps?.elicitation?.url !== undefined) {
176
+ try {
177
+ await server.server.elicitInput(urlElicitationParams(alias, url, randomBytes(16).toString('hex')));
178
+ }
179
+ catch {
180
+ // the URL in the pending text is the recovery
181
+ }
182
+ }
183
+ return { ok: 'pending', url };
184
+ }
153
185
  // Bind the ephemeral loopback listener BEFORE building the auth URL: the
154
186
  // redirect URI needs the assigned port, and listening first means the
155
187
  // callback can't race the browser.
@@ -267,6 +299,8 @@ export function registerAccountWizardTools(registry, server) {
267
299
  invalidateAccountSet();
268
300
  // S3 + S4: consent + validate.
269
301
  const consent = await runConsent(server, validated.alias);
302
+ if (consent.ok === 'pending')
303
+ return textResult(pendingText(validated.alias, consent.url));
270
304
  if (!consent.ok)
271
305
  return errorResult(consent.slug, consent.message, consent.hint, validated.alias);
272
306
  // B15: offer to register the server with another MCP client.
@@ -303,6 +337,8 @@ export function registerAccountWizardTools(registry, server) {
303
337
  return errorResult('validation_error', `Unknown account "${alias}".`, `Pass one of the configured aliases: ${getAccountSet().aliases.join(', ')}. Use account_add to create a new one.`, alias);
304
338
  }
305
339
  const consent = await runConsent(server, alias);
340
+ if (consent.ok === 'pending')
341
+ return textResult(pendingText(alias, consent.url));
306
342
  if (!consent.ok)
307
343
  return errorResult(consent.slug, consent.message, consent.hint, alias);
308
344
  return textResult(s4Text(alias, consent.missing));
@@ -353,6 +389,12 @@ export function registerAccountWizardTools(registry, server) {
353
389
  if (client.managed === 'cli') {
354
390
  blocks.push(`${client.label}: run\n ${instr.text}`);
355
391
  }
392
+ else if (a.write && mode === 'http') {
393
+ // Over HTTP this tool runs on the DAEMON, whose homedir is not the
394
+ // caller's machine: write:true would edit the operator's own client
395
+ // configs (a cross-tenant hazard under tenancy). Snippet only.
396
+ blocks.push(`${client.label}: write:true is ignored over HTTP — this tool runs on the server, not your machine. Add to ${instr.path}\n${instr.text}`);
397
+ }
356
398
  else if (a.write) {
357
399
  const res = applyFileEntry(client, name, entry);
358
400
  blocks.push(res.ok
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-google-multi",
3
- "version": "6.0.0-alpha.44",
3
+ "version": "6.0.0-alpha.45",
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",