import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
// 基本ツール
import {
listAccounts,
getPropertyDetails,
runReport,
runRealtimeReport,
getMetadata,
} from "./tools/basic/index.js";
// 分析ツール
import {
getTrafficSummary,
getTopPages,
getTrafficSources,
getDeviceBreakdown,
getGeoBreakdown,
comparePeriods,
getLandingPages,
getExitPages,
getUserJourney,
getConversionFunnel,
getHourlyTraffic,
getDailyTrend,
getNewVsReturning,
getEngagementMetrics,
getSearchTerms,
} from "./tools/analytics/index.js";
import { validateCredentials } from "./client.js";
// ===== スキーマ定義 =====
// 共通のperiod型
const PeriodSchema = z.enum([
"today",
"yesterday",
"7days",
"28days",
"30days",
"90days",
]);
const ShortPeriodSchema = z.enum([
"today",
"yesterday",
"7days",
"28days",
"30days",
]);
// ツール定義
const tools = [
// 基本ツール
{
name: "list_accounts",
description:
"GA4のアカウントとプロパティの一覧を取得します。どのプロパティIDを使うべきか確認する際に便利です。",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "get_property_details",
description:
"指定したGA4プロパティの詳細情報(名前、タイムゾーン、通貨など)を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: {
type: "string",
description: "GA4プロパティID(例: 123456789)",
},
},
required: ["propertyId"],
},
},
{
name: "run_report",
description:
"GA4の汎用レポートを実行します。任意のディメンションとメトリクスを指定して柔軟にデータを取得できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: {
type: "string",
description:
"GA4プロパティID(省略時は環境変数GA4_PROPERTY_IDを使用)",
},
startDate: {
type: "string",
description:
'開始日(YYYY-MM-DD形式、または "today", "yesterday", "7daysAgo" など)',
},
endDate: {
type: "string",
description:
'終了日(YYYY-MM-DD形式、または "today", "yesterday" など)',
},
dimensions: {
type: "array",
items: { type: "string" },
description:
'ディメンション名の配列(例: ["pagePath", "deviceCategory"])',
},
metrics: {
type: "array",
items: { type: "string" },
description:
'メトリクス名の配列(例: ["screenPageViews", "sessions"])',
},
dimensionFilter: {
type: "object",
description: "ディメンションフィルター(オプション)",
properties: {
fieldName: { type: "string" },
stringFilter: {
type: "object",
properties: {
matchType: {
type: "string",
enum: [
"EXACT",
"BEGINS_WITH",
"ENDS_WITH",
"CONTAINS",
"REGEXP",
],
},
value: { type: "string" },
caseSensitive: { type: "boolean" },
},
},
},
},
orderBy: {
type: "object",
description: "ソート設定(オプション)",
properties: {
metric: { type: "string" },
dimension: { type: "string" },
desc: { type: "boolean" },
},
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10、最大: 100000)",
},
},
required: ["startDate", "endDate", "dimensions", "metrics"],
},
},
{
name: "run_realtime_report",
description:
"リアルタイムレポートを実行し、現在のアクティブユーザー数などを取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: {
type: "string",
description: "GA4プロパティID(省略時は環境変数を使用)",
},
dimensions: {
type: "array",
items: { type: "string" },
description:
'ディメンション(デフォルト: ["country", "deviceCategory"])',
},
metrics: {
type: "array",
items: { type: "string" },
description: 'メトリクス(デフォルト: ["activeUsers"])',
},
},
required: [],
},
},
{
name: "get_metadata",
description:
"利用可能なディメンションとメトリクスの一覧を取得します。run_reportで使える項目を確認できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: {
type: "string",
description: "GA4プロパティID(省略時は環境変数を使用)",
},
},
required: [],
},
},
// 分析ツール
{
name: "get_traffic_summary",
description:
"指定期間のトラフィック概要(PV数、ユーザー数、セッション数、直帰率など)を取得します。ダッシュボード的な使い方に最適です。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["today", "yesterday", "7days", "28days", "30days", "90days"],
description: "集計期間",
},
},
required: ["period"],
},
},
{
name: "get_top_pages",
description:
"人気ページのランキングを取得します。PV数、滞在時間、直帰率などを確認できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["today", "yesterday", "7days", "28days", "30days"],
description: "集計期間",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10)",
},
pathFilter: {
type: "string",
description:
'パスのフィルター(例: "/blog" で /blog 配下のみを取得)',
},
},
required: ["period"],
},
},
{
name: "get_traffic_sources",
description:
"流入元(チャネル、ソース、メディアなど)の分析結果を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days", "90days"],
description: "集計期間",
},
groupBy: {
type: "string",
enum: ["channel", "source", "medium", "sourceMedium", "campaign"],
description: "グループ化の方法",
},
},
required: ["period", "groupBy"],
},
},
{
name: "get_device_breakdown",
description:
"デバイス別(PC/モバイル/タブレット)のアクセス内訳を取得します。OS別、ブラウザ別の情報も含まれます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
},
required: ["period"],
},
},
{
name: "get_geo_breakdown",
description: "地域別(国または市区町村)のアクセス分析結果を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
level: {
type: "string",
enum: ["country", "city"],
description: "地域レベル",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10)",
},
},
required: ["period", "level"],
},
},
{
name: "compare_periods",
description:
"2つの期間を比較します。前週比、前月比、前年同期比などの分析が可能です。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
comparisonType: {
type: "string",
enum: ["previousPeriod", "previousYear", "custom"],
description:
"比較タイプ(previousPeriod: 直前期間、previousYear: 前年同期、custom: カスタム)",
},
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description:
"基準期間(comparisonTypeがcustom以外の場合に使用)",
},
period1: {
type: "object",
properties: {
startDate: { type: "string" },
endDate: { type: "string" },
},
description: "比較期間1(comparisonTypeがcustomの場合に使用)",
},
period2: {
type: "object",
properties: {
startDate: { type: "string" },
endDate: { type: "string" },
},
description: "比較期間2(comparisonTypeがcustomの場合に使用)",
},
metrics: {
type: "array",
items: { type: "string" },
description:
'比較するメトリクス(デフォルト: ["screenPageViews", "totalUsers", "sessions"])',
},
},
required: ["comparisonType"],
},
},
{
name: "get_landing_pages",
description:
"ランディングページ(ユーザーが最初にアクセスしたページ)の分析結果を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10)",
},
},
required: ["period"],
},
},
{
name: "get_exit_pages",
description: "離脱ページ(ユーザーが最後に見たページ)の分析結果を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10)",
},
},
required: ["period"],
},
},
{
name: "get_user_journey",
description:
"特定ページの前後に閲覧されるページを分析します。ユーザーの回遊パターンの把握に便利です。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
pagePath: {
type: "string",
description: '分析対象のページパス(例: "/product/123")',
},
direction: {
type: "string",
enum: ["next", "previous"],
description: "next: 次に見たページ、previous: 前に見たページ",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 10)",
},
},
required: ["period", "pagePath", "direction"],
},
},
{
name: "get_conversion_funnel",
description:
"コンバージョンファネルの分析を行います。各ステップの通過率や離脱率を確認できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
steps: {
type: "array",
items: {
type: "object",
properties: {
name: {
type: "string",
description: 'ステップ名(例: "商品詳細")',
},
pagePath: {
type: "string",
description: 'マッチするページパス(例: "/product/")',
},
},
required: ["name", "pagePath"],
},
description: "ファネルのステップ定義(最低2つ必要)",
},
},
required: ["period", "steps"],
},
},
{
name: "get_hourly_traffic",
description:
"時間帯別のアクセス状況を分析します。投稿やキャンペーンのタイミング最適化に活用できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
},
required: ["period"],
},
},
{
name: "get_daily_trend",
description:
"日別のトレンドデータを取得します。グラフ作成やトレンド分析に使用できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
startDate: {
type: "string",
description: '開始日(YYYY-MM-DD形式、または "30daysAgo" など)',
},
endDate: {
type: "string",
description: '終了日(YYYY-MM-DD形式、または "today" など)',
},
metrics: {
type: "array",
items: { type: "string" },
description:
'取得するメトリクス(デフォルト: ["screenPageViews", "activeUsers"])',
},
},
required: ["startDate", "endDate"],
},
},
{
name: "get_new_vs_returning",
description:
"新規ユーザーとリピーターの比較分析を行います。それぞれのセッション数や滞在時間を確認できます。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
},
required: ["period"],
},
},
{
name: "get_engagement_metrics",
description:
"エンゲージメント関連の詳細指標(エンゲージメント率、平均エンゲージメント時間など)を取得します。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
},
required: ["period"],
},
},
{
name: "get_search_terms",
description:
"サイト内検索キーワードを分析します。GA4でサイト内検索が設定されている場合のみ有効です。",
inputSchema: {
type: "object" as const,
properties: {
propertyId: { type: "string", description: "GA4プロパティID" },
period: {
type: "string",
enum: ["7days", "28days", "30days"],
description: "集計期間",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 20)",
},
},
required: ["period"],
},
},
];
// ===== ツールハンドラー =====
async function handleToolCall(
name: string,
args: Record<string, unknown>
): Promise<unknown> {
switch (name) {
// 基本ツール
case "list_accounts":
return await listAccounts();
case "get_property_details":
return await getPropertyDetails({
propertyId: args.propertyId as string,
});
case "run_report":
return await runReport({
propertyId: args.propertyId as string | undefined,
startDate: args.startDate as string,
endDate: args.endDate as string,
dimensions: args.dimensions as string[],
metrics: args.metrics as string[],
dimensionFilter: args.dimensionFilter as
| {
fieldName: string;
stringFilter: {
matchType:
| "EXACT"
| "BEGINS_WITH"
| "CONTAINS"
| "ENDS_WITH"
| "REGEXP";
value: string;
caseSensitive?: boolean;
};
}
| undefined,
orderBy: args.orderBy as
| { metric?: string; dimension?: string; desc?: boolean }
| undefined,
limit: args.limit as number | undefined,
});
case "run_realtime_report":
return await runRealtimeReport({
propertyId: args.propertyId as string | undefined,
dimensions: args.dimensions as string[] | undefined,
metrics: args.metrics as string[] | undefined,
});
case "get_metadata":
return await getMetadata({
propertyId: args.propertyId as string | undefined,
});
// 分析ツール
case "get_traffic_summary":
return await getTrafficSummary({
propertyId: args.propertyId as string | undefined,
period: args.period as
| "today"
| "yesterday"
| "7days"
| "28days"
| "30days"
| "90days",
});
case "get_top_pages":
return await getTopPages({
propertyId: args.propertyId as string | undefined,
period: args.period as
| "today"
| "yesterday"
| "7days"
| "28days"
| "30days",
limit: args.limit as number | undefined,
pathFilter: args.pathFilter as string | undefined,
});
case "get_traffic_sources":
return await getTrafficSources({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days" | "90days",
groupBy: args.groupBy as
| "channel"
| "source"
| "medium"
| "sourceMedium"
| "campaign",
});
case "get_device_breakdown":
return await getDeviceBreakdown({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
});
case "get_geo_breakdown":
return await getGeoBreakdown({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
level: args.level as "country" | "city",
limit: args.limit as number | undefined,
});
case "compare_periods":
return await comparePeriods({
propertyId: args.propertyId as string | undefined,
comparisonType: args.comparisonType as
| "previousPeriod"
| "previousYear"
| "custom",
period: args.period as "7days" | "28days" | "30days" | undefined,
period1: args.period1 as
| { startDate: string; endDate: string }
| undefined,
period2: args.period2 as
| { startDate: string; endDate: string }
| undefined,
metrics: args.metrics as string[] | undefined,
});
case "get_landing_pages":
return await getLandingPages({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
limit: args.limit as number | undefined,
});
case "get_exit_pages":
return await getExitPages({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
limit: args.limit as number | undefined,
});
case "get_user_journey":
return await getUserJourney({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
pagePath: args.pagePath as string,
direction: args.direction as "next" | "previous",
limit: args.limit as number | undefined,
});
case "get_conversion_funnel":
return await getConversionFunnel({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
steps: args.steps as Array<{ name: string; pagePath: string }>,
});
case "get_hourly_traffic":
return await getHourlyTraffic({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
});
case "get_daily_trend":
return await getDailyTrend({
propertyId: args.propertyId as string | undefined,
startDate: args.startDate as string,
endDate: args.endDate as string,
metrics: args.metrics as string[] | undefined,
});
case "get_new_vs_returning":
return await getNewVsReturning({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
});
case "get_engagement_metrics":
return await getEngagementMetrics({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
});
case "get_search_terms":
return await getSearchTerms({
propertyId: args.propertyId as string | undefined,
period: args.period as "7days" | "28days" | "30days",
limit: args.limit as number | undefined,
});
default:
throw new Error(`Unknown tool: ${name}`);
}
}
// ===== サーバー作成 =====
export function createServer(): Server {
const server = new Server(
{
name: "ga4-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// ツール一覧の返却
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// ツール実行
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const result = await handleToolCall(name, args || {});
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `エラーが発生しました: ${errorMessage}`,
},
],
isError: true,
};
}
});
return server;
}
// ===== サーバー起動 =====
export async function startServer(): Promise<void> {
// 認証情報の確認
validateCredentials();
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("GA4 MCP Server started");
}