Yahoo広告のMCPサーバーは自作できる?実装でつまずいた6つの落とし穴
「Yahoo広告のデータを Claude や Cursor から扱いたい」「Yahoo広告の MCP サーバーを自前で作れないか」と考えて、この記事にたどり着いた方もいるかと思います。
結論から言うと、作れなくはありません。ただし、想像以上に細かい落とし穴が多いです。「動いているつもりでエラーを見落とす」「クリエイティブが想定どおり取れない」「MCC構成で謎の権限エラーが出る」「並列化したらDBが落ちる」など、コードを実際に動かして初めて気づくポイントが次々に出てきます。しかもそれぞれが、ドキュメントを読んだだけでは予想しにくい内容です。
本記事は、Yahoo! Ads APIのデータ取得機能を MCP サーバーとして実装した実体験をもとに、つまずいた6点を共有するものです。これから自作を検討している方が「どれくらい大変か」を見積もる材料にしていただければ幸いです。
対象読者は、Yahoo! Ads API を Python から呼んだ経験があるか、これから Yahoo広告 × MCP の仕組みを作ろうとしているエンジニア・技術寄りの運用者の方です。
なお MCP(Model Context Protocol)は、AI に外部のデータやツールを安全につなぐための共通規格です。Yahoo広告のデータ取得を MCP 化すれば、Claude や Cursor から自然言語で Yahoo広告のデータを扱えるようになります。英語では「Yahoo Ads MCP」と呼ばれることもあり、本記事はその自作を検討している方に向けています。
目次
1. errors キーが存在しても、null なら成功
Yahoo! Ads APIの多くのエンドポイントは、正常レスポンスでもerrorsキーを含みます。
{
"errors": null,
"rval": { "values": [...] }
}
最初に"errors" in responseの存在チェックで弾く実装を書くと、正常なレスポンスをすべてエラー扱いしてしまいます。正しい判定は「errorsの値が truthy かどうか」です。
errors = data.get("errors") if isinstance(data, dict) else None
if errors: # None や空リストは通過させる
raise SomeAPIError(...)
この挙動はYahoo! Ads APIのレスポンス仕様によるもので、他の広告APIとは異なる構造をしています。初見で混乱しやすいポイントのひとつです。
2. キーワードレポートは reportType="KEYWORD" では取れない
キーワード別の実績を取りたい場合、reportType="KEYWORD" を指定したくなります。しかしこのレポートタイプは未サポートです(v18で未サポートを確認し、現行の実装である v19 でも SEARCH_QUERY で代替しています)。
代替手段は reportType="SEARCH_QUERY" レポートで KEYWORD フィールドを含めることです。検索語句レポートにはヒットしたキーワードのフィールドが含まれており、これを使ってキーワード軸の集計を実現できます。
fields = [
"CAMPAIGN_NAME", "ADGROUP_NAME",
"KEYWORD",
"IMPS", "CLICKS", "COST", ...
]
# report_type="SEARCH_QUERY" を指定して実行
ドキュメントを見るとフィールド名だけが存在するので「使えるはず」と思いがちですが、実際には reportType の組み合わせ制約があり、指定できるフィールドはレポートタイプによって変わります。フィールドとレポートタイプの対応表を事前に確認しておくと手戻りが少なくなります。
3. クリエイティブ取得は MediaService より AdGroupAdService が確実
「ディスプレイ広告の素材(画像・動画)一覧を取りたい」となると、まず MediaService/get を試したくなります。しかしMCCの子アカウントを指定した場合、values が空配列で返ることがあります。エラーでもなく、ただ空で返ってきます。
実用的な回避策は、AdGroupAdService/get で配信中の広告を取得し、そこに含まれる mediaId フィールドから素材を逆引きする方式です。
# AdGroupAdService から ad 一覧を取得
ads = await client.get_ads(api_type="display", ...)
# ad の中から mediaId を抽出・重複排除してクリエイティブ一覧を構築
seen = {}
for ad in ads:
media_id = ad.get("mediaId") or (ad.get("ad") or {}).get("mediaId")
if media_id and media_id not in seen:
seen[media_id] = { ... }
この方式の利点は「実際に配信に使われている素材」だけが取れることです。未使用の素材は混入しません。ただし動画の場合、Yahoo! Ads APIは動画URLを直接公開しない設計になっており、ファイル本体を取得するには VideoService/download エンドポイントを経由する必要があります。
4. MCC構成では x-z-base-account-id ヘッダーが必要
代理店(MCC)配下の子アカウントへのリクエストでは、MCCのアカウントIDを x-z-base-account-id ヘッダーに付与しないとアカウント不一致エラーになります。
また、MCCが「search系MCC」と「display系MCC」で別々に存在するケースがあります。その場合、searchアカウントへのリクエストにdisplay用のMCC IDを渡すとアカウント不一致になります。アカウントタイプ(search/display)に応じて、対応するMCC IDを使い分ける設計が必要です。
def _mcc_account_id(self, api_type: str) -> str:
if api_type == "search":
return self.creds.search_mcc_account_id
else:
return self.creds.display_mcc_account_id
初期実装では単一のMCC IDを全リクエストに使い回していたため、displayアカウントへのリクエストで断続的にエラーが発生しました。アカウント構成を確認する段階でsearch/displayの対応関係を記録しておくと、後の実装がスムーズになります。
5. OAuthトークンの非同期取得とキャッシュ設計
Yahoo! Ads APIのアクセストークンはOAuthのリフレッシュトークンから都度取得する方式で、有効期限は30分程度です。複数のAPIリクエストが並走する環境で毎回トークンを取得し直すと、不要な認証コールが大量に発生してしまいます。
インメモリキャッシュで有効期限内のトークンを使い回す設計が基本ですが、キャッシュの有効期限をAPIの実際の有効期限(30分)に合わせると、期限ぎりぎりにキャッシュを使った場合にAPIコール中にトークンが失効するリスクがあります。安全マージンとして、キャッシュTTLは実際の有効期限より数分短く設定するのが定石です。
_TOKEN_TTL = 25 * 60 # 25分(Yahooアクセストークンの有効期限30分より短く)
async def _get_access_token(self) -> str:
now = time.monotonic()
cached = _token_cache.get(self.creds.account_id)
if cached and now < cached[1]:
return cached[0]
# キャッシュ切れ or 未取得なら再発行
...
_token_cache[account_id] = (token, now + _TOKEN_TTL)
return token
さらに、401が返ってきた場合はキャッシュを無効化して即座に再取得するリトライを1回だけ行うと、セッション中にトークンが失効した場合も自動回復できます。
if resp.status_code == 401:
_token_cache.pop(self.creds.account_id, None)
headers = await self._headers(api_type)
resp = await client.post(url, json=payload, headers=headers)
6. 非同期処理:並列化が「別の問題」を呼ぶ
Yahoo! Ads APIクライアントは httpx の AsyncClient で実装し、複数アカウント・複数媒体のデータ取得を asyncio.gather でまとめて並列実行できます。レスポンスは速くなりますが、並列化そのものが想定外の副作用を生むことがあります。
並列リクエストが下流のリソースを食いつぶす
「アカウント数 × 媒体数」の取得を一斉に gather で走らせたところ、2つの問題が出ました。ひとつは各媒体APIのレート制限に当たりやすくなること。もうひとつは、各リクエストが必要とするトークン取得のDB参照が同時多発し、PostgreSQLのコネクションプールを枯渇させたことです。
並列度は「速さ」だけでなく「下流への同時接続数」も増やします。対処として、トークンをまとめて1回で引くバッチ化と、コネクションプール上限の調整を行いました。セマフォで同時実行数に上限を設ける方法も有効です。
同期クライアントを async の中でそのまま回さない
媒体ごとにSDKやライブラリの事情が異なり、非同期対応のものと同期のものが混在することがあります。同期的なHTTP呼び出しを async 関数の中でそのまま呼ぶと、その処理中はイベントループをブロックし、gather でまとめても実質的に直列実行になってしまいます。同期処理は asyncio.to_thread などでスレッドに逃がすと、本来の並列性が得られます。
待機は time.sleep ではなく await asyncio.sleep
レポート生成はジョブ方式(作成→ポーリング→ダウンロード)で待ち時間が発生します。ここで time.sleep を使うとイベントループ全体が停止し、並列で走っているはずの他の取得まで止まります。await asyncio.sleep(5) で待てば、待機中も他のコルーチンが進みます。
トークンキャッシュは同時アクセスに注意
複数のコルーチンが同時に起動し、ちょうどトークンが失効していると、全員が同時にトークン再発行へ走ります(thundering herd)。アカウント単位でロックを取り「最初の1本だけ発行し、残りは待って使い回す」設計にすると、無駄な認証コールを防げます。
MCP化ならではの設計上の注意
上記のAPIレベルのつまずきとは別に、MCPサーバーとして公開する際に意識すべき設計ポイントが2点あります。
読み取り専用の明示的な保証
MCPツールは自然言語から呼ばれるため、意図しない操作が起きないよう設計上の制約が重要になります。Yahoo! Ads APIは書き込み系エンドポイントも持ちますが、MCPサーバー側でGETに相当するデータ取得のみを実装し、変更・削除系のツールは一切用意しないことで「読み取り専用」を構造的に保証できます。
レスポンスからのトークン除去
APIのレスポンスには認証情報に相当するキー(access_token、token、secret 等)が含まれる可能性があります。MCPのレスポンスはLLMに渡るため、意図せずトークンが会話ログに残るリスクがあります。レスポンスをLLMに返す前に、トークン関連のキーを再帰的に除去する処理を挟むのが安全です。
# dict/list を再帰走査してトークン関連キーを除去する関数を通してから返す
return strip_tokens(result)
レポートのタイムアウト設計
Yahoo! Ads APIのレポート生成はジョブ方式で、作成→ポーリング→ダウンロードの3フェーズになります。MCPサーバー経由で呼ばれる場合、チャットセッション全体のタイムアウト予算の中で複数ツールが呼ばれることがあります。単一ツールが無制限に待つと、セッション全体をブロックしてしまいます。
ポーリングの最大回数(例: 18回×5秒=90秒)を設定し、超過した場合は「期間を短くして再試行してほしい」旨のエラーを返すことで、LLMが別のアプローチでリカバリできるようにする設計が有効です。
まとめ
Yahoo! Ads APIをMCPサーバーとして実装した際につまずいたポイントをまとめると以下のとおりです。
errors: nullは正常レスポンス。存在チェックではなく値のtruthyチェックをしてください。reportType="KEYWORD"は使えません。SEARCH_QUERYレポートのKEYWORDフィールドで代替します。- MediaServiceの子アカウントへのリクエストは空配列が返ることがあります。AdGroupAdServiceからmediaIdを逆引きしてください。
- MCC構成ではsearch/display別にMCC IDを使い分けてください。
x-z-base-account-idヘッダーは必須です。 - トークンキャッシュのTTLは実際の有効期限より短く設定し、401時はキャッシュを破棄して再取得してください。
- 非同期で並列取得すると下流(レート制限・DBコネクション)を圧迫します。同期処理はスレッドに逃がし、待機は
asyncio.sleepを使ってください。
どれも一度ハマれば対処できますが、問題は「これらを全部、自前で正しく実装し、APIのバージョン更新や仕様変更に追従しながら保守し続ける」点にあります。Yahoo広告だけでこの手間で、媒体が増えればさらに増えます。APIの細かい挙動は実際に動かさないと分からないことが多く、公式ドキュメントと実動作の差分を記録しながら進める覚悟が必要です。
よくある質問
Q. Yahoo広告のMCPは自作と既製、どちらがいい? A. 一度きりの検証なら自作でもよいでしょう。ただし運用で使い続けるなら、APIのバージョン更新や仕様変更への追従・保守コストが重くのしかかります。継続利用するなら、メンテ込みで提供される既製のMCPサーバーを使う方が現実的です。
Q. Yahoo広告以外の媒体も同じ MCP で扱える? A. 設計しだいです。媒体ごとにAPIの癖が違うため、横断対応には各媒体分の実装が要ります。Harubaseは単一エンドポイントでMeta・Yahoo広告・TikTokを横断して扱えます(Googleは順次対応予定)。
Q. MCP 接続でYahooのデータをAIに渡して安全? A. 読み取り専用(データ取得のみ)に限定し、レスポンスからトークン等の認証情報を除去する設計にすれば、書き込み事故や認証情報の漏えいリスクを抑えられます。
自作の前に、まず試してみてください
ここまで読んでいただいて分かるとおり、Yahoo! Ads APIのMCP実装は「動かすだけ」なら数日ですが、仕様変更への追従・MCC対応・トークン管理・非同期の安定化まで含めると、継続的な保守コストがかかり続けます。媒体が1つ増えるたびに同じ手間が積み重なります。自作で得られるものと、そこに投じる時間が釣り合うかどうか、一度立ち止まって考えてみてください。
MCPサーバー「Harubase」では、ここで挙げたYahoo! Ads APIのつまずきはすでに解決済みです。接続するだけで、ClaudeやCursorから自然言語でYahoo広告のデータを取得できます(Meta・TikTokにも対応。Googleは順次対応予定)。APIの追従も保守もこちらで行うため、運用者の方は分析と改善に集中できます。
無料プランから始められます。まずは Harubase を繋いで、「先月のYahoo広告のCPAを教えて」と Claude に聞くところから試してみてください。
実機での接続手順は Yahoo広告×Claudeの実機ガイド をご覧ください。




ディスカッション
コメント一覧
まだ、コメントがありません