Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 92 additions & 7 deletions bun.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,5 +430,14 @@
"veo/**",
"shared/**"
]
},
"google-analytics": {
"site": "google-analytics",
"entrypoint": "./dist/server/main.js",
"platformName": "kubernetes-bun",
"watch": [
"google-analytics/**",
"shared/**"
]
}
}
33 changes: 33 additions & 0 deletions google-analytics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Dependencies
node_modules/

# Build output
dist/

# Environment
.env
.env.local

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Data
data/
*.db
*.sqlite

# Template files
# Note: Keep app.json.example, ignore app.json if it's just a copy
# app.json

13 changes: 13 additions & 0 deletions google-analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# google-analytics

Official Google Analytics (GA4) MCP integration

## Getting Started

1. Configure your MCP in `server/types/env.ts`
2. Implement tools in `server/tools/`
3. Rename `app.json.example` to `app.json` and customize
4. Add to `deploy.json` for deployment
5. Test with `bun run dev`

See [template-minimal/README.md](../template-minimal/README.md) for detailed instructions.
32 changes: 32 additions & 0 deletions google-analytics/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"scopeName": "deco",
"name": "google-analytics",
"friendlyName": "Google Analytics",
"connection": {
"type": "HTTP",
"url": "https://sites-google-analytics.decocache.com/mcp"
},
"description": "Integrate with Google Analytics 4 (GA4) to query reports, fetch realtime data, and manage analytics properties.",
"icon": "https://developers.google.com/favicon.ico",
"unlisted": false,
"auth": {
"type": "oauth2"
},
"metadata": {
"categories": [
"Analytics",
"Marketing",
"Data"
],
"official": false,
"tags": [
"google",
"analytics",
"ga4",
"data",
"reporting"
],
"short_description": "Integrate with Google Analytics 4 (GA4) to query reports, fetch realtime data, and manage analytics properties.",
"mesh_description": "The Google Analytics 4 (GA4) MCP provides comprehensive programmatic access to your Analytics data. **Key Features** - Query custom reports with dimensions and metrics using the Data API. Fetch realtime active users data. Retrieve metadata for custom dimensions and metrics. Navigate your account hierarchy. **Use Cases** - Perfect for marketers and developers needing to automate analytics reporting, identify top-performing content, or monitor active audiences directly from the LLM. **Authentication** - Uses OAuth 2.0 with Google Analytics read-only scope for secure access."
}
}
25 changes: 25 additions & 0 deletions google-analytics/app.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"scopeName": "deco",
"name": "my-mcp",
"friendlyName": "My MCP",
"connection": {
"type": "HTTP",
"url": "https://sites-my-mcp.decocache.com/mcp"
},
"description": "Short description of what this MCP does (1-2 sentences)",
"icon": "https://assets.decocache.com/mcp/{uuid}/icon.png",
"unlisted": false,
"auth": {
"type": "token",
"header": "Authorization",
"prefix": "Bearer"
},
"metadata": {
"categories": ["Productivity"],
"official": false,
"tags": ["example", "template", "mcp"],
"short_description": "Short description of what this MCP does",
"mesh_description": "Detailed description of your MCP (max 1500 characters). Explain what it does, key features, use cases, and authentication method. Use **bold** for feature names. Example: **Key Features** - Feature 1 description. **Use Cases** - Use case description. **Authentication** - How to authenticate. Perfect for developers who need [your use case]. Provides [your benefit]."
}
}

30 changes: 30 additions & 0 deletions google-analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "google-analytics",
"version": "1.0.0",
"description": "Official Google Analytics (GA4) MCP integration",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run --hot server/main.ts",
"check": "tsc --noEmit",
"build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js",
"build": "bun run build:server"
},
"dependencies": {
"@decocms/runtime": "1.2.5",
"@google-analytics/admin": "^9.0.1",
"@google-analytics/data": "^5.2.1",
"google-auth-library": "^10.6.2",
"zod": "^4.0.0"
},
"devDependencies": {
"@decocms/mcps-shared": "workspace:*",
"@modelcontextprotocol/sdk": "1.25.1",
"bun-types": "^1.3.7",
"deco-cli": "^0.28.0",
"typescript": "^5.7.2"
},
"engines": {
"node": ">=22.0.0"
}
}
13 changes: 13 additions & 0 deletions google-analytics/server/instructions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const instructions = `You are a Google Analytics 4 (GA4) specialized assistant.
Your goal is to help the user query their Google Analytics 4 data effectively.

IMPORTANT INSTRUCTION FOR ALL INTERACTIONS:
If a user asks you to fetch Analytics data or run a report, and they DO NOT provide a GA4 property ID (e.g., 'properties/1234567'), you MUST strictly:
1. First use the \`get-account-summaries\` tool to discover available GA4 properties and accounts for the authenticated user.
2. If multiple properties exist, list them to the user and ask them which property they want to query.
3. If they provide a property, use it for \`run-report\` or \`run-realtime-report\`.
4. You should use \`get-custom-dimensions-and-metrics\` and \`get-property-details\` if you need to know what custom configurations are available before crafting a complex query.
5. In \`run-report\`, always ensure \`dateRanges\` follows the structure like \`{ startDate: "30daysAgo", endDate: "today" }\`.
6. Ensure dimensions and metrics match GA4 standard names (e.g. \`sessionSource\`, \`activeUsers\`, \`screenPageViews\`).

Remember that property names always start with "properties/" followed by the numeric ID.`;
17 changes: 17 additions & 0 deletions google-analytics/server/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Env } from "../types/env.ts";

