Skip to main content
ai2026年2月22日6 分钟阅读

MCP 服务器:让 Google Analytics 真正对开发者有用

模型上下文协议服务器如何将 Google Analytics 从营销仪表板转变为开发者友好的数据源,让你可以通过对话方式进行查询。

mcpanalyticsai
MCP 服务器:让 Google Analytics 真正对开发者有用

Google Analytics 一直是开发者们勉强接受的营销工具。你设置跟踪,配置事件,然后将其交给真正喜欢点击仪表板的人。当你自己需要数据时——特定页面的跳出率、新功能的转化漏斗、按国家/地区细分的实时活跃用户——你最终会点击五个嵌套菜单,与 GA4 查询构建器搏斗,并疑惑为什么提问比回答问题需要更多的精力。

模型上下文协议(Model Context Protocol,MCP)彻底改变了这一切。通过将 GA4 的报告 API 封装在 MCP 服务器中,你可以通过 Claude 或任何兼容 MCP 的 AI 助手以对话方式查询你的分析数据。没有仪表板。没有查询构建器。只需提出你想知道的问题。

什么是 MCP 及其重要性

模型上下文协议是 Anthropic 创建的一个开放标准,它定义了 AI 助手如何与外部数据源和工具交互。可以将其视为 AI 的 USB-C 端口——一个通用接口,让任何兼容的助手都能连接到任何兼容的数据源,而无需为每对连接编写自定义集成代码。

在 MCP 出现之前,将 AI 助手连接到你的分析数据意味着构建一个定制的管道:将数据导出到 CSV,粘贴到聊天中,希望上下文窗口足够大。或者构建一个自定义 API 集成,编写提示模板,自行处理身份验证。每个新的数据源都需要一个新的集成。

MCP 将其标准化为三个概念:

  • 资源(Resources) — 服务器公开的结构化数据(你的 GA4 属性、报告、维度、指标)
  • 工具(Tools) — AI 可以调用的操作(运行报告、查询实时数据、列出可用指标)
  • 提示(Prompts) — 服务器提供的可重用提示模板(预先结构化以获得良好结果的常见分析查询)

服务器处理身份验证、数据获取和响应格式。AI 助手处理自然语言理解和结果解释。双方都不需要了解对方的实现细节。

为什么这对分析特别重要

分析平台信息密集但交互贫乏。GA4 拥有极其强大的数据——它跟踪你整个产品中的每个用户会话、每个页面浏览、每个事件、每个转化。但访问这些数据需要你用 GA4 的词汇来思考:维度、指标、细分、日期范围、过滤器、比较。你必须将你的问题翻译成 GA4 的查询模型才能得到答案。

MCP 服务器颠覆了这一点。你用普通语言提出问题。AI 将其翻译成正确的 API 调用,执行它们,并解释结果。“我有一个问题”和“我有一个答案”之间的摩擦从几分钟缩短到几秒钟。

为 GA4 构建 MCP 服务器

实现涉及三个层面:MCP 服务器框架、Google Analytics Data API 客户端以及将自然语言意图映射到 API 调用的胶合逻辑。

项目设置

mkdir ga4-mcp-server
cd ga4-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk @google-analytics/data zod dotenv
npm install -D typescript @types/node

@modelcontextprotocol/sdk 包提供了服务器框架。@google-analytics/data 是 Google 官方的 GA4 Data API 客户端库(报告 API,而非 Admin API)。zod 处理工具参数的输入验证。

使用 Google 进行身份验证

GA4 API 身份验证使用 Google Cloud 服务帐号。在 Google Cloud Console 中创建一个,下载 JSON 密钥文件,并授予其对你的 GA4 属性的“查看者”访问权限。

// src/analytics-client.ts
import { BetaAnalyticsDataClient } from "@google-analytics/data";

const analyticsClient = new BetaAnalyticsDataClient({
  keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
});

const propertyId = process.env.GA4_PROPERTY_ID;

