MCP 服务器:让 Google Analytics 真正对开发者有用
模型上下文协议服务器如何将 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_users 或 totalUsers。
实时数据端点
// 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 作为维度,sessions 和 engagedSessions 作为指标。
转化调试
“过去两周,/pricing 的跳出率与 /features 相比如何?”
两次过滤报告调用,结果并排比较。这种问题通过对话方式提问需要 30 秒,而通过 GA4 界面回答则需要 3 分钟。
“哪些国家/地区的注册事件转化率最高?”
一个报告,包含 country 维度,eventCount 指标(过滤到 sign_up 事件),以及 sessions 以计算转化率。AI 甚至可以直接进行除法并呈现百分比。
性能监控
“本月移动设备与桌面设备每次会话的平均参与时间是多少?”
按 deviceCategory 细分,测量 averageSessionDuration 和 engagedSessions。简单的查询,但在 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 而不是编写脚本的分析工具。
入门
如果你想自己尝试一下,最简单的路径是:
- 创建一个 Google Cloud 项目并启用 Google Analytics Data API。
- 创建一个服务帐号,并授予对你的 GA4 属性的查看者访问权限。
- 下载服务帐号密钥 JSON 文件。
- 如上所述克隆或构建 MCP 服务器。
- 将服务器添加到你的 Claude Desktop 或 Claude Code 配置中。
- 开始提问。
如果你已经有一个 GA4 属性,整个设置大约需要 30 分钟。回报是立竿见影的——当你第一次在 5 秒内而不是 5 分钟内得到分析问题的答案时,你将不想再回到仪表板。
分析数据是大多数开发团队中最未充分利用的资产之一。不是因为它没有价值,而是因为访问它一直不方便,足以阻止随意查询。MCP 消除了这种摩擦。数据是相同的。问题是相同的。改变的是回答时间。