/**
* Get Google OAuth access token from environment context
* @param env - The environment containing the mesh request context
* @returns The OAuth access token
* @throws Error if not authenticated
*/
export const getGoogleAccessToken = (env: Env): string => {
const authorization = env.MESH_REQUEST_CONTEXT?.authorization;
if (!authorization) {
throw new Error(
"Not authenticated. Please authorize with Google Analytics first.",
);
}
return authorization.replace(/^Bearer\s+/i, "");
};
28 changes: 28 additions & 0 deletions google-analytics/server/lib/ga-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { OAuth2Client } from "google-auth-library";
import { BetaAnalyticsDataClient } from "@google-analytics/data";
import { AnalyticsAdminServiceClient } from "@google-analytics/admin";
import type { Env } from "../types/env.ts";
import { getGoogleAccessToken } from "./env.ts";

export interface GaClientConfig {
accessToken: string;
}

export class GaClient {
public dataClient: BetaAnalyticsDataClient;
public adminClient: AnalyticsAdminServiceClient;

constructor(config: GaClientConfig) {
const authClient = new OAuth2Client();
authClient.setCredentials({ access_token: config.accessToken });

// We instantiate both clients utilizing the explicit oauth object.
this.dataClient = new BetaAnalyticsDataClient({ authClient });
this.adminClient = new AnalyticsAdminServiceClient({ authClient });
}

static fromEnv(env: Env): GaClient {
const accessToken = getGoogleAccessToken(env);
return new GaClient({ accessToken });
}
}
33 changes: 33 additions & 0 deletions google-analytics/server/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Google Analytics (GA4) MCP Server
*
* This MCP provides tools for interacting with Google Analytics 4,
* including querying reports, fetching realtime data, and retrieving property details.
*/
import { withRuntime } from "@decocms/runtime";
import { serve } from "@decocms/mcps-shared/serve";
import { createGoogleOAuth } from "@decocms/mcps-shared/google-oauth";

import { tools } from "./tools/index.ts";
import type { Env } from "./types/env.ts";

export type { Env };

/**
* Configure the MCP runtime
*
* This sets up:
* - OAuth configuration for Google Analytics read-only access
* - Tools (from ./tools/index.ts)
*/
const runtime = withRuntime<Env>({
tools: (env: Env) => tools.map((createTool) => createTool(env)),
oauth: createGoogleOAuth({
scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
}),
});

// Start the server
if (runtime.fetch) {
serve(runtime.fetch);
}
24 changes: 24 additions & 0 deletions google-analytics/server/tools/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from "zod";
import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../types/env.ts";
import { GaClient } from "../lib/ga-client.ts";

export const getAccountSummariesTool = (env: Env) =>
createPrivateTool({
id: "get-account-summaries",
description: "Retrieves information about the user's Google Analytics accounts and properties.",
inputSchema: z.object({}),
execute: async () => {
const client = GaClient.fromEnv(env);

try {
const [response] = await client.adminClient.listAccountSummaries({});

return {
response: response
};
} catch (error: any) {
throw new Error(`Failed to retrieve account summaries: ${error.message}`);
}
},
});
26 changes: 26 additions & 0 deletions google-analytics/server/tools/ads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { z } from "zod";
import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../types/env.ts";
import { GaClient } from "../lib/ga-client.ts";

export const listGoogleAdsLinksTool = (env: Env) =>
createPrivateTool({
id: "list-google-ads-links",
description: "Returns a list of links to Google Ads accounts for a property.",
inputSchema: z.object({
property: z.string().describe("The Google Analytics Property identifier e.g. 'properties/1234567'"),
}),
execute: async ({ context: args }) => {
const client = GaClient.fromEnv(env);

try {
const [response] = await client.adminClient.listGoogleAdsLinks({
parent: args.property,
});

return { response };
} catch (error: any) {
throw new Error(`Failed to retrieve Google Ads links: ${error.message}`);
}
},
});
Loading