はじめに
前回、「日本の公開データを改ざん検知つきでAIに渡すMCP」を作った話を書いたんですけど、その続きです。
コードはほぼAI(Claude)に書いてもらってるんですが、「動くコードができる」と「本番でちゃんと動く」の間には、想像の3倍くらい谷があったんですよね…。ローカルでは全部緑だったのに、Cloudflare Workersに上げた瞬間に色々ぶっ壊れました。
同じように「AIと一緒にMCPサーバー作って本番に上げよう」としてる人がいたら、たぶん同じ穴に落ちると思うので、踏んだ罠を正直に置いておきます。
環境は Cloudflare Workers + TypeScript + D1(SQLite) です。
罠1: Cloudflare Cron、曜日指定が通らない
「このデータは週1回でいいや」と思って、cronをこう書いたんです。
crons = ["30 20 * * 0"] # 毎週日曜
デプロイしたらこれ。
✘ [ERROR] invalid cron string: 30 20 * * 0 [code: 10100]
30 20 * * 0 は普通のcronとしては正しい(日曜の20:30)はずなのに、Cloudflareに弾かれる。調べると、Cloudflare Cron Triggers は曜日指定(5番目のフィールド)をうまく受け付けないみたいで。
結局、素直に日次にしました。
crons = ["30 20 * * *"] # 毎日
週1回のはずを毎日叩くのはちょっと無駄に見えるけど、「変化がなければ何も起きない」作りにしてあるので実害なし。曜日cronを使いたくなったら日次で代用、が一番ハマらないです。
罠2: 「1行ずつINSERT」で本番が死ぬ(サブリクエスト上限)
これが一番ハマりました。
数千件のデータを取り込むとき、最初は素直に「1行ずつ INSERT / UPDATE」してたんです。ローカルのテストでは件数が少ないので普通に通る。で、本番で全国分を流したら、これ。
Too many API requests by single Worker invocation.
Cloudflare Workersは、1回の実行で投げられる外部リクエスト(DBアクセス含む)に上限があるんですよね(有料プランで1000)。1行ごとにDBを叩いてると、数千行で簡単に超えます。
直し方は、
- 既存データを 1回のSELECTでまとめて取得
- 差分は メモリ上で計算
- 書き込みは 100件ずつ束ねて(batch) 投げる
これで、数千行でも実行あたりのリクエストが数十回に収まって、上限に余裕で収まりました。「ループの中で毎回DBを叩いてないか?」は、Workersだと最初に疑うポイントです。
罠3: 役所のデータ、gzipで返ってくる
国交省のAPIを叩いたら、レスポンスがgzip圧縮で返ってきました。
ありがたいことに、サーバーが Content-Encoding: gzip をちゃんと付けてくれていれば、Workersの fetch() が勝手に解凍してくれるので .json() でそのまま読めます。
ただ、付いてないケースもあるらしいので、保険で DecompressionStream('gzip') のフォールバックも入れておきました。「ローカルのcurl(--compressed)では読めたのに本番で文字化け」みたいなときは、ここを疑うといいです。
罠4: 初回にDiscordへ数万件の通知を爆撃した
このサービス、変化があったらDiscordに通知を飛ばすようにしてるんですけど。
台帳を作った一番最初って、過去ぶんが全部「新規」として入ってくるんですよね。それを全部「新規です!」って通知に流したら、Discordに3万件以上のメッセージが飛んでいきました…。
対策は、「初回の取り込み(まだ1件もデータが無い状態)のときは通知しない」というガードを入れること。差分アラート系を作るときは、初回バックフィルだけは黙らせるのを最初から入れておかないと、自分のWebhookが死にます。
罠5: データのフィールド名が、年版で意味が変わる(schema drift)
地価公示のデータ(GeoJSON)を取り込んだときの話。
このデータ、フィールド名が L01_001, L01_002… みたいなただの番号で来るんです。で、怖いのが、同じ番号でも年版(仕様バージョン)によって指してる中身が違うこと。たとえば「公示価格」が、ある年は L01_006、別の年は L01_008 だったり。
これに気づかず番号でハードコードしてると、ある日こっそり別の列を「価格」として読み込んで、それっぽい嘘データを記録し続けるという最悪の事故が起きます。改ざん検知つきの台帳でこれをやったら本末転倒…。
なので、取り込み時に「値の妥当性チェック(sanity check)」を入れました。
- 年が4桁で、今年の近くか
- 価格がint型で、現実的な範囲か
- 座標が日本の範囲内か
これが1つでも崩れたら、そのデータは取り込まずに中止してアラートを出す。「黙って続行」じゃなくて「おかしかったら止まる」にしておくのが、データの信頼性を売りにするなら必須でした。
おまけ: MCPエンドポイントの形
ちなみに、出来上がったMCPサーバーは Streamable HTTP(JSON-RPC) で、Claude Desktopの設定にURLを足すだけで使えます。
{
"mcpServers": {
"japan-public-ledgers": {
"url": "https://mcp.mcp-revenue-empire.com/mcp"
}
}
}
ツール一覧はcurlでも見れます。
curl https://mcp.mcp-revenue-empire.com/mcp/tools
まとめ
- AIにコードを書いてもらっても、本番(Cloudflare Workers)の制約とデータの癖には自分でぶつかる
- 特にハマったのは「曜日cron不可」「サブリクエスト上限(1行ずつDBが死ぬ)」「初回通知の爆撃」「フィールドのschema drift」
- でも、こういう「動かしてみて初めて分かる」部分が、いじってて一番面白いところでもあります
同じ穴に落ちた人の検索に引っかかれば本望です。「ここもっとこうした方がいいよ」があれば、コメントで教えてください〜!