mcp-ga4 1.0.1 → 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
 
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,8 @@ 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
+ import { execSync } from "child_process";
18
19
  import { URL } from "url";
19
20
  const CONFIG_DIR = join(homedir(), ".config", "mcp-ga4");
20
21
  const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
@@ -192,35 +193,60 @@ async function verifyConnection(propertyId, credsFile) {
192
193
  return false;
193
194
  }
194
195
  }
195
- function generateConfig(propertyId, credsFile) {
196
- printHeader("Step 4: Claude Code Configuration");
197
- const mcpConfig = {
198
- ga4: {
199
- command: "npx",
200
- args: ["-y", "mcp-ga4"],
201
- env: {
202
- GA4_PROPERTY_ID: propertyId,
203
- 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
+ },
204
213
  },
205
- },
206
- };
207
- console.log("Add this to your Claude Code MCP configuration:\n");
208
- if (platform() === "darwin") {
209
- const configPath = join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
210
- console.log(` File: ${configPath}`);
211
- 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;
212
217
  }
213
- else if (platform() === "win32") {
214
- const configPath = join(homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
215
- 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" });
216
221
  }
217
- else {
218
- 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;
219
249
  }
220
- console.log('Add this inside the "mcpServers" key:\n');
221
- console.log(JSON.stringify(mcpConfig, null, 2));
222
- // Offer to write .mcp.json
223
- console.log(`\n${"=".repeat(50)}`);
224
250
  }
225
251
  // ============================================
226
252
  // MAIN
@@ -236,10 +262,19 @@ async function main() {
236
262
  const tokens = await runOAuth(clientId, clientSecret);
237
263
  const credsFile = saveCredentials(tokens, clientId, clientSecret);
238
264
  if (await verifyConnection(propertyId, credsFile)) {
239
- generateConfig(propertyId, credsFile);
265
+ const autoRegistered = registerWithClaude(propertyId, credsFile);
240
266
  printHeader("Setup Complete!");
241
- console.log("You can now use Claude to query your GA4 data.");
242
- 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
+ }
243
278
  console.log("To update or re-authenticate: npx mcp-ga4-setup");
244
279
  console.log("To report issues: use the ga4_send_feedback tool in Claude\n");
245
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.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",