mcp-google-multi 6.0.0-alpha.13 → 6.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mcp-google-multi
2
2
 
3
- The most complete **local Google Workspace MCP server**: Gmail, Drive, Calendar, Sheets, Docs, Slides, Forms, Contacts, Tasks, Chat, Meet, Classroom, Vault, Admin and more — **every OAuth-reachable Workspace API method** as a tool, across **multiple Google accounts** at once, from Claude Code or any MCP client.
3
+ The most complete **local Google Workspace MCP server**: Gmail, Drive, Calendar, Sheets, Docs, Slides, Forms, Contacts, Tasks, Chat, Meet, Analytics (GA4), Search Console, Classroom, Vault, Admin and more — **every OAuth-reachable API method** as a tool, across **multiple Google accounts** at once, from Claude Code or any MCP client.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/mcp-google-multi?label=npm&color=cb3837)](https://www.npmjs.com/package/mcp-google-multi)
6
6
 
@@ -1,4 +1,4 @@
1
- export declare const WORKSPACE_APIS: Record<string, {
1
+ export declare const SUPPORTED_APIS: Record<string, {
2
2
  id: string;
3
3
  version: string;
4
4
  }>;
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
- export const WORKSPACE_APIS = {
4
+ export const SUPPORTED_APIS = {
5
5
  gmail: { id: 'gmail', version: 'v1' },
6
6
  drive: { id: 'drive', version: 'v3' },
7
7
  calendar: { id: 'calendar', version: 'v3' },
@@ -98,9 +98,9 @@ export async function loadMethodIndex(api, deps = {}) {
98
98
  const cached = memoryCache.get(api);
99
99
  if (cached && now() < cached.refreshAfter)
100
100
  return cached.index;
101
- const spec = WORKSPACE_APIS[api];
101
+ const spec = SUPPORTED_APIS[api];
102
102
  if (!spec) {
103
- throw new Error(`Unknown api "${api}". Known: ${Object.keys(WORKSPACE_APIS).join(', ')}`);
103
+ throw new Error(`Unknown api "${api}". Known: ${Object.keys(SUPPORTED_APIS).join(', ')}`);
104
104
  }
105
105
  const fetchFn = deps.fetchFn ?? ((url) => fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }));
106
106
  const cacheDir = deps.cacheDir ?? discoveryCacheDir();
@@ -129,6 +129,19 @@ export function mapGoogleError(error, account, forbiddenHint, scopeContext) {
129
129
  }
130
130
  if (status === 429) {
131
131
  const retryAfter = error?.response?.headers?.['retry-after'];
132
+ // GA4 quota exhaustion ("Exhausted property tokens ...") is a per-property
133
+ // token bucket, not a transient rate spike: shrinking the request is the
134
+ // lever that helps, and a blind immediate retry only burns more tokens.
135
+ if (/property tokens/i.test(message)) {
136
+ return {
137
+ error: 'rate_limited',
138
+ message,
139
+ hint: 'GA4 quotas are per-property token buckets that refill over the hour/day. ' +
140
+ 'Narrow the date range, request fewer dimensions/metrics/rows, and pass returnPropertyQuota to see the remaining tokens before retrying.',
141
+ retriable: true,
142
+ account,
143
+ };
144
+ }
132
145
  return {
133
146
  error: 'rate_limited',
134
147
  message,
@@ -6,7 +6,7 @@ import { coerceJson } from './_coerce.js';
6
6
  import { getToolsets, toolsetEnabled } from '../toolsets.js';
7
7
  import { editDistance } from '../scope-catalog.js';
8
8
  import { executeApiMethod, jsonResult } from '../executor.js';
9
- import { WORKSPACE_APIS, cudFromMethod, loadMethodIndex, searchMethods, } from '../discovery-client.js';
9
+ import { SUPPORTED_APIS, cudFromMethod, loadMethodIndex, searchMethods, } from '../discovery-client.js';
10
10
  const accountEnum = accountAliasSchema.optional();
11
11
  // Policy/toolset namespace for each API alias must match the NAMED tools' service
12
12
  // names, or user deny globs and GOOGLE_TOOLSETS silently miss escape-hatch calls.
@@ -61,7 +61,7 @@ export function registerEscapeTools(registry, policy, deps = {}) {
61
61
  const apiEnabled = (alias) => {
62
62
  return toolsetEnabled(toolsets, serviceForAlias(alias));
63
63
  };
64
- const enabledApis = Object.keys(WORKSPACE_APIS).filter(apiEnabled);
64
+ const enabledApis = Object.keys(SUPPORTED_APIS).filter(apiEnabled);
65
65
  const apiList = enabledApis.join(', ');
66
66
  const toolsetDisabled = (alias) => jsonResult({
67
67
  error: 'toolset_disabled',
@@ -80,7 +80,7 @@ export function registerEscapeTools(registry, policy, deps = {}) {
80
80
  },
81
81
  annotations: { openWorldHint: true },
82
82
  }, async ({ query, api, maxResults }) => {
83
- if (api && !WORKSPACE_APIS[api]) {
83
+ if (api && !SUPPORTED_APIS[api]) {
84
84
  return jsonResult({ error: 'unknown_api', message: `Unknown api "${api}".`, hint: `Known APIs: ${apiList}`, retriable: false }, true);
85
85
  }
86
86
  if (api && !apiEnabled(api))
@@ -129,7 +129,7 @@ export function registerEscapeTools(registry, policy, deps = {}) {
129
129
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
130
130
  _meta: { 'anthropic/maxResultSizeChars': 100_000 },
131
131
  }, async ({ account, api, methodId, pathParams, queryParams, body }) => {
132
- if (!WORKSPACE_APIS[api]) {
132
+ if (!SUPPORTED_APIS[api]) {
133
133
  return jsonResult({ error: 'unknown_api', message: `Unknown api "${api}".`, hint: `Known APIs: ${apiList}`, retriable: false, account }, true);
134
134
  }
135
135
  if (!apiEnabled(api))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-google-multi",
3
- "version": "6.0.0-alpha.13",
3
+ "version": "6.0.0-alpha.14",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "license": "MIT",