export async function runReport(
  dimensions: string[],
  metrics: string[],
  startDate: string,
  endDate: string,
  dimensionFilter?: Record<string, string>
) {
  const request: any = {
    property: `properties/${propertyId}`,
    dimensions: dimensions.map((d) => ({ name: d })),
    metrics: metrics.map((m) => ({ name: m })),
    dateRanges: [{ startDate, endDate }],
  };

  if (dimensionFilter) {
    const filterKey = Object.keys(dimensionFilter)[0];
    request.dimensionFilter = {
      filter: {
        fieldName: filterKey,
        stringFilter: {
          value: dimensionFilter[filterKey],
          matchType: "EXACT",
        },
      },
    };
  }

  const [response] = await analyticsClient.runReport(request);
  return formatReportResponse(response);
}

function formatReportResponse(response: any) {
  if (!response.rows || response.rows.length === 0) {
    return { data: [], summary: "No data found for the specified query." };
  }

  const headers = [
    ...(response.dimensionHeaders?.map((h: any) => h.name) ?? []),
    ...(response.metricHeaders?.map((h: any) => h.name) ?? []),
  ];

  const rows = response.rows.map((row: any) => {
    const values = [
      ...(row.dimensionValues?.map((v: any) => v.value) ?? []),
      ...(row.metricValues?.map((v: any) => v.value) ?? []),
    ];
    return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
  });

  return {
    data: rows,
    rowCount: response.rowCount,
    summary: `Returned ${rows.length} rows.`,
  };
}

MCP 服务器

// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { runReport, getRealtimeData, getAvailableMetrics } from "./analytics-client";

const server = new McpServer({
  name: "ga4-analytics",
  version: "1.0.0",
});

// Tool: Run a custom report
server.tool(
  "run_report",
  "Run a Google Analytics 4 report with specified dimensions, metrics, and date range",
  {
    dimensions: z.array(z.string()).describe("GA4 dimensions like 'pagePath', 'country', 'deviceCategory'"),
    metrics: z.array(z.string()).describe("GA4 metrics like 'activeUsers', 'sessions', 'bounceRate'"),
    startDate: z.string().describe("Start date in YYYY-MM-DD format or relative like '7daysAgo'"),
    endDate: z.string().describe("End date in YYYY-MM-DD format or 'today'"),
    filter: z.record(z.string()).optional().describe("Optional dimension filter as key-value pair"),
  },
  async ({ dimensions, metrics, startDate, endDate, filter }) => {
    const result = await runReport(dimensions, metrics, startDate, endDate, filter);
    return {
      content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
    };
  }
);

// Tool: Get real-time active users
server.tool(
  "realtime_users",
  "Get current real-time active users, optionally segmented by a dimension",
  {
    dimension: z.string().optional().describe("Optional dimension to segment by, like 'country' or 'pagePath'"),
  },
  async ({ dimension }) => {
    const result = await getRealtimeData(dimension);
    return {
      content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
    };
  }
);

// Tool: List available metrics and dimensions
server.tool(
  "list_metrics",
  "List all available GA4 metrics and dimensions for the connected property",
  {},
  async () => {
    const result = await getAvailableMetrics();
    return {
      content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
    };
  }
);

// Resource: Property metadata
server.resource(
  "property-info",
  "ga4://property/info",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify({
        propertyId: process.env.GA4_PROPERTY_ID,
        description: "Connected GA4 property for analytics queries",
      }),
    }],
  })
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

这为 AI 助手提供了三个工具:运行任意报告、检查实时数据以及发现可用的指标和维度。发现工具很重要——它让 AI 能够找出正确的字段名称,而无需你记住是 activeUsers 还是 active_userstotalUsers

实时数据端点

// Added to analytics-client.ts
export async function getRealtimeData(dimension?: string) {
  const request: any = {
    property: `properties/${propertyId}`,
    metrics: [{ name: "activeUsers" }],
  };

  if (dimension) {
    request.dimensions = [{ name: dimension }];
  }

  const [response] = await analyticsClient.runRealtimeReport(request);
  return formatReportResponse(response);
}

实时数据是最高价值的功能之一。在仪表板中,你需要导航到实时部分,等待它加载,然后读取数字。通过 MCP,你输入“现在网站上有多少人?”并在两秒内得到答案。

开发者真正想运行的实际查询

理论能力很无聊。以下是我每天都会运行的实际查询,以及它们作为对 AI 的对话请求的样子:

