mcp-google-multi 6.0.0-alpha.12 → 6.0.0-alpha.13
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/services.js +3 -2
- package/dist/tools/analytics.d.ts +18 -0
- package/dist/tools/analytics.js +279 -0
- package/dist/tools/generated/analytics.js +0 -55
- package/package.json +3 -1
package/dist/services.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerSlidesTools } from './tools/slides.js';
|
|
|
11
11
|
import { registerFormsTools } from './tools/forms.js';
|
|
12
12
|
import { registerChatTools } from './tools/chat.js';
|
|
13
13
|
import { registerAdminTools } from './tools/admin.js';
|
|
14
|
+
import { registerAnalyticsTools } from './tools/analytics.js';
|
|
14
15
|
import { getOptionalBundles, getAdminAccounts } from './auth.js';
|
|
15
16
|
export const SERVICES = [
|
|
16
17
|
{ name: 'gmail', register: registerGmailTools },
|
|
@@ -25,16 +26,16 @@ export const SERVICES = [
|
|
|
25
26
|
{ name: 'slides', register: registerSlidesTools, enabled: () => new Set(getOptionalBundles()).has('slides') },
|
|
26
27
|
{ name: 'forms', register: registerFormsTools, enabled: () => new Set(getOptionalBundles()).has('forms') },
|
|
27
28
|
{ name: 'chat', register: registerChatTools, enabled: () => new Set(getOptionalBundles()).has('chat') },
|
|
29
|
+
{ name: 'analytics', register: registerAnalyticsTools, enabled: () => new Set(getOptionalBundles()).has('analytics') },
|
|
28
30
|
{ name: 'admin', register: registerAdminTools, enabled: () => getAdminAccounts().length > 0 },
|
|
29
31
|
];
|
|
30
|
-
// Generated-only services with opt-in scopes; admin/forms/chat reuse their curated gate in buildRegistry,
|
|
32
|
+
// Generated-only services with opt-in scopes; admin/forms/chat/analytics reuse their curated gate in buildRegistry,
|
|
31
33
|
// and workspaceevents is deliberately absent — no dedicated scope (subscriptions use resource scopes).
|
|
32
34
|
const bundleGate = (name) => ({
|
|
33
35
|
enabled: () => new Set(getOptionalBundles()).has(name),
|
|
34
36
|
hint: `add "${name}" to an account's scope profile (or legacy GOOGLE_OPTIONAL_SCOPES)`,
|
|
35
37
|
});
|
|
36
38
|
export const GENERATED_GATES = {
|
|
37
|
-
analytics: bundleGate('analytics'),
|
|
38
39
|
appsmarket: bundleGate('appsmarket'),
|
|
39
40
|
classroom: bundleGate('classroom'),
|
|
40
41
|
cloudidentity: bundleGate('cloudidentity'),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
/** Accepts "213025502" or "properties/213025502"; rejects the identifiers
|
|
3
|
+
* people paste by mistake (G-… measurement IDs, UA-… properties) with a
|
|
4
|
+
* pointer to the right one. */
|
|
5
|
+
export declare function normalizeProperty(input: string): {
|
|
6
|
+
name: string;
|
|
7
|
+
} | {
|
|
8
|
+
hint: string;
|
|
9
|
+
};
|
|
10
|
+
/** GA4 report rows ({dimensionValues:[{value}], metricValues:[{value}]}) are
|
|
11
|
+
* verbose; merge each row into one {name: value} object (dimension and metric
|
|
12
|
+
* API names never collide). Shared by runReport and runRealtimeReport. */
|
|
13
|
+
export declare function shapeReport(data: any): Record<string, unknown>;
|
|
14
|
+
export declare function shapeAccountSummaries(data: any): Record<string, unknown>;
|
|
15
|
+
/** Full metadata descriptions run to paragraphs; the first ~160 chars carry
|
|
16
|
+
* the disambiguation the model needs without bloating a ~300-entry list. */
|
|
17
|
+
export declare function shapeMetadata(data: any): Record<string, unknown>;
|
|
18
|
+
export declare function registerAnalyticsTools(server: ToolRegistry): void;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceBoolean, coerceJson } from './_coerce.js';
|
|
3
|
+
import { analyticsdata as analyticsdataClient } from '@googleapis/analyticsdata';
|
|
4
|
+
import { analyticsadmin as analyticsadminClient } from '@googleapis/analyticsadmin';
|
|
5
|
+
import { accountAliasSchema } from '../accounts.js';
|
|
6
|
+
import { getClient } from '../client.js';
|
|
7
|
+
import { handleGoogleApiError } from './_errors.js';
|
|
8
|
+
const accountEnum = accountAliasSchema.optional();
|
|
9
|
+
/** Accepts "213025502" or "properties/213025502"; rejects the identifiers
|
|
10
|
+
* people paste by mistake (G-… measurement IDs, UA-… properties) with a
|
|
11
|
+
* pointer to the right one. */
|
|
12
|
+
export function normalizeProperty(input) {
|
|
13
|
+
const t = input.trim();
|
|
14
|
+
if (/^properties\/\d+$/.test(t))
|
|
15
|
+
return { name: t };
|
|
16
|
+
if (/^\d+$/.test(t))
|
|
17
|
+
return { name: `properties/${t}` };
|
|
18
|
+
if (/^G-[A-Z0-9]+$/i.test(t)) {
|
|
19
|
+
return {
|
|
20
|
+
hint: `"${t}" is a measurement ID (a web data-stream tag), not a GA4 property ID. ` +
|
|
21
|
+
'Use the numeric property ID from GA Admin > Property settings, or find it with analytics_account_summaries.',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (/^UA-/i.test(t)) {
|
|
25
|
+
return {
|
|
26
|
+
hint: `"${t}" is a Universal Analytics property, which the GA4 APIs cannot query. ` +
|
|
27
|
+
'Use the numeric ID of a GA4 property (find yours with analytics_account_summaries).',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
hint: `"${t}" is not a GA4 property reference. Pass the numeric property ID ` +
|
|
32
|
+
'(e.g. "213025502" or "properties/213025502"); find yours with analytics_account_summaries.',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** GA4 report rows ({dimensionValues:[{value}], metricValues:[{value}]}) are
|
|
36
|
+
* verbose; merge each row into one {name: value} object (dimension and metric
|
|
37
|
+
* API names never collide). Shared by runReport and runRealtimeReport. */
|
|
38
|
+
export function shapeReport(data) {
|
|
39
|
+
const dimensionHeaders = (data.dimensionHeaders ?? []).map((h) => h.name);
|
|
40
|
+
const metricHeaders = (data.metricHeaders ?? []).map((h) => ({ name: h.name, type: h.type }));
|
|
41
|
+
const mergeRow = (r) => {
|
|
42
|
+
const out = {};
|
|
43
|
+
dimensionHeaders.forEach((name, i) => {
|
|
44
|
+
out[name] = r.dimensionValues?.[i]?.value ?? '';
|
|
45
|
+
});
|
|
46
|
+
metricHeaders.forEach((h, i) => {
|
|
47
|
+
out[h.name] = r.metricValues?.[i]?.value ?? '';
|
|
48
|
+
});
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
const shaped = {
|
|
52
|
+
rowCount: data.rowCount ?? data.rows?.length ?? 0,
|
|
53
|
+
dimensionHeaders,
|
|
54
|
+
metricHeaders,
|
|
55
|
+
rows: (data.rows ?? []).map(mergeRow),
|
|
56
|
+
};
|
|
57
|
+
if (data.totals?.length)
|
|
58
|
+
shaped.totals = data.totals.map(mergeRow);
|
|
59
|
+
if (data.maximums?.length)
|
|
60
|
+
shaped.maximums = data.maximums.map(mergeRow);
|
|
61
|
+
if (data.minimums?.length)
|
|
62
|
+
shaped.minimums = data.minimums.map(mergeRow);
|
|
63
|
+
if (data.metadata)
|
|
64
|
+
shaped.metadata = data.metadata;
|
|
65
|
+
if (data.propertyQuota)
|
|
66
|
+
shaped.propertyQuota = data.propertyQuota;
|
|
67
|
+
return shaped;
|
|
68
|
+
}
|
|
69
|
+
export function shapeAccountSummaries(data) {
|
|
70
|
+
const accounts = (data.accountSummaries ?? []).map((a) => ({
|
|
71
|
+
account: a.account,
|
|
72
|
+
displayName: a.displayName,
|
|
73
|
+
properties: (a.propertySummaries ?? []).map((p) => ({
|
|
74
|
+
property: p.property,
|
|
75
|
+
displayName: p.displayName,
|
|
76
|
+
...(p.propertyType && p.propertyType !== 'PROPERTY_TYPE_ORDINARY' ? { propertyType: p.propertyType } : {}),
|
|
77
|
+
})),
|
|
78
|
+
}));
|
|
79
|
+
return { accounts, ...(data.nextPageToken ? { nextPageToken: data.nextPageToken } : {}) };
|
|
80
|
+
}
|
|
81
|
+
const DESCRIPTION_CAP = 160;
|
|
82
|
+
/** Full metadata descriptions run to paragraphs; the first ~160 chars carry
|
|
83
|
+
* the disambiguation the model needs without bloating a ~300-entry list. */
|
|
84
|
+
export function shapeMetadata(data) {
|
|
85
|
+
const cap = (s) => typeof s === 'string' && s.length > DESCRIPTION_CAP ? `${s.slice(0, DESCRIPTION_CAP - 3)}...` : s || undefined;
|
|
86
|
+
return {
|
|
87
|
+
dimensions: (data.dimensions ?? []).map((d) => ({
|
|
88
|
+
apiName: d.apiName,
|
|
89
|
+
uiName: d.uiName,
|
|
90
|
+
category: d.category,
|
|
91
|
+
...(d.customDefinition ? { custom: true } : {}),
|
|
92
|
+
description: cap(d.description),
|
|
93
|
+
})),
|
|
94
|
+
metrics: (data.metrics ?? []).map((m) => ({
|
|
95
|
+
apiName: m.apiName,
|
|
96
|
+
uiName: m.uiName,
|
|
97
|
+
category: m.category,
|
|
98
|
+
...(m.type ? { type: m.type } : {}),
|
|
99
|
+
...(m.expression ? { expression: m.expression } : {}),
|
|
100
|
+
...(m.customDefinition ? { custom: true } : {}),
|
|
101
|
+
description: cap(m.description),
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const propertySchema = z
|
|
106
|
+
.string()
|
|
107
|
+
.describe('GA4 property: numeric ID like "213025502" or "properties/213025502" — NOT a "G-..." measurement ID and not "UA-...". Find yours with analytics_account_summaries.');
|
|
108
|
+
export function registerAnalyticsTools(server) {
|
|
109
|
+
server.registerTool('analytics_account_summaries', {
|
|
110
|
+
description: 'List every Google Analytics (GA4) account and property this Google account can access, with their numeric property IDs. The starting point for any Analytics question ("what properties do I have?").',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
account: accountEnum.describe('Google account alias'),
|
|
113
|
+
pageSize: z.number().min(1).max(200).optional().describe('Summaries per page (default 50, max 200)'),
|
|
114
|
+
pageToken: z.string().optional().describe('Token from a previous page'),
|
|
115
|
+
},
|
|
116
|
+
}, async ({ account, pageSize, pageToken }) => {
|
|
117
|
+
try {
|
|
118
|
+
const auth = await getClient(account);
|
|
119
|
+
const admin = analyticsadminClient({ version: 'v1beta', auth });
|
|
120
|
+
const res = await admin.accountSummaries.list({ pageSize, pageToken });
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: JSON.stringify(shapeAccountSummaries(res.data), null, 2) }],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return handleAnalyticsError(error, account);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
server.registerTool('analytics_run_report', {
|
|
130
|
+
description: 'Run a Google Analytics (GA4) report: metrics over a date range, optionally grouped by dimensions — the workhorse for questions like "how many users last week, by country". Dates accept YYYY-MM-DD or relative forms ("today", "yesterday", "28daysAgo"). Unsure which dimension/metric names are valid? Call analytics_get_metadata first.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
account: accountEnum.describe('Google account alias'),
|
|
133
|
+
property: propertySchema,
|
|
134
|
+
startDate: z.string().describe('Start date: YYYY-MM-DD or relative ("today", "yesterday", "NdaysAgo" e.g. "28daysAgo")'),
|
|
135
|
+
endDate: z.string().describe('End date: YYYY-MM-DD or relative ("today", "yesterday", "NdaysAgo")'),
|
|
136
|
+
metrics: coerceArray(z.string()).describe('Metric API names, e.g. ["activeUsers","sessions","screenPageViews"]. Max 10. Valid names (including custom ones) come from analytics_get_metadata.'),
|
|
137
|
+
dimensions: coerceArray(z.string())
|
|
138
|
+
.optional()
|
|
139
|
+
.describe('Dimension API names to group by, e.g. ["date"] or ["country","deviceCategory"]. Max 9. Omit for a single total row.'),
|
|
140
|
+
dimensionFilter: coerceJson(z.record(z.string(), z.unknown()))
|
|
141
|
+
.optional()
|
|
142
|
+
.describe('FilterExpression on dimensions (applies independently of metricFilter). Simple: {"filter":{"fieldName":"country","stringFilter":{"matchType":"EXACT","value":"France"}}}. AND of two: {"andGroup":{"expressions":[{"filter":{"fieldName":"country","stringFilter":{"matchType":"EXACT","value":"France"}}},{"filter":{"fieldName":"deviceCategory","stringFilter":{"matchType":"EXACT","value":"mobile"}}}]}}. matchType: EXACT | BEGINS_WITH | ENDS_WITH | CONTAINS | FULL_REGEXP (add "caseSensitive":true for case-sensitive). Also available: inListFilter, notExpression, orGroup.'),
|
|
143
|
+
metricFilter: coerceJson(z.record(z.string(), z.unknown()))
|
|
144
|
+
.optional()
|
|
145
|
+
.describe('FilterExpression on metric values, e.g. {"filter":{"fieldName":"sessions","numericFilter":{"operation":"GREATER_THAN","value":{"int64Value":"100"}}}}. operation: EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN | GREATER_THAN_OR_EQUAL; betweenFilter takes fromValue/toValue.'),
|
|
146
|
+
orderBys: coerceJson(z.array(z.record(z.string(), z.unknown())))
|
|
147
|
+
.optional()
|
|
148
|
+
.describe('Sort order, e.g. [{"metric":{"metricName":"sessions"},"desc":true}] or [{"dimension":{"dimensionName":"date"}}]. Default: unordered.'),
|
|
149
|
+
limit: z.number().min(1).max(250000).optional().describe('Max rows to return (API default 10000). Keep small for readable output.'),
|
|
150
|
+
offset: z.number().min(0).optional().describe('Zero-based row offset for pagination'),
|
|
151
|
+
metricAggregations: coerceArray(z.enum(['TOTAL', 'MINIMUM', 'MAXIMUM', 'COUNT']))
|
|
152
|
+
.optional()
|
|
153
|
+
.describe('Also return aggregate rows across all matching data (surfaced as totals/maximums/minimums)'),
|
|
154
|
+
keepEmptyRows: coerceBoolean.optional().describe('Include rows whose metrics are all zero (default false)'),
|
|
155
|
+
returnPropertyQuota: coerceBoolean.optional().describe("Include this property's remaining quota tokens in the response"),
|
|
156
|
+
},
|
|
157
|
+
}, async ({ account, property, startDate, endDate, metrics, dimensions, dimensionFilter, metricFilter, orderBys, limit, offset, metricAggregations, keepEmptyRows, returnPropertyQuota }) => {
|
|
158
|
+
const prop = normalizeProperty(property);
|
|
159
|
+
if ('hint' in prop)
|
|
160
|
+
return invalidProperty(prop.hint, account);
|
|
161
|
+
try {
|
|
162
|
+
const auth = await getClient(account);
|
|
163
|
+
const dataApi = analyticsdataClient({ version: 'v1beta', auth });
|
|
164
|
+
const requestBody = {
|
|
165
|
+
dateRanges: [{ startDate, endDate }],
|
|
166
|
+
metrics: metrics.map((name) => ({ name })),
|
|
167
|
+
};
|
|
168
|
+
if (dimensions?.length)
|
|
169
|
+
requestBody.dimensions = dimensions.map((name) => ({ name }));
|
|
170
|
+
if (dimensionFilter)
|
|
171
|
+
requestBody.dimensionFilter = dimensionFilter;
|
|
172
|
+
if (metricFilter)
|
|
173
|
+
requestBody.metricFilter = metricFilter;
|
|
174
|
+
if (orderBys?.length)
|
|
175
|
+
requestBody.orderBys = orderBys;
|
|
176
|
+
if (limit !== undefined)
|
|
177
|
+
requestBody.limit = limit;
|
|
178
|
+
if (offset !== undefined)
|
|
179
|
+
requestBody.offset = offset;
|
|
180
|
+
if (metricAggregations?.length)
|
|
181
|
+
requestBody.metricAggregations = metricAggregations;
|
|
182
|
+
if (keepEmptyRows !== undefined)
|
|
183
|
+
requestBody.keepEmptyRows = keepEmptyRows;
|
|
184
|
+
if (returnPropertyQuota !== undefined)
|
|
185
|
+
requestBody.returnPropertyQuota = returnPropertyQuota;
|
|
186
|
+
const res = await dataApi.properties.runReport({ property: prop.name, requestBody });
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: 'text', text: JSON.stringify(shapeReport(res.data), null, 2) }],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return handleAnalyticsError(error, account);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
server.registerTool('analytics_run_realtime_report', {
|
|
196
|
+
description: 'Run a GA4 realtime report: who is on the site right now (last 30 minutes). Realtime supports a restricted set of names, e.g. metrics activeUsers, screenPageViews, eventCount, keyEvents; dimensions country, city, deviceCategory, unifiedScreenName, eventName.',
|
|
197
|
+
inputSchema: {
|
|
198
|
+
account: accountEnum.describe('Google account alias'),
|
|
199
|
+
property: propertySchema,
|
|
200
|
+
metrics: coerceArray(z.string()).describe('Realtime metric API names, e.g. ["activeUsers"]'),
|
|
201
|
+
dimensions: coerceArray(z.string()).optional().describe('Realtime dimension API names, e.g. ["country"] or ["unifiedScreenName"]'),
|
|
202
|
+
dimensionFilter: coerceJson(z.record(z.string(), z.unknown()))
|
|
203
|
+
.optional()
|
|
204
|
+
.describe('FilterExpression on dimensions — same shape as analytics_run_report'),
|
|
205
|
+
metricFilter: coerceJson(z.record(z.string(), z.unknown()))
|
|
206
|
+
.optional()
|
|
207
|
+
.describe('FilterExpression on metric values — same shape as analytics_run_report'),
|
|
208
|
+
minuteRanges: coerceJson(z.array(z.record(z.string(), z.unknown())))
|
|
209
|
+
.optional()
|
|
210
|
+
.describe('Up to 2 ranges of minutes-ago, e.g. [{"startMinutesAgo":29,"endMinutesAgo":0}] (default: last 30 minutes)'),
|
|
211
|
+
limit: z.number().min(1).max(250000).optional().describe('Max rows to return'),
|
|
212
|
+
returnPropertyQuota: coerceBoolean.optional().describe("Include this property's remaining realtime quota tokens"),
|
|
213
|
+
},
|
|
214
|
+
}, async ({ account, property, metrics, dimensions, dimensionFilter, metricFilter, minuteRanges, limit, returnPropertyQuota }) => {
|
|
215
|
+
const prop = normalizeProperty(property);
|
|
216
|
+
if ('hint' in prop)
|
|
217
|
+
return invalidProperty(prop.hint, account);
|
|
218
|
+
try {
|
|
219
|
+
const auth = await getClient(account);
|
|
220
|
+
const dataApi = analyticsdataClient({ version: 'v1beta', auth });
|
|
221
|
+
const requestBody = { metrics: metrics.map((name) => ({ name })) };
|
|
222
|
+
if (dimensions?.length)
|
|
223
|
+
requestBody.dimensions = dimensions.map((name) => ({ name }));
|
|
224
|
+
if (dimensionFilter)
|
|
225
|
+
requestBody.dimensionFilter = dimensionFilter;
|
|
226
|
+
if (metricFilter)
|
|
227
|
+
requestBody.metricFilter = metricFilter;
|
|
228
|
+
if (minuteRanges?.length)
|
|
229
|
+
requestBody.minuteRanges = minuteRanges;
|
|
230
|
+
if (limit !== undefined)
|
|
231
|
+
requestBody.limit = limit;
|
|
232
|
+
if (returnPropertyQuota !== undefined)
|
|
233
|
+
requestBody.returnPropertyQuota = returnPropertyQuota;
|
|
234
|
+
const res = await dataApi.properties.runRealtimeReport({ property: prop.name, requestBody });
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: 'text', text: JSON.stringify(shapeReport(res.data), null, 2) }],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return handleAnalyticsError(error, account);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
server.registerTool('analytics_get_metadata', {
|
|
244
|
+
description: 'List every valid dimension and metric API name for a GA4 property, including its custom definitions. Call this before analytics_run_report when unsure which names exist. Property "0" returns the standard set without property access.',
|
|
245
|
+
inputSchema: {
|
|
246
|
+
account: accountEnum.describe('Google account alias'),
|
|
247
|
+
property: propertySchema,
|
|
248
|
+
},
|
|
249
|
+
}, async ({ account, property }) => {
|
|
250
|
+
const prop = normalizeProperty(property);
|
|
251
|
+
if ('hint' in prop)
|
|
252
|
+
return invalidProperty(prop.hint, account);
|
|
253
|
+
try {
|
|
254
|
+
const auth = await getClient(account);
|
|
255
|
+
const dataApi = analyticsdataClient({ version: 'v1beta', auth });
|
|
256
|
+
const res = await dataApi.properties.getMetadata({ name: `${prop.name}/metadata` });
|
|
257
|
+
return {
|
|
258
|
+
content: [{ type: 'text', text: JSON.stringify(shapeMetadata(res.data), null, 2) }],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
return handleAnalyticsError(error, account);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function invalidProperty(hint, account) {
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: 'text',
|
|
271
|
+
text: JSON.stringify({ error: 'invalid_params', message: 'Invalid GA4 property reference.', hint, retriable: false, account }),
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
isError: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function handleAnalyticsError(error, account) {
|
|
278
|
+
return handleGoogleApiError(error, account, 'Needs the "analytics" bundle on this account (add it to the scope profile, then re-auth), and the Google account must have access to this GA4 property.');
|
|
279
|
+
}
|
|
@@ -118,20 +118,6 @@ export function registerAnalyticsGeneratedTools(registry) {
|
|
|
118
118
|
fields: z.string().optional().describe('Response field mask.'),
|
|
119
119
|
},
|
|
120
120
|
});
|
|
121
|
-
registerGeneratedTool(registry, {
|
|
122
|
-
name: "analytics_account_summaries_list",
|
|
123
|
-
cud: "read",
|
|
124
|
-
description: "Returns summaries of all accounts accessible by the caller.",
|
|
125
|
-
method: { id: "analyticsadmin.accountSummaries.list", httpMethod: "GET", path: "v1beta/accountSummaries", baseUrl: "https://analyticsadmin.googleapis.com/", requiredParams: [], scopes: S_analyticsadmin_v1beta[1] },
|
|
126
|
-
params: [{ "field": "pageSize", "api": "pageSize", "location": "query" }, { "field": "pageToken", "api": "pageToken", "location": "query" }, { "field": "fields", "api": "fields", "location": "query" }],
|
|
127
|
-
hasBody: false,
|
|
128
|
-
shape: {
|
|
129
|
-
account: accountField(),
|
|
130
|
-
pageSize: z.number().describe("Optional. The maximum number of AccountSummary resources to return. The service may return fewer than this value, even if there are additional pages. If unspecified, at most 50 resources will be retur").optional(),
|
|
131
|
-
pageToken: z.string().describe("Optional. A page token, received from a previous `ListAccountSummaries` call. Provide this to retrieve the subsequent page. When paginating, all other parameters provided to `ListAccountSummaries` mus").optional(),
|
|
132
|
-
fields: z.string().optional().describe('Response field mask.'),
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
121
|
registerGeneratedTool(registry, {
|
|
136
122
|
name: "analytics_properties_acknowledge_user_data_collection",
|
|
137
123
|
cud: "update",
|
|
@@ -881,19 +867,6 @@ export function registerAnalyticsGeneratedTools(registry) {
|
|
|
881
867
|
fields: z.string().optional().describe('Response field mask.'),
|
|
882
868
|
},
|
|
883
869
|
});
|
|
884
|
-
registerGeneratedTool(registry, {
|
|
885
|
-
name: "analytics_properties_get_metadata",
|
|
886
|
-
cud: "read",
|
|
887
|
-
description: "Returns metadata for dimensions and metrics available in reporting methods. Used to explore the dimensions and metrics. In this method, a Google Analytics prope",
|
|
888
|
-
method: { id: "analyticsdata.properties.getMetadata", httpMethod: "GET", path: "v1beta/{+name}", baseUrl: "https://analyticsdata.googleapis.com/", requiredParams: ["name"], scopes: S_analyticsdata_v1beta[0] },
|
|
889
|
-
params: [{ "field": "name", "api": "name", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
|
|
890
|
-
hasBody: false,
|
|
891
|
-
shape: {
|
|
892
|
-
account: accountField(),
|
|
893
|
-
name: z.string().describe("Required. The resource name of the metadata to retrieve. This name field is specified in the URL path and not URL parameters. Property is a numeric Google Analytics property identifier. To learn more,"),
|
|
894
|
-
fields: z.string().optional().describe('Response field mask.'),
|
|
895
|
-
},
|
|
896
|
-
});
|
|
897
870
|
registerGeneratedTool(registry, {
|
|
898
871
|
name: "analytics_properties_run_pivot_report",
|
|
899
872
|
cud: "read",
|
|
@@ -908,32 +881,4 @@ export function registerAnalyticsGeneratedTools(registry) {
|
|
|
908
881
|
fields: z.string().optional().describe('Response field mask.'),
|
|
909
882
|
},
|
|
910
883
|
});
|
|
911
|
-
registerGeneratedTool(registry, {
|
|
912
|
-
name: "analytics_properties_run_realtime_report",
|
|
913
|
-
cud: "read",
|
|
914
|
-
description: "Returns a customized report of realtime event data for your property. Events appear in realtime reports seconds after they have been sent to the Google Analytic",
|
|
915
|
-
method: { id: "analyticsdata.properties.runRealtimeReport", httpMethod: "POST", path: "v1beta/{+property}:runRealtimeReport", baseUrl: "https://analyticsdata.googleapis.com/", requiredParams: ["property"], scopes: S_analyticsdata_v1beta[0] },
|
|
916
|
-
params: [{ "field": "property", "api": "property", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
|
|
917
|
-
hasBody: true,
|
|
918
|
-
shape: {
|
|
919
|
-
account: accountField(),
|
|
920
|
-
property: z.string().describe("A Google Analytics property identifier whose events are tracked. Specified in the URL path and not the body. To learn more, see [where to find your Property ID](https://developers.google.com/analytics"),
|
|
921
|
-
body: coerceJson(z.record(z.string(), z.unknown())).describe("RunRealtimeReportRequest JSON request body. Top-level fields: dimensionFilter, dimensions, limit, metricAggregations, metricFilter, metrics, minuteRanges, orderBys, returnPropertyQuota."),
|
|
922
|
-
fields: z.string().optional().describe('Response field mask.'),
|
|
923
|
-
},
|
|
924
|
-
});
|
|
925
|
-
registerGeneratedTool(registry, {
|
|
926
|
-
name: "analytics_properties_run_report",
|
|
927
|
-
cud: "read",
|
|
928
|
-
description: "Returns a customized report of your Google Analytics event data. Reports contain statistics derived from data collected by the Google Analytics tracking code. T",
|
|
929
|
-
method: { id: "analyticsdata.properties.runReport", httpMethod: "POST", path: "v1beta/{+property}:runReport", baseUrl: "https://analyticsdata.googleapis.com/", requiredParams: ["property"], scopes: S_analyticsdata_v1beta[0] },
|
|
930
|
-
params: [{ "field": "property", "api": "property", "location": "path" }, { "field": "fields", "api": "fields", "location": "query" }],
|
|
931
|
-
hasBody: true,
|
|
932
|
-
shape: {
|
|
933
|
-
account: accountField(),
|
|
934
|
-
property: z.string().describe("A Google Analytics property identifier whose events are tracked. Specified in the URL path and not the body. To learn more, see [where to find your Property ID](https://developers.google.com/analytics"),
|
|
935
|
-
body: coerceJson(z.record(z.string(), z.unknown())).describe("RunReportRequest JSON request body. Top-level fields: cohortSpec, comparisons, currencyCode, dateRanges, dimensionFilter, dimensions, keepEmptyRows, limit, metricAggregations, metricFilter, metrics, offset, +3 more."),
|
|
936
|
-
fields: z.string().optional().describe('Response field mask.'),
|
|
937
|
-
},
|
|
938
|
-
});
|
|
939
884
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.13",
|
|
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",
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@googleapis/admin": "^37.0.0",
|
|
66
|
+
"@googleapis/analyticsadmin": "^22.0.0",
|
|
67
|
+
"@googleapis/analyticsdata": "^10.0.0",
|
|
66
68
|
"@googleapis/calendar": "^20.0.0",
|
|
67
69
|
"@googleapis/chat": "^51.0.0",
|
|
68
70
|
"@googleapis/docs": "^14.0.0",
|