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

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.
@@ -126,20 +126,34 @@ export async function loadMethodIndex(api, deps = {}) {
126
126
  if (cacheFresh())
127
127
  doc = readCache();
128
128
  if (!doc) {
129
- const url = `https://www.googleapis.com/discovery/v1/apis/${spec.id}/${spec.version}/rest`;
130
- try {
131
- const res = await fetchFn(url);
132
- if (!res.ok)
133
- throw new Error(`HTTP ${res.status}`);
134
- doc = (await res.json());
135
- fs.mkdirSync(cacheDir, { recursive: true });
136
- fs.writeFileSync(cacheFile, JSON.stringify(doc), { mode: 0o600 });
129
+ // Newer APIs (analyticsadmin/analyticsdata) are absent from the central
130
+ // discovery directory (404); each service's own $discovery endpoint is
131
+ // authoritative, so try both — same fallback as scripts/fetch-discovery.
132
+ const urls = [
133
+ `https://www.googleapis.com/discovery/v1/apis/${spec.id}/${spec.version}/rest`,
134
+ `https://${spec.id}.googleapis.com/$discovery/rest?version=${spec.version}`,
135
+ ];
136
+ let fetchError;
137
+ for (const url of urls) {
138
+ try {
139
+ const res = await fetchFn(url);
140
+ if (!res.ok)
141
+ throw new Error(`HTTP ${res.status}`);
142
+ doc = (await res.json());
143
+ fs.mkdirSync(cacheDir, { recursive: true });
144
+ fs.writeFileSync(cacheFile, JSON.stringify(doc), { mode: 0o600 });
145
+ break;
146
+ }
147
+ catch (err) {
148
+ fetchError = err;
149
+ doc = undefined;
150
+ }
137
151
  }
138
- catch (err) {
152
+ if (!doc) {
139
153
  doc = readCache();
140
154
  staleFallback = true;
141
155
  if (!doc) {
142
- throw new Error(`Could not fetch the Google API Discovery document for "${api}" and no local cache exists (${err.message}). Retry when online.`, { cause: err });
156
+ throw new Error(`Could not fetch the Google API Discovery document for "${api}" and no local cache exists (${fetchError?.message ?? 'fetch failed'}). Retry when online.`, { cause: fetchError });
143
157
  }
144
158
  }
145
159
  }
@@ -108,13 +108,22 @@ export const BUNDLE_CATALOG = {
108
108
  description: 'Read Gmail Postmaster Tools deliverability data.',
109
109
  risk: 'low',
110
110
  },
111
- // Read-only by design: GA4 admin writes need analytics.edit, which ships as
112
- // a separate opt-in bundle only if real demand appears (plan 6.0.0 GA-1).
113
111
  analytics: {
114
112
  scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
115
113
  description: 'Read Google Analytics (GA4): run reports and inspect accounts, properties and their configuration.',
116
114
  risk: 'low',
117
115
  },
116
+ // Includes readonly so it is self-sufficient: analytics.edit alone does not
117
+ // authorize Data API reads. The smaller `analytics` bundle stays the hint
118
+ // target for read scopes (scope-observability sorts bundles by size).
119
+ analytics_write: {
120
+ scopes: [
121
+ 'https://www.googleapis.com/auth/analytics.readonly',
122
+ 'https://www.googleapis.com/auth/analytics.edit',
123
+ ],
124
+ description: 'Edit Google Analytics (GA4) configuration: properties, data streams, key events, custom dimensions and metrics. Includes read access.',
125
+ risk: 'medium',
126
+ },
118
127
  groupssettings: {
119
128
  scopes: ['https://www.googleapis.com/auth/apps.groups.settings'],
120
129
  description: 'Change Google Groups settings for the domain.',
package/dist/services.js CHANGED
@@ -26,7 +26,7 @@ export const SERVICES = [
26
26
  { name: 'slides', register: registerSlidesTools, enabled: () => new Set(getOptionalBundles()).has('slides') },
27
27
  { name: 'forms', register: registerFormsTools, enabled: () => new Set(getOptionalBundles()).has('forms') },
28
28
  { name: 'chat', register: registerChatTools, enabled: () => new Set(getOptionalBundles()).has('chat') },
29
- { name: 'analytics', register: registerAnalyticsTools, enabled: () => new Set(getOptionalBundles()).has('analytics') },
29
+ { name: 'analytics', register: registerAnalyticsTools, enabled: () => { const b = new Set(getOptionalBundles()); return b.has('analytics') || b.has('analytics_write'); } },
30
30
  { name: 'admin', register: registerAdminTools, enabled: () => getAdminAccounts().length > 0 },
31
31
  ];
32
32
  // Generated-only services with opt-in scopes; admin/forms/chat/analytics reuse their curated gate in buildRegistry,
@@ -13,6 +13,12 @@ export interface GeneratedToolDef {
13
13
  method: ApiMethodRef;
14
14
  params: GeneratedParam[];
15
15
  hasBody: boolean;
16
+ /** Typed-body tier: these top-level args assemble into the request body
17
+ * (flat schemas only; deep schemas keep the single opaque `body` arg). */
18
+ bodyParams?: Array<{
19
+ field: string;
20
+ api: string;
21
+ }>;
16
22
  shape: z.ZodRawShape;
17
23
  }
18
24
  export declare function accountField(): z.ZodOptional<z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>>;
@@ -25,11 +25,21 @@ export function registerGeneratedTool(registry, def, deps = {}) {
25
25
  else
26
26
  queryParams[p.api] = value;
27
27
  }
28
+ let body = def.hasBody ? args.body : undefined;
29
+ if (def.bodyParams) {
30
+ const assembled = {};
31
+ for (const bp of def.bodyParams) {
32
+ const value = args[bp.field];
33
+ if (value !== undefined)
34
+ assembled[bp.api] = value;
35
+ }
36
+ body = assembled;
37
+ }
28
38
  return executeApiMethod(def.method, {
29
39
  account: args.account,
30
40
  pathParams,
31
41
  queryParams,
32
- body: def.hasBody ? args.body : undefined,
42
+ body,
33
43
  }, deps);
34
44
  });
35
45
  }