流量分析

“本周按会话数排名前 10 的页面是哪些,每个页面与上周相比如何?”

AI 将其翻译为两次 runReport 调用——一次用于本周,一次用于上周——然后计算差异并呈现比较表。在 GA4 界面中,这需要配置比较日期范围,选择正确的报告,添加页面维度,按会话排序,并限制为 10 个。通过 MCP,它只是一句话。

“显示过去 30 天来自自然搜索的流量,按着陆页细分。”

这成为一个报告,其中 sessionDefaultChannelGroup 作为维度过滤器(过滤为“Organic Search”),landingPagePlusQueryString 作为维度,sessionsengagedSessions 作为指标。

转化调试

“过去两周,/pricing 的跳出率与 /features 相比如何?”

两次过滤报告调用,结果并排比较。这种问题通过对话方式提问需要 30 秒,而通过 GA4 界面回答则需要 3 分钟。

“哪些国家/地区的注册事件转化率最高?”

一个报告,包含 country 维度,eventCount 指标(过滤到 sign_up 事件),以及 sessions 以计算转化率。AI 甚至可以直接进行除法并呈现百分比。

性能监控

“本月移动设备与桌面设备每次会话的平均参与时间是多少?”

deviceCategory 细分,测量 averageSessionDurationengagedSessions。简单的查询,但在 GA4 中需要导航到正确的报告并配置细分。

“本周是否有任何页面浏览量超过 1000 次但参与率低于 30%?”

这是对话式分析真正发挥作用的地方。AI 运行报告,以编程方式过滤结果,并仅返回符合这两个条件的页面。在 GA4 中,你需要导出到电子表格并手动过滤,或者构建自定义探索。

连接到 Claude 和 AI 助手

Claude Desktop 配置

一旦你的 MCP 服务器构建完成,将其连接到 Claude Desktop 只需要修改一个配置文件:

{
  "mcpServers": {
    "ga4-analytics": {
      "command": "node",
      "args": ["/path/to/ga4-mcp-server/dist/server.js"],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "/path/to/service-account-key.json",
        "GA4_PROPERTY_ID": "123456789"
      }
    }
  }
}

在 macOS 上,这位于 ~/Library/Application Support/Claude/claude_desktop_config.json。在 Windows 上,它位于 %APPDATA%\Claude\claude_desktop_config.json

重启 Claude Desktop 后,GA4 工具将出现在工具菜单中。Claude 现在可以直接在对话中调用它们。

Claude Code 集成

对于主要在终端中工作的开发者,Claude Code 原生支持 MCP 服务器。将服务器添加到你的项目的 .mcp.json 中:

{
  "mcpServers": {
    "ga4-analytics": {
      "command": "node",
      "args": ["./mcp-servers/ga4/dist/server.js"],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "./credentials/ga4-service-account.json",
        "GA4_PROPERTY_ID": "123456789"
      }
    }
  }
}

现在你无需离开编辑器即可查询分析数据。在调试性能回归时,你可以询问“本周与上周相比,/checkout 的跳出率是多少?”,而无需切换上下文到浏览器。

与其他兼容 MCP 的客户端一起使用

MCP 作为标准的美妙之处在于任何兼容的客户端都可以工作。Cursor、Windsurf 和其他支持 MCP 的编辑器都可以连接到同一个服务器。你只需构建一次服务器,它就可以在任何地方工作。

对话式分析的实际示例

让我带你了解一个我经常使用的真实工作流程。

晨间检查

我以询问 Claude 开始一天:“给我总结一下昨天的流量——总会话数、活跃用户、前 5 个页面,以及任何参与率低于 25% 的页面。”

AI 进行三次工具调用:一次用于整体指标,一次用于热门页面,一次用于低参与度页面。它会返回类似以下内容:

昨天:2,341 次会话,来自 1,892 个独立用户。热门页面是 /home (580 次会话)、/pricing (412)、/docs/getting-started (389)、/blog/mcp-guide (267)、/features (198)。有两个页面的参与率低于 25%:/legal/terms (12%) 和 /blog/old-post-2024 (22%)。

这在 GA4 界面中需要 5-10 分钟。通过 MCP,只需 15 秒。

