mcp-ga4 1.0.0 → 1.1.0
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 +6 -24
- package/dist/ga4.d.ts +1 -0
- package/dist/ga4.js +33 -0
- package/dist/index.js +5 -0
- package/dist/setup.js +71 -56
- package/dist/tools.js +14 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -20,29 +20,15 @@ This will:
|
|
|
20
20
|
Click **Advanced** then **Go to mcp-ga4 (unsafe)** to continue. This is safe --
|
|
21
21
|
the app only requests read-only access to your analytics data.
|
|
22
22
|
|
|
23
|
-
### 2.
|
|
23
|
+
### 2. Restart Claude Code
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The setup wizard automatically registers the GA4 server with Claude Code.
|
|
26
|
+
Just restart Claude Code to pick it up:
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
- **CLI:** Exit and reopen `claude`
|
|
29
|
+
- **Desktop app:** Quit and reopen the app
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
"ga4": {
|
|
32
|
-
"command": "npx",
|
|
33
|
-
"args": ["-y", "mcp-ga4"],
|
|
34
|
-
"env": {
|
|
35
|
-
"GA4_PROPERTY_ID": "YOUR_PROPERTY_ID",
|
|
36
|
-
"GOOGLE_APPLICATION_CREDENTIALS": "~/.config/mcp-ga4/credentials.json"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
- **Claude Desktop (Mac):** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
43
|
-
- **Claude Code CLI:** `.mcp.json` in your project directory
|
|
44
|
-
|
|
45
|
-
That's it. No Python, no pip, no venv. Just `npx`.
|
|
31
|
+
That's it. No config files to edit, no JSON to paste. Just `npx` and restart.
|
|
46
32
|
|
|
47
33
|
## Usage
|
|
48
34
|
|
|
@@ -72,10 +58,6 @@ Go to https://myaccount.google.com/permissions, remove "mcp-ga4", then re-run `n
|
|
|
72
58
|
Your Google account may not have access to the GA4 property. Check your access at
|
|
73
59
|
GA4 > Admin > Account Access Management.
|
|
74
60
|
|
|
75
|
-
**"OAuth client credentials not found"**
|
|
76
|
-
The setup needs OAuth client credentials. Set `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET`
|
|
77
|
-
environment variables, or on macOS store them in Keychain (the setup will guide you).
|
|
78
|
-
|
|
79
61
|
**Token expired**
|
|
80
62
|
Re-run `npx mcp-ga4-setup` to refresh your credentials.
|
|
81
63
|
|
package/dist/ga4.d.ts
CHANGED
|
@@ -19,5 +19,6 @@ export declare class GA4Manager {
|
|
|
19
19
|
listDataStreams(propertyId: string): Promise<any>;
|
|
20
20
|
listCustomDimensions(propertyId: string): Promise<any>;
|
|
21
21
|
createCustomDimension(propertyId: string, parameterName: string, displayName: string, scope: string, description: string): Promise<any>;
|
|
22
|
+
healthCheck(propertyId: string): Promise<any>;
|
|
22
23
|
listCustomMetrics(propertyId: string): Promise<any>;
|
|
23
24
|
}
|
package/dist/ga4.js
CHANGED
|
@@ -118,6 +118,39 @@ export class GA4Manager {
|
|
|
118
118
|
scope: result.scope,
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
+
async healthCheck(propertyId) {
|
|
122
|
+
const checks = {};
|
|
123
|
+
// Check 1: Credentials file
|
|
124
|
+
try {
|
|
125
|
+
const { readFileSync } = await import("fs");
|
|
126
|
+
readFileSync(this.credentialsFile, "utf-8");
|
|
127
|
+
checks.credentials_file = "ok";
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
checks.credentials_file = `FAIL: ${err.message}`;
|
|
131
|
+
return { status: "unhealthy", checks };
|
|
132
|
+
}
|
|
133
|
+
// Check 2: Token refresh
|
|
134
|
+
try {
|
|
135
|
+
const token = await getAccessToken(this.credentialsFile);
|
|
136
|
+
checks.token_refresh = token ? "ok" : "FAIL: no token returned";
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
checks.token_refresh = `FAIL: ${err.message}`;
|
|
140
|
+
return { status: "unhealthy", checks };
|
|
141
|
+
}
|
|
142
|
+
// Check 3: GA4 API access
|
|
143
|
+
try {
|
|
144
|
+
const data = await this.request(`${ADMIN_API}/properties/${propertyId}/dataStreams`);
|
|
145
|
+
const count = (data.dataStreams || []).length;
|
|
146
|
+
checks.ga4_api = `ok (${count} data stream${count !== 1 ? "s" : ""})`;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
checks.ga4_api = `FAIL: ${err.message}`;
|
|
150
|
+
return { status: "unhealthy", checks };
|
|
151
|
+
}
|
|
152
|
+
return { status: "healthy", property_id: propertyId, checks };
|
|
153
|
+
}
|
|
121
154
|
async listCustomMetrics(propertyId) {
|
|
122
155
|
const data = await withResilience(() => this.request(`${ADMIN_API}/properties/${propertyId}/customMetrics`), "listCustomMetrics");
|
|
123
156
|
const metrics = (data.customMetrics || []).map((m) => ({
|
package/dist/index.js
CHANGED
|
@@ -205,6 +205,11 @@ async function main() {
|
|
|
205
205
|
const result = await ga4.listCustomMetrics(args?.property_id);
|
|
206
206
|
return text(result);
|
|
207
207
|
}
|
|
208
|
+
// ---- Health Check ----
|
|
209
|
+
case "ga4_health_check": {
|
|
210
|
+
const result = await ga4.healthCheck(args?.property_id);
|
|
211
|
+
return text(result);
|
|
212
|
+
}
|
|
208
213
|
// ---- Feedback ----
|
|
209
214
|
case "ga4_send_feedback": {
|
|
210
215
|
const feedbackType = args?.feedback_type;
|
package/dist/setup.js
CHANGED
|
@@ -14,7 +14,7 @@ import { createInterface } from "readline";
|
|
|
14
14
|
import { createServer } from "http";
|
|
15
15
|
import { mkdirSync, writeFileSync, readFileSync, chmodSync } from "fs";
|
|
16
16
|
import { join } from "path";
|
|
17
|
-
import { homedir
|
|
17
|
+
import { homedir } from "os";
|
|
18
18
|
import { execSync } from "child_process";
|
|
19
19
|
import { URL } from "url";
|
|
20
20
|
const CONFIG_DIR = join(homedir(), ".config", "mcp-ga4");
|
|
@@ -22,6 +22,11 @@ const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
|
|
|
22
22
|
const SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"];
|
|
23
23
|
const REDIRECT_PORT = 8087;
|
|
24
24
|
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`;
|
|
25
|
+
// OAuth app credentials (not user secrets -- identifies the app, not the user).
|
|
26
|
+
// Google docs: client_secret for Desktop/CLI OAuth apps is not confidential.
|
|
27
|
+
// Each user gets their own refresh_token via the consent flow below.
|
|
28
|
+
const DEFAULT_CLIENT_ID = "557294086068-o7rb5neg65g28uf65j85q0h60cop40j9.apps.googleusercontent.com";
|
|
29
|
+
const DEFAULT_CLIENT_SECRET = "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";
|
|
25
30
|
// ============================================
|
|
26
31
|
// HELPERS
|
|
27
32
|
// ============================================
|
|
@@ -40,33 +45,9 @@ function ask(question) {
|
|
|
40
45
|
});
|
|
41
46
|
}
|
|
42
47
|
function getOAuthCredentials() {
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (clientId && clientSecret)
|
|
47
|
-
return { clientId, clientSecret };
|
|
48
|
-
// Try macOS Keychain
|
|
49
|
-
if (platform() === "darwin") {
|
|
50
|
-
try {
|
|
51
|
-
if (!clientId) {
|
|
52
|
-
clientId = execSync('security find-generic-password -a mcp-ga4 -s OAUTH_CLIENT_ID -w', { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
53
|
-
}
|
|
54
|
-
if (!clientSecret) {
|
|
55
|
-
clientSecret = execSync('security find-generic-password -a mcp-ga4 -s OAUTH_CLIENT_SECRET -w', { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// Not found in Keychain
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (!clientId || !clientSecret) {
|
|
63
|
-
console.error("OAuth client credentials not found.");
|
|
64
|
-
console.error("Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET environment variables,");
|
|
65
|
-
console.error("or on macOS, store them in Keychain:");
|
|
66
|
-
console.error(' security add-generic-password -a mcp-ga4 -s OAUTH_CLIENT_ID -w \'<your-client-id>\'');
|
|
67
|
-
console.error(' security add-generic-password -a mcp-ga4 -s OAUTH_CLIENT_SECRET -w \'<your-secret>\'');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
48
|
+
// Allow override via env vars (for users with their own GCP project)
|
|
49
|
+
const clientId = process.env.OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
|
|
50
|
+
const clientSecret = process.env.OAUTH_CLIENT_SECRET || DEFAULT_CLIENT_SECRET;
|
|
70
51
|
return { clientId, clientSecret };
|
|
71
52
|
}
|
|
72
53
|
// ============================================
|
|
@@ -212,35 +193,60 @@ async function verifyConnection(propertyId, credsFile) {
|
|
|
212
193
|
return false;
|
|
213
194
|
}
|
|
214
195
|
}
|
|
215
|
-
function
|
|
216
|
-
printHeader("Step 4: Claude Code
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
196
|
+
function registerWithClaude(propertyId, credsFile) {
|
|
197
|
+
printHeader("Step 4: Registering with Claude Code");
|
|
198
|
+
// Try claude mcp add (works if Claude Code CLI is installed)
|
|
199
|
+
try {
|
|
200
|
+
execSync("claude --version", { stdio: "pipe" });
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// claude CLI not found -- fall back to manual instructions
|
|
204
|
+
console.log("Claude Code CLI not detected. Add this to your .mcp.json manually:\n");
|
|
205
|
+
const mcpConfig = {
|
|
206
|
+
ga4: {
|
|
207
|
+
command: "npx",
|
|
208
|
+
args: ["-y", "mcp-ga4"],
|
|
209
|
+
env: {
|
|
210
|
+
GA4_PROPERTY_ID: propertyId,
|
|
211
|
+
GOOGLE_APPLICATION_CREDENTIALS: credsFile,
|
|
212
|
+
},
|
|
224
213
|
},
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (platform() === "darwin") {
|
|
229
|
-
const configPath = join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
230
|
-
console.log(` File: ${configPath}`);
|
|
231
|
-
console.log(" (Or for Claude Code CLI: add to .mcp.json in your project)\n");
|
|
214
|
+
};
|
|
215
|
+
console.log(JSON.stringify({ mcpServers: mcpConfig }, null, 2));
|
|
216
|
+
return false;
|
|
232
217
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
218
|
+
// Remove existing ga4 server if present (ignore errors)
|
|
219
|
+
try {
|
|
220
|
+
execSync("claude mcp remove ga4 -s user", { stdio: "pipe" });
|
|
236
221
|
}
|
|
237
|
-
|
|
238
|
-
|
|
222
|
+
catch {
|
|
223
|
+
// not present, that's fine
|
|
224
|
+
}
|
|
225
|
+
const cmd = `claude mcp add -s user` +
|
|
226
|
+
` -e GA4_PROPERTY_ID=${propertyId}` +
|
|
227
|
+
` -e GOOGLE_APPLICATION_CREDENTIALS=${credsFile}` +
|
|
228
|
+
` ga4 -- npx -y mcp-ga4`;
|
|
229
|
+
try {
|
|
230
|
+
execSync(cmd, { stdio: "inherit" });
|
|
231
|
+
console.log("\nGA4 server registered with Claude Code.");
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
console.error(`\nFailed to register automatically: ${err.message}`);
|
|
236
|
+
console.log("Add this to your .mcp.json manually:\n");
|
|
237
|
+
const mcpConfig = {
|
|
238
|
+
ga4: {
|
|
239
|
+
command: "npx",
|
|
240
|
+
args: ["-y", "mcp-ga4"],
|
|
241
|
+
env: {
|
|
242
|
+
GA4_PROPERTY_ID: propertyId,
|
|
243
|
+
GOOGLE_APPLICATION_CREDENTIALS: credsFile,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
console.log(JSON.stringify({ mcpServers: mcpConfig }, null, 2));
|
|
248
|
+
return false;
|
|
239
249
|
}
|
|
240
|
-
console.log('Add this inside the "mcpServers" key:\n');
|
|
241
|
-
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
242
|
-
// Offer to write .mcp.json
|
|
243
|
-
console.log(`\n${"=".repeat(50)}`);
|
|
244
250
|
}
|
|
245
251
|
// ============================================
|
|
246
252
|
// MAIN
|
|
@@ -256,10 +262,19 @@ async function main() {
|
|
|
256
262
|
const tokens = await runOAuth(clientId, clientSecret);
|
|
257
263
|
const credsFile = saveCredentials(tokens, clientId, clientSecret);
|
|
258
264
|
if (await verifyConnection(propertyId, credsFile)) {
|
|
259
|
-
|
|
265
|
+
const autoRegistered = registerWithClaude(propertyId, credsFile);
|
|
260
266
|
printHeader("Setup Complete!");
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
if (autoRegistered) {
|
|
268
|
+
console.log("GA4 is now connected to Claude Code.\n");
|
|
269
|
+
console.log("IMPORTANT: Restart Claude Code to load the new server.");
|
|
270
|
+
console.log(" - CLI: exit and reopen claude");
|
|
271
|
+
console.log(" - Desktop app: quit and reopen the app\n");
|
|
272
|
+
console.log('Then try asking: "What were my top 10 pages last week?"\n');
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
console.log("After adding the config, restart Claude Code to load the server.\n");
|
|
276
|
+
console.log('Then try asking: "What were my top 10 pages last week?"\n');
|
|
277
|
+
}
|
|
263
278
|
console.log("To update or re-authenticate: npx mcp-ga4-setup");
|
|
264
279
|
console.log("To report issues: use the ga4_send_feedback tool in Claude\n");
|
|
265
280
|
}
|
package/dist/tools.js
CHANGED
|
@@ -168,6 +168,20 @@ export const tools = [
|
|
|
168
168
|
required: ["property_id"],
|
|
169
169
|
},
|
|
170
170
|
},
|
|
171
|
+
{
|
|
172
|
+
name: "ga4_health_check",
|
|
173
|
+
description: "Verify that GA4 credentials are valid and the API is accessible. Run this to diagnose connection issues.",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
property_id: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "GA4 property ID (numeric)",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
required: ["property_id"],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
171
185
|
{
|
|
172
186
|
name: "ga4_send_feedback",
|
|
173
187
|
description: "Send feedback about the GA4 tools. Use when a query didn't work as expected, to request a feature, or ask a question.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ga4",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server for querying Google Analytics 4 with natural language via Claude",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
},
|
|
28
28
|
"keywords": ["mcp", "ga4", "google-analytics", "claude", "analytics"],
|
|
29
29
|
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/mharnett/mcp-ga4.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/mharnett/mcp-ga4/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/mharnett/mcp-ga4#readme",
|
|
30
38
|
"dependencies": {
|
|
31
39
|
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
32
40
|
"cockatiel": "^3.2.1",
|