Skip to main content
ai2026년 2월 22일6분 소요

MCP 서버: 개발자에게 Google Analytics를 실제로 유용하게 만드는 방법

Model Context Protocol 서버가 Google Analytics를 마케팅 대시보드에서 대화형으로 쿼리할 수 있는 개발자 친화적인 데이터 소스로 전환하는 방법.

mcpanalyticsai
MCP 서버: 개발자에게 Google Analytics를 실제로 유용하게 만드는 방법

Google Analytics는 개발자들이 참아온 마케팅 도구였습니다. 추적을 설정하고, 이벤트를 구성한 다음, 대시보드를 클릭하는 것을 실제로 즐기는 사람에게 넘겨줍니다. 특정 페이지의 이탈률, 새로운 기능의 전환 퍼널, 국가별 실시간 활성 사용자 등 직접 데이터가 필요할 때면, 다섯 개의 중첩된 메뉴를 클릭하고, GA4 쿼리 빌더와 씨름하며, 질문하는 데 답변하는 것보다 더 많은 노력이 드는 이유를 궁금해하게 됩니다.

Model Context Protocol은 이를 완전히 바꿉니다. GA4의 보고 API를 MCP 서버로 감싸면, Claude 또는 MCP 호환 AI 비서를 통해 분석 데이터를 대화형으로 쿼리할 수 있습니다. 대시보드도, 쿼리 빌더도 필요 없습니다. 그저 알고 싶은 것을 물어보세요.

MCP란 무엇이며 왜 중요한가

Model Context Protocol은 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는 GA4 Data API(보고 API이며 Admin API가 아님)를 위한 Google의 공식 클라이언트 라이브러리입니다. 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 비서에게 세 가지 도구를 제공합니다: 임의 보고서 실행, 실시간 데이터 확인, 사용 가능한 측정항목 및 측정기준 검색. 검색 도구는 중요합니다. activeUsers인지 active_users인지 totalUsers인지 기억할 필요 없이 AI가 올바른 필드 이름을 알아낼 수 있도록 합니다.

실시간 데이터 엔드포인트

// 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를 통해 "지금 사이트에 몇 명이 있나요?"라고 입력하면 2초 만에 답변을 얻을 수 있습니다.

개발자들이 실제로 실행하고 싶어 하는 실제 쿼리

이론적인 기능은 지루합니다. 다음은 제가 매일 실행하는 실제 쿼리와 AI에 대한 대화형 요청으로 어떻게 보이는지입니다.

트래픽 분석

"이번 주 세션 수 기준 상위 10개 페이지는 무엇이며, 각 페이지는 지난주와 어떻게 비교되나요?"

AI는 이를 두 개의 runReport 호출(하나는 이번 주, 다른 하나는 지난주)로 번역한 다음, 차이를 계산하고 비교 테이블을 제시합니다. GA4 인터페이스에서는 비교 날짜 범위를 구성하고, 올바른 보고서를 선택하고, 페이지 측정기준을 추가하고, 세션별로 정렬하고, 10개로 제한해야 합니다. MCP를 통해서는 한 문장으로 끝납니다.

"지난 30일 동안의 유기적 검색 트래픽을 랜딩 페이지별로 분류하여 보여주세요."

이는 sessionDefaultChannelGroup을 측정기준 필터("Organic Search"로 필터링됨), landingPagePlusQueryString을 측정기준, sessionsengagedSessions를 측정항목으로 하는 단일 보고서가 됩니다.

전환 디버깅

"지난 2주 동안 /pricing 페이지의 이탈률은 /features 페이지와 비교하여 어떤가요?"

두 개의 필터링된 보고서 호출, 결과는 나란히 비교됩니다. 이는 대화형으로 질문하는 데 30초가 걸리고 GA4 인터페이스를 통해 답변하는 데 3분이 걸리는 종류의 질문입니다.

"가입 이벤트에 대한 전환율이 가장 높은 국가는 어디인가요?"

country 측정기준, sign_up 이벤트로 필터링된 eventCount 측정항목, 그리고 전환율을 계산하기 위한 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는 대화에서 직접 이 도구들을 호출할 수 있습니다.

DU

Danil Ulmashev

Full Stack Developer

함께 일하는 데 관심이 있으신가요?