功能发布监控

当我发布新功能时,我想知道人们是否找到了它并正在使用它。“比较 /features/new-dashboard 在发布前 3 天与发布后 3 天的流量,按获取渠道细分。”

AI 运行比较,识别哪些渠道正在推动发现(通常在发布后的最初几天,直接流量和自然搜索落后于社交和推荐),并突出显示任何异常。

调试用户投诉

当用户报告“网站感觉很慢”时,我可以问:“过去 7 天,按页面划分的平均页面加载时间是多少,按最慢的排序?”如果 GA4 有 Web Vitals 数据(通过测量协议或 gtag 事件),MCP 服务器可以显示这些数据,而无需我打开浏览器。

每周报告

“生成我们前 20 个页面的会话数、新用户、参与率和转化率的周环比比较。”

这会生成一个格式化的表格,我可以直接放入 Slack 消息或团队更新中。无需电子表格操作,无需截图裁剪。

构建高级功能

缓存层

GA4 的 API 有速率限制(免费层通常每个属性每分钟 10 个请求)。对于可能频繁查询的 MCP 服务器,添加一个缓存层:

import { createHash } from "crypto";

const cache = new Map<string, { data: any; expiry: number }>();

function getCacheKey(dimensions: string[], metrics: string[], startDate: string, endDate: string): string {
  const input = JSON.stringify({ dimensions, metrics, startDate, endDate });
  return createHash("md5").update(input).digest("hex");
}

export async function cachedRunReport(
  dimensions: string[],
  metrics: string[],
  startDate: string,
  endDate: string
) {
  const key = getCacheKey(dimensions, metrics, startDate, endDate);
  const cached = cache.get(key);

  if (cached && cached.expiry > Date.now()) {
    return cached.data;
  }

  const result = await runReport(dimensions, metrics, startDate, endDate);

  // Cache for 5 minutes (real-time queries get shorter TTL)
  const ttl = startDate === "today" ? 60_000 : 300_000;
  cache.set(key, { data: result, expiry: Date.now() + ttl });

  return result;
}

实时查询的 TTL 为 1 分钟。历史查询的 TTL 为 5 分钟。这使你在对话会话中保持在速率限制内,AI 可能会在短时间内进行多次相关查询。

多属性支持

如果你管理多个产品或客户端,扩展服务器以支持在 GA4 属性之间切换:

server.tool(
  "switch_property",
  "Switch to a different GA4 property",
  {
    propertyId: z.string().describe("The GA4 property ID to switch to"),
  },
  async ({ propertyId }) => {
    // Validate access
    const properties = await listAccessibleProperties();
    if (!properties.includes(propertyId)) {
      return {
        content: [{ type: "text", text: `No access to property ${propertyId}` }],
      };
    }

    currentPropertyId = propertyId;
    return {
      content: [{ type: "text", text: `Switched to property ${propertyId}` }],
    };
  }
);

现在你可以说“切换到 staging 属性并显示我昨天的流量”,而无需重新配置任何内容。

安全注意事项

运行访问分析数据的 MCP 服务器会引入一些需要深思熟虑的安全问题。

凭证管理

服务帐号密钥文件是最敏感的部分。切勿将其提交到版本控制。使用环境变量或秘密管理器。如果你在本地运行 MCP 服务器(这是 Claude Desktop 的常见情况),请将密钥文件存储在项目目录之外,并限制文件权限:

chmod 600 /path/to/service-account-key.json

对于团队环境,考虑使用 Google Cloud 的 Workload Identity Federation 而不是密钥文件。它通过使用与运行时身份绑定的短期令牌来完全消除静态凭证。

最小权限原则

服务帐号应具有对 GA4 的只读访问权限。具体来说,在 GA4 属性级别授予“查看者”角色——而不是“编辑者”或“管理员”角色。MCP 服务器永远不需要修改你的分析配置、创建受众或更改测量设置。

在 Google Cloud 项目级别,仅授予 analyticsdata.readonly 范围。这可以防止服务帐号在密钥泄露时访问任何其他 Google Cloud 服务。

数据暴露范围

