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 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. Configure Claude Code
23
+ ### 2. Restart Claude Code
24
24
 
25
- Copy the JSON snippet from the setup wizard into your Claude Code MCP config.
25
+ The setup wizard automatically registers the GA4 server with Claude Code.
26
+ Just restart Claude Code to pick it up:
26
27
 
27
- The snippet looks like:
28
+ - **CLI:** Exit and reopen `claude`
29
+ - **Desktop app:** Quit and reopen the app
28
30
 
29
- ```json
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, platform } from "os";
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
- // Try env vars first
44
- let clientId = process.env.OAUTH_CLIENT_ID || "";
45
- let clientSecret = process.env.OAUTH_CLIENT_SECRET || "";
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 generateConfig(propertyId, credsFile) {
216
- printHeader("Step 4: Claude Code Configuration");
217
- const mcpConfig = {
218
- ga4: {
219
- command: "npx",
220
- args: ["-y", "mcp-ga4"],
221
- env: {
222
- GA4_PROPERTY_ID: propertyId,
223
- GOOGLE_APPLICATION_CREDENTIALS: credsFile,
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
- console.log("Add this to your Claude Code MCP configuration:\n");
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
- else if (platform() === "win32") {
234
- const configPath = join(homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
235
- console.log(` File: ${configPath}\n`);
218
+ // Remove existing ga4 server if present (ignore errors)
219
+ try {
220
+ execSync("claude mcp remove ga4 -s user", { stdio: "pipe" });
236
221
  }
237
- else {
238
- console.log(" Add to .mcp.json in your project directory:\n");
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
- generateConfig(propertyId, credsFile);
265
+ const autoRegistered = registerWithClaude(propertyId, credsFile);
260
266
  printHeader("Setup Complete!");
261
- console.log("You can now use Claude to query your GA4 data.");
262
- console.log('Try asking: "What were my top 10 pages last week?"\n');
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.0.0",
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",