Google純正の Google Maps MCP(Grounding Lite)を試す

December 17, 2025

Google Maps PlatformGrounding Lite 構想 の一部として、MCP エンドポイント が公開されました。

さっそく mapstools.googleapis.com/mcp を試してみます。

ドキュメント

Grounding Lite

https://developers.google.com/maps/ai/grounding-lite?utm_source=chatgpt.com&hl=ja#policies_and_terms_of_service

MCP Reference: mapstools.googleapis.com

https://developers.google.com/maps/ai/grounding-lite/reference/mcp?utm_source=chatgpt.com&hl=ja

何ができる?

  1. search_places(場所検索)
  2. lookup_weather(天気)
  3. compute_routes(経路)

現時点で、mapstools.googleapis.com が提供する公式 MCP では、上記の3つのみが利用可能です。

それ以外の情報(クチコミ情報)などは、別途 API を呼び出す必要があります。

とはいえ、Google ADK では、Agent 側に独自の Tool を追加できるため、
公式 MCP で不足する情報(クチコミ等)は、
Places API(Place Details)を呼び出す Tool を別途追加して補完できます。

試してみる

Google ADK で動かす

今回は、公式ドキュメントで例示されている Google ADK を使って試しました。

https://google.github.io/adk-docs/tools-custom/mcp-tools

各種サービスを有効化する必要がある

AI サービス

今回の MCP を利用するには Google Maps API Key が必要です。
また、Agent として Gemini を使う場合は、Gemini API Key または Vertex AI の認証情報が別途必要になります。

API Services を有効化する

gcloud beta services enable mapstools.googleapis.com --project=PROJECT_ID
gcloud beta services mcp enable mapstools.googleapis.com --project=PROJECT_ID
# 有効化できたか確認
gcloud beta services mcp list --enabled --project=_PROJECT_ID_

※ローカルの gcloud では未対応な場合があります。mapstools.googleapis.com の有効化に関しては、Cloud Shell から実行します。

コード

公式 MCPに、Place Details を呼び出す Tool を追加しています。

※ 本コードは検証・学習用途のサンプルです。
※ Google Places API の利用には課金設定が必要になる場合があります。
※ reviews(クチコミ)は API 仕様上、常に多数取得できるわけではありません(最大5件程度)。

import os
import json
import urllib.parse
import urllib.request
import urllib.error
from dotenv import load_dotenv
from typing import Any, Dict, List, Optional

from google.adk.agents.llm_agent import Agent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams

load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env"))

MAPS_MCP_URL = "https://mapstools.googleapis.com/mcp"

PLACES_API_BASE_URL = "https://places.googleapis.com/v1"


def _get_places_api_key() -> str:
    return os.getenv("GOOGLE_PLACES_API_KEY") or os.getenv("GOOGLE_MAPS_API_KEY", "")


def _http_get_json(url: str, headers: Dict[str, str], timeout_sec: int = 30) -> Dict[str, Any]:
    req = urllib.request.Request(url, headers=headers, method="GET")
    try:
        with urllib.request.urlopen(req, timeout=timeout_sec) as resp:
            raw = resp.read().decode("utf-8")
    except urllib.error.HTTPError as e:
        body = ""
        try:
            body = e.read().decode("utf-8")
        except Exception:
            body = ""
        raise RuntimeError(f"HTTP {e.code}: {body or e.reason}") from e
    except urllib.error.URLError as e:
        raise RuntimeError(f"ネットワークエラー: {e.reason}") from e

    try:
        return json.loads(raw) if raw else {}
    except json.JSONDecodeError as e:
        raise RuntimeError("JSON のパースに失敗しました") from e