请谨慎对待 MCP 服务器可以访问的数据。GA4 可能包含 PII——用户 ID、客户端 ID、IP 派生的地理位置。你的 MCP 服务器应剥离或屏蔽对你希望支持的查询不必要的字段。

const BLOCKED_DIMENSIONS = ["userID", "clientId"];

function validateDimensions(dimensions: string[]) {
  const blocked = dimensions.filter((d) => BLOCKED_DIMENSIONS.includes(d));
  if (blocked.length > 0) {
    throw new Error(`Blocked dimensions for privacy: ${blocked.join(", ")}`);
  }
}

如果其他团队成员可以与 MCP 服务器交互,这一点尤其重要。你需要防止可能暴露个人用户数据的随意查询。

传输安全

MCP 目前在使用 Claude Desktop 时通过 stdio(标准输入/输出)运行,这意味着通信发生在本地进程边界内。没有网络暴露。但是,如果你通过 HTTP 部署 MCP 服务器(使用 SSE 传输),你需要 TLS、身份验证令牌和速率限制——将其视为任何其他 API 端点。

审计日志

记录 MCP 服务器处理的每个查询。这不仅是为了安全——也是为了问责。当有人问“谁何时查询了我们的分析数据”时,你应该有答案。

function logQuery(tool: string, params: Record<string, unknown>) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    tool,
    params,
    propertyId: currentPropertyId,
  }));
}

权衡和限制

哪些方面效果好

对话式分析消除了开发者的 GA4 学习曲线。你不需要知道 GA4 的维度和指标名称、报告类型或过滤语法。AI 处理翻译。对于临时问题——那种你只问一次且不再重复的问题——这比仪表板快得多。

将分析与开发上下文结合起来非常强大。当你在 Claude Code 中调试时,可以同时检查分析数据,反馈循环会收紧。你可以在一个地方看到代码、错误日志和用户影响。

哪些方面效果不佳

需要迭代细化的复杂探索在 GA4 的探索界面中仍然更快。如果你正在构建一个具有多个阶段、自定义细分和比较组的漏斗分析,可视化构建器可以提供即时反馈,这是对话式界面无法比拟的。

自动刷新的实时仪表板超出了 MCP 当前的模型。MCP 是请求-响应式的——你提问,它回答。对于显示实时用户的实时更新仪表板,像 GA4 的实时视图或自定义 Grafana 面板这样的专用工具更好。

数据量是一个限制。GA4 Data API 每次请求最多返回 100,000 行。对于每天有数百万事件的属性,你可能需要具体说明维度和日期范围,以避免达到此限制。API 对于大型数据集也有采样行为——对于高基数查询,结果可能是估计值而非精确值。

MCP 与直接 API 集成

如果你正在构建需要分析数据的产品功能(例如你的 SaaS 的管理仪表板),请直接使用 GA4 API。MCP 用于人机交互查询,而不是程序化数据管道。当你确切知道要运行什么查询时,自然语言翻译和 AI 解释的开销是不必要的。

当问题是非结构化的、当你不知道确切要寻找什么、或者当构建适当集成的努力对于一次性问题不值得时,MCP 就会大放异彩。它相当于打开 REPL 而不是编写脚本的分析工具。

入门

如果你想自己尝试一下,最简单的路径是:

  1. 创建一个 Google Cloud 项目并启用 Google Analytics Data API。
  2. 创建一个服务帐号,并授予对你的 GA4 属性的查看者访问权限。
  3. 下载服务帐号密钥 JSON 文件。
  4. 如上所述克隆或构建 MCP 服务器。
  5. 将服务器添加到你的 Claude Desktop 或 Claude Code 配置中。
  6. 开始提问。

如果你已经有一个 GA4 属性,整个设置大约需要 30 分钟。回报是立竿见影的——当你第一次在 5 秒内而不是 5 分钟内得到分析问题的答案时,你将不想再回到仪表板。

分析数据是大多数开发团队中最未充分利用的资产之一。不是因为它没有价值,而是因为访问它一直不方便,足以阻止随意查询。MCP 消除了这种摩擦。数据是相同的。问题是相同的。改变的是回答时间。

DU

Danil Ulmashev

Full Stack Developer

有兴趣一起合作吗?