認証を通したら、次はMCPサーバーの実装です。ここは拍子抜けするほど簡単で、google_ads_server.py という1ファイルで完結します。
この検証では、Python + FastMCPでGoogle広告のMCPサーバーを構築し、AIに「過去30日のキャンペーンパフォーマンスを見せて」と聞くだけでデータが返る状態を作りました。
本記事は3回シリーズの第2回です。第1回では認証設定、第3回ではリモート化を扱います。
この検証の概要
検証時期 | 2026年4月5日 |
|---|---|
やりたかったこと | 認証基盤の上にMCPサーバーを構築し、Claude DesktopやCursorから自然言語でGoogle広告データを操作する |
技術スタック | Python 3.11+ / mcp(FastMCP)>= 1.3.0 / google-auth / requests / pydantic |
実装規模 | 1ファイル・14ツール |
接続先 | Claude Desktop / Cursor(stdioトランスポート) |
結論:docstringの品質がツール選択の精度を決める
FastMCPでは @mcp.tool() デコレータを付けるだけで、Python関数がMCPツールとして公開されます。
@mcp.tool()
async def list_accounts() -> str:
"""アクセス可能なすべてのGoogle Adsアカウントを一覧表示します。"""
creds = get_credentials()
headers = get_headers(creds)
url = f"https://googleads.googleapis.com/{API_VERSION}/customers:listAccessibleCustomers"
response = requests.get(url, headers=headers)
# ... 結果をフォーマットして返すここで最も重要なのはdocstringです。AIはこの説明文を読んで「このツールは何ができるのか」を判断します。
つまりdocstringの品質が、そのままツール選択の精度に直結します。実装が正しくても説明が曖昧だと、意図したツールが呼ばれません。
パラメータについても同様で、Pydanticの Field に説明を付けます。
@mcp.tool()
async def execute_gaql_query(
customer_id: str = Field(description="Google Ads顧客ID(10桁、ハイフンなし)"),
query: str = Field(description="GAQLクエリ文字列")
) -> str:
"""カスタムGAQL(Google Ads Query Language)クエリを実行します。"""AIはこのdescriptionを見て、ユーザーの入力からパラメータをマッピングします。
完成イメージ
やりたいこと | AIへの指示例 | 呼ばれるツール |
|---|---|---|
アカウント確認 | 「アカウント一覧を表示して」 |
|
パフォーマンス分析 | 「過去30日のキャンペーンパフォーマンスを見せて」 |
|
カスタムクエリ | GAQLを直接指定 |
|
広告クリエイティブ確認 | 「広告の見出しと説明文を一覧で見たい」 |
|
トークン確認 | 「認証トークンの有効性を確認して」 |
|
GAQLクエリもAIが組み立てるため、クエリ構文を覚える必要はありません。
提供する14のツール
カテゴリ | ツール |
|---|---|
アカウント |
|
クエリ・レポート |
|
アセット |
|
認証 |
|
実際に使うと、ほとんどのケースは list_accounts → get_campaign_performance のワークフローか、run_gaql でカスタムクエリを投げるパターンに集約されます。
ツールを14個用意しても、日常的に使われるのは数個です。それでも網羅しておくと、AIが状況に応じて選べます。
認証は2方式を切り替え可能に
環境変数 GOOGLE_ADS_AUTH_TYPE で切り替えます。
方式 | 用途 | 特徴 |
|---|---|---|
OAuth 2.0(デフォルト) | 個人ユーザー向け | トークンファイルから読み込み、期限切れなら自動リフレッシュ、無ければブラウザ認証 |
サービスアカウント | 自動化システム向け | JSONキーから認証。ドメイン委任にも対応 |
def get_oauth_credentials():
"""OAuthユーザー認証情報を取得・更新します。"""
creds = None
token_path = GOOGLE_ADS_CREDENTIALS_PATH
if os.path.exists(token_path):
creds = Credentials.from_authorized_user_info(creds_data, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request()) # 自動リフレッシュ
else:
flow = InstalledAppFlow.from_client_config(client_config, SCOPES)
creds = flow.run_local_server(port=8080)
return creds地味にハマる:顧客IDのフォーマット
実運用で効いてくる細部です。
ユーザーはハイフン付き・引用符付きなど、さまざまな形式で顧客IDを入力してきます。そこで正規化関数を用意しています。
def format_customer_id(customer_id: str) -> str:
customer_id = str(customer_id)
customer_id = customer_id.replace('\\"', '').replace('"', '')
customer_id = ''.join(char for char in customer_id if char.isdigit())
return customer_id.zfill(10)123-456-7890 でも "1234567890" でも、すべて 1234567890 に正規化されます。
AIが渡してくる値の揺れを吸収する関数です。人間相手なら「ハイフンなしで入力してください」と書けますが、AI経由では入力形式を制御できません。受け側で吸収する必要があります。
GAQLで知っておくべき3点
SELECT campaign.name, metrics.clicks, metrics.impressions, metrics.cost_micros
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
ORDER BY metrics.cost_micros DESC
LIMIT 50cost_microsは100万分の1単位——1,000,000= 1通貨単位- 日付フィルタは定数が使える——
DURING LAST_7_DAYS、DURING LAST_30_DAYSなど - 出力形式を切り替えられる——
run_gaqlなら table / json / csv
SQLとの違いとして、JOINが不要(リソース間の関係はAPI側で解決される)、* によるワイルドカード選択ができない点があります。
ただし細かい構文を覚える必要はほとんどありません。「過去7日間でクリック数が多い順にキャンペーンを表示して」と伝えれば、AIが適切なクエリを生成します。
接続設定
stdioトランスポートで起動するため、クライアント側がプロセスを直接起動して通信します。
if __name__ == "__main__":
mcp.run(transport="stdio")Claude Desktopの場合は設定ファイルに追記します。
{
"mcpServers": {
"google-ads": {
"command": "python",
"args": ["/path/to/mcp-google-ads/google_ads_server.py"],
"env": {
"GOOGLE_ADS_AUTH_TYPE": "oauth",
"GOOGLE_ADS_CREDENTIALS_PATH": "/path/to/google_ads_token.json",
"GOOGLE_ADS_DEVELOPER_TOKEN": "your-developer-token",
"GOOGLE_ADS_LOGIN_CUSTOMER_ID": "your-mcc-id"
}
}
}
}Cursorの場合は Settings → MCP から「Add new MCP server」で、Typeを command、Commandに実行パスを指定します。
テストMCCでの動作確認手順
Developer TokenがPending状態でも、以下の順で確認できます。
list_accounts— テストMCC配下のアカウントIDが表示されればOKget_account_currency—JPYやUSDが返れば正常get_campaign_performance— テストMCCにはキャンペーンがないので空データが返るが、エラーが出なければAPI通信は成功
本番承認後は、GOOGLE_ADS_LOGIN_CUSTOMER_ID を本番MCCのIDに切り替えるだけで移行完了です。
ローカルMCPサーバーの限界
1ファイルで実用的なものが作れる一方、構造的な課題があります。
- PCが起動していないと使えない——ローカルプロセスのため、外出先からは使えない
- チーム共有ができない——各メンバーが個別にセットアップする必要がある
- 認証情報の管理——各端末に
.envやトークンファイルを置く必要がある
個人利用なら十分ですが、組織で使うとなると別の構成が必要になります。次回はこれをリモート化します。
よくある質問
MCPサーバーの実装は難しいですか?
1ファイルで完結します。FastMCPの @mcp.tool() デコレータを付けるだけで、Python関数がMCPツールとして公開されます。
意図したツールが呼ばれません
docstringを見直してください。AIはdocstringを読んでツールを選択するため、説明の品質がそのまま選択精度になります。パラメータもPydanticの Field にdescriptionを付けます。
GAQLの構文を覚える必要がありますか?
ほとんど不要です。「過去7日間でクリック数が多い順に」と伝えれば、AIが適切なクエリを生成します。ただし cost_micros が100万分の1単位である点は知っておくと解釈を誤りません。
顧客IDでエラーになります
入力形式の揺れを吸収する正規化関数を用意してください。AI経由では入力形式を制御できないため、ハイフンや引用符を除去して10桁ゼロ埋めする処理を受け側に置きます。
まとめ
- MCPサーバーは1ファイル・14ツールで実用的なものが作れる
- docstringの品質がツール選択の精度に直結する。実装より説明が効く
- 認証はOAuthとサービスアカウントの2方式を環境変数で切り替え
- AIが渡す値の揺れは受け側で吸収する(顧客IDの正規化)
- GAQLはJOIN不要・ワイルドカード不可。
cost_microsは100万分の1単位 - 実際に使われるのは14ツールのうち数個に集約される
- ローカル構成の限界はPC依存・チーム共有不可・認証情報の分散
AIに外部APIを触らせる仕組みでは、機能の多さより「AIが正しく選べるか」が実用性を決めます。説明文の設計が実装と同じくらい重要になります。
株式会社AI棒では、こうしたAI活用の検証を日々行い、企業の広告運用・マーケティング業務へのAI導入を支援しています。ご相談はお問い合わせフォームから、継続的な伴走支援についてはAI参謀をご覧ください。