def get_place_details(
    place_id: str,
    language_code: str = "ja",
    region_code: str = "",
    max_reviews: int = 5,
    field_mask: str = "",
) -> Dict[str, Any]:
    """Google Places API の Place Details を取得します(reviews 等)。

    Args:
        place_id: Google Maps/Places の place_id
        language_code: 例: "ja", "en"
        region_code: 例: "JP"(任意)
        max_reviews: 返す reviews の最大件数(0-20 推奨)
        field_mask: 取得フィールド(未指定なら reviews など最低限)
    """
    api_key = _get_places_api_key()
    if not api_key:
        return {
            "status": "error",
            "error": "API Key が未設定です。GOOGLE_PLACES_API_KEY または GOOGLE_MAPS_API_KEY を設定してください。",
        }

    if not place_id:
        return {"status": "error", "error": "place_id が空です。"}

    safe_max_reviews = max(0, min(int(max_reviews), 20))
    effective_field_mask = (
        field_mask.strip()
        or "displayName,formattedAddress,rating,userRatingCount,reviews,websiteUri,internationalPhoneNumber"
    )

    query = {"languageCode": language_code}
    if region_code.strip():
        query["regionCode"] = region_code.strip()
    url = f"{PLACES_API_BASE_URL}/places/{urllib.parse.quote(place_id)}?{urllib.parse.urlencode(query)}"

    headers = {
        "X-Goog-Api-Key": api_key,
        "X-Goog-FieldMask": effective_field_mask,
    }

    try:
        data = _http_get_json(url, headers=headers, timeout_sec=30)
    except Exception as e:
        return {
            "status": "error",
            "place_id": place_id,
            "error": str(e),
            "hint": "Places API が有効化され、課金設定が必要な場合があります。",
        }

    reviews_raw: List[Dict[str, Any]] = data.get("reviews") or []
    reviews_raw = reviews_raw[:safe_max_reviews] if safe_max_reviews else []

    def _review_text(r: Dict[str, Any]) -> str:
        text = r.get("text") or {}
        if isinstance(text, dict):
            return (text.get("text") or "").strip()
        if isinstance(text, str):
            return text.strip()
        return ""

    reviews: List[Dict[str, Any]] = []
    review_texts: List[str] = []
    for r in reviews_raw:
        if not isinstance(r, dict):
            continue
        t = _review_text(r)
        if t:
            review_texts.append(t)
        reviews.append(
            {
                "rating": r.get("rating"),
                "text": t,
                "publishTime": r.get("publishTime"),
                "authorName": (r.get("authorAttribution") or {}).get("displayName"),
                "relativePublishTimeDescription": r.get("relativePublishTimeDescription"),
            }
        )

    display_name = data.get("displayName") or {}
    name_text = display_name.get("text") if isinstance(display_name, dict) else None

    return {
        "status": "success",
        "place_id": place_id,
        "place": {
            "name": name_text or data.get("name"),
            "formattedAddress": data.get("formattedAddress"),
            "rating": data.get("rating"),
            "userRatingCount": data.get("userRatingCount"),
            "websiteUri": data.get("websiteUri"),
            "internationalPhoneNumber": data.get("internationalPhoneNumber"),
            "reviews": reviews,
        },
        "review_texts": review_texts,
        # "raw": data, # デバッグ用
    }


maps_toolset = MCPToolset(
    connection_params=StreamableHTTPConnectionParams(
        url=MAPS_MCP_URL,
        headers={
            "X-Goog-Api-Key": os.getenv("GOOGLE_MAPS_API_KEY", ""),
        },
         timeout=120,
    )
)

instruction = """
あなたは Google Maps のエキスパートです。

このエージェントは、以下を行えます:
- Google Maps 検索(place_id の特定、位置情報の取得)
- 天気情報の取得
- 経路・距離の計算
- Places API による Place Details 取得(reviews/住所/評価など)

注意:
- Maps MCP ツール単体ではクチコミ本文は取得できないため、必要なら get_place_details を使ってください。
""".strip()

# エージェントの定義
root_agent = Agent(
    model="gemini-2.5-flash",
    name="maps_agent",
    description="An agent that can use Google Maps (Google-hosted MCP server).",
    instruction=instruction,
    tools=[
        maps_toolset,
        get_place_details,
    ],
)

いざテスト

Web UI 起動

$ adk web --port 8000

INFO:     Started server process [88832]
INFO:     Waiting for application startup.

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://127.0.0.1:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.

質問1:店を探してもらう

純正 MCP に含まれている機能です。

Q: 東銀座駅に近いマクドナルドはどこ?

ちゃんと検索してくれました。

質問2:経路を計算させる

純正 MCP に含まれている機能です。

Q: 東銀座を出て、 その2店舗をはしごしたら、何分かかる?

経路検索です。

難易度の高い計算です。もともとあった機能ですが、MCP 経由でもきちんと機能しました。

質問3:クチコミを取得しておく

これは公式 MCP の機能ではなく、
Agent に追加した独自 Tool(Places API: Place Details)による取得です。

Q: その2店舗のクチコミを列挙してください。

2回 Place Details を実施していることがわかります。

きちんと取得できています。