こちらは、ArcGIS アドベントカレンダー2025 の 24 日目の記事です。他の記事もぜひご覧ください。
今年も AI と開発に関する話題が絶え間なく取り上げられていた 1年でした。
ArcGIS でも GeoAI はもちろん、各製品に用意された AI アシスタントや今後登場する AI エージェントなどの AI 機能の開発が進んでおり、地理空間 AI プラットフォームが構築されつつあります。
そんな AI と開発に関する話題の中でも、とりわけ注目度の高い技術として Model Context Protocol (MCP) が挙げられると思います。
本記事では、MCP サーバーを簡単に構築できる FastMCP を使って ArcGIS のルート検索機能を大規模言語モデル (以下 LLM) を用いた AI チャット アプリで利用できるように下図のように作成してみたいと思います。
AI チャット アプリとルート検索機能をつなぐ MCP サーバー の構成例
MCP は、LLM が外部ツール (Web API など) と安全かつ統一的に連携するためオープン プロトコルです。この技術は、Claude などで有名な AI 企業である Anthropic 社が開発、提唱し、AI アプリの開発者に広く支持されています。様々な AI チャット アプリが MCP に対応するなど、今年最も盛り上がった LLM 周辺技術の1つであると言っても過言ではありません。
地理空間情報の分野においても日本国内の地理空間情報のポータル サイトとして展開されている国土交通データプラットフォームでは MCP サーバーが公開されています。
本記事で使用する MCP に接続できるソフトウェアと MCP サーバーを構築するのに便利なライブラリの一覧を箇条書きで下記に示します。
MCP サーバー/クライアントを Python で簡単・高速に構築できるフレームワークで、本記事では MCP サーバーを作成するのに利用します。
Anthropic 社が提供する AI チャット アプリ Claude のデスクトップ アプリで、本記事では MCP サーバーへ接続し、正しく動作しているか確認する MCP ホストとして利用します。
Microsoft 社が提供する無償のソースコード エディターで、本記事では拡張機能である GitHub Copilot Chat を Claude Desktop と同様に MCP ホストとして利用します。
LLM を使った AI チャット アプリでは、ルート検索について聞いても周辺情報からの回答を返すことしかできません。実際に Claude Desktop でルート検索を依頼してみた結果は下図の通りです。
MCP を使わない場合
回答してくれてはいますが、正確な情報は別のアプリやサービスで聞くようおすすめされています。
MCP サーバーとしてルート検索の Web API が利用できれば、LLM でもルート検索が扱えるようになります。ArcGIS は REST API 形式で公開されているルート検索機能を提供しています。本記事では、ArcGIS のルート検索機能に Python スクリプトからリクエストを送信して得られたルート検索の結果を LLM の回答に反映させるため、ArcGIS Routing MCP として MCP サーバーを作成してみます。
なお、ルート検索を実行するのに必要な API キーは ArcGIS Location Platform アカウントで作成し、API キーは Python スクリプト内に記述します。アカウントの作成方法はコチラから、API キーの作成する方法はコチラをご参考にしてみてください。
まず初めにルート検索用のエンド ポイントへリクエストを送信して、ルートの検索結果を取得する Python スクリプトを作成します。
import json
import urllib.parse
import urllib.request
ARCGIS_ROUTING_URL = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve"
API_KEY = "<事前に用意した API キー>"
def _call_arcgis_route(stops, token, return_directions=True, depart="now"):
params = {
"f": "json",
"token": token,
"stops": ";".join(f"{x},{y}" for (x, y) in stops),
"returnDirections": str(return_directions).lower(),
"startTime": depart
}
url = ARCGIS_ROUTING_URL + "?" + urllib.parse.urlencode(params)
with urllib.request.urlopen(url, timeout=30) as resp:
return json.loads(resp.read())
if __name__=="__main__":
stops=[(139.745486, 35.658569),(139.749603, 35.660191)]
result = _call_arcgis_route(stops, API_KEY, return_directions=True,depart="now")
if "error" in result:
raise RuntimeError(result["error"])
print(result)
上記のスクリプトで、2点間のルート検索を行うことができます。
スタート地点を東京タワー、ゴール地点を芝公園の緯度経度を入力しているので実行してみるとルートの詳細を JSON 形式で返してくれています。
Python スクリプトを実行した結果
なお、今回はチャットの文章でルート検索ができるようにしたいので、
上記のスクリプトに住所検索とルート検索で移動手段を指定する際に利用する移動手段のエンド ポイントにリクエストする関数を追加しています。
追加した例が下記のスクリプトです。
import json
import urllib.parse
import urllib.request
GEOCODE_FIND_URL = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates"
ARCGIS_ROUTING_URL = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve"
ARCGIS_TRAVELMODE_URL = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/retrieveTravelModes"
API_KEY ="<事前に用意した API キー>"
def _http_get(url: str) -> dict:
with urllib.request.urlopen(url, timeout=30) as resp:
return json.loads(resp.read())
def geocode_address(address: str, token: str, country_code: str = "JP") -> dict:
"""
住所や都市名、POI から 緯度経度へ変換する。
"""
params = {
"f": "json",
"token": token,
"SingleLine": address,
"countryCode": country_code,
"outFields": "*",
"langCode": "ja",
"maxLocations": 1
}
url = GEOCODE_FIND_URL + "?" + urllib.parse.urlencode(params)
result = _http_get(url)
if "error" in result:
raise RuntimeError(result["error"])
for c in result.get("candidates", []):
loc = c.get("location") or {}
return {
"lon": loc.get("x"),
"lat": loc.get("y"),
}
def _travel_mode_selector(travelMode: str, token: str):
"""
travelMode 引数から ArcGIS Routing の travelMode 名を返す。
"""
params = {
"f": "json",
"token": token
}
url = ARCGIS_TRAVELMODE_URL + "?" + urllib.parse.urlencode(params)
travelmode_list=_http_get(url)["supportedTravelModes"]
# 一致した文字列があれば travelmode として返す。
for travelmode in travelmode_list:
if travelmode["name"]== travelMode:
travelmode_param=travelmode
break
return travelmode_param
def _call_arcgis_route(stops, token, return_directions=True, travel_mode="運転時間", depart="now"):
params = {
"f": "json",
"token": token,
"stops": ";".join(f"{x},{y}" for (x, y) in stops),
"returnDirections": str(return_directions).lower(),
"travelMode": travel_mode, # 追加
"startTime": depart, # now / yyyy-mm-ddThh:mm:ss
}
url = ARCGIS_ROUTING_URL + "?" + urllib.parse.urlencode(params)
return _http_get(url)
if __name__=="__main__":
stops=["東京タワー","芝公園"]
result = _call_arcgis_route(stops, API_KEY, return_directions=True,depart="now")
if "error" in result:
raise RuntimeError(result["error"])
print(result)
FastMCP は MCP サーバー/クライアントを Python で簡単・高速に構築できるフレームワークです。
ここからは上記で作成した Python スクリプトを FastMCP を使って MCP サーバー化していきましょう。
FastMCP は pip からライブラリをインストールできます。
pip install fastmcp
上記のコマンドを実行したあとで FastMCP がインストールされているか下記のスクリプトを実行して試してみましょう。
インストールした時点での最新版がインストールされているはずです。
import fastmcp
print(fastmcp.__version__)
# >> 2.xx.x
ここまで作成してきたルート検索用のエンド ポイントにリクエストを送信する Python スクリプトに FastMCP ライブラリをインポートし、MCP サーバーを立ち上げるためのスクリプトを記述していきます。
追加したスクリプトが下記となります。追加したコマンドや関数には、コメント アウトで追加した旨を記載してあります。
import json
import urllib.parse
import urllib.request
from fastmcp import FastMCP # 追加
mcp = FastMCP("ArcGIS Routing MCP Server") # 追加
GEOCODE_FIND_URL = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates"
ARCGIS_ROUTING_URL = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve"
ARCGIS_TRAVELMODE_URL = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/retrieveTravelModes"
API_KEY ="<事前に用意した API キー>"
def _http_get(url: str) -> dict:
with urllib.request.urlopen(url, timeout=30) as resp:
return json.loads(resp.read())
def geocode_address(address: str, token: str, country_code: str = "JP") -> dict:
"""
住所や都市名、POI から 緯度経度へ変換する。
"""
params = {
"f": "json",
"token": token,
"SingleLine": address,
"countryCode": country_code,
"outFields": "*",
"langCode": "ja",
"maxLocations": 1
}
url = GEOCODE_FIND_URL + "?" + urllib.parse.urlencode(params)
result = _http_get(url)
if "error" in result:
raise RuntimeError(result["error"])
for c in result.get("candidates", []):
loc = c.get("location") or {}
return {
"lon": loc.get("x"),
"lat": loc.get("y"),
}
def _travel_mode_selector(travelMode: str, token: str):
"""
travelMode 引数から ArcGIS Routing の travelMode 名を返す。
"""
params = {
"f": "json",
"token": token
}
url = ARCGIS_TRAVELMODE_URL + "?" + urllib.parse.urlencode(params)
travelmode_list=_http_get(url)["supportedTravelModes"]
# 一致した文字列があれば travelmode として返す。
for travelmode in travelmode_list:
if travelmode["name"]== travelMode:
travelmode_param=travelmode
break
return travelmode_param
def _call_arcgis_route(stops, token, return_directions=True, travel_mode="運転時間", depart="now"):
params = {
"f": "json",
"token": token,
"stops": ";".join(f"{x},{y}" for (x, y) in stops),
"returnDirections": str(return_directions).lower(),
"travelMode": travel_mode, # 追加
"startTime": depart, # now / yyyy-mm-ddThh:mm:ss
}
url = ARCGIS_ROUTING_URL + "?" + urllib.parse.urlencode(params)
return _http_get(url)
# ここから追加
@mcp.tool()
def solve_route(
stop_list: list,
depart: str = "now",
travelMode: str = "運転時間",
) -> dict:
"""
ArcGIS Routing の Route_World を呼び、最短/最速ルートと案内を返す。
Args:
stop_list: 経由地名リスト(住所文字列のリスト)
depart: 出発時刻("now" または ISO8601)特に指定がなければ "now"
travelMode: 移動手段("運転時間", "運転距離","徒歩時間","徒歩距離","トラック輸送時間","トラック輸送距離", "運転時間 (未舗装道路使用)","運転距離 (未舗装道路使用)")特に指定がなければ "運転時間"
Returns:
ルート形状・時間・距離・案内(可能なら日本語)を含む辞書
"""
stops=[]
for stop in stop_list:
geocode_result=geocode_address(stop, API_KEY)
stops.append((geocode_result["lon"], geocode_result["lat"]))
result = _call_arcgis_route(stops, API_KEY, return_directions=True, travel_mode=_travel_mode_selector(travelMode, API_KEY), depart=depart)
if "error" in result:
raise RuntimeError(result["error"])
return result # MCP の tool 戻り値は JSON/オブジェクトで良い
# ここまで追加
if __name__ == "__main__":
mcp.run() # ここに追加
# 下記をコメント アウト
# stops=["東京タワー","芝公園"]
# result = _call_arcgis_route(stops, API_KEY, return_directions=True,depart="now")
# if "error" in result:
# raise RuntimeError(result["error"])
# print(result)
ここでのポイントは @mcp.tool 下の関数が LLM の利用するツールとしてスクリプトやツールの説明文を記述している点です。LLM が MCP サーバーを参照する際には、ここに記載の説明文を確認してリクエストする際に必要な引数の JSON を作成します。そのため、利用するのに必要となる引数のデータ型の指定と説明をコメント アウト内に記載し、LLM が使い方を間違えないようにします。
このスクリプトを実行して問題なければ下図のようにローカルで MCP サーバーが立ち上がるようになります。
MCP サーバーの起動
ここまで記述してきたスクリプトでも Claude Desktop に設定可能ですが、
本当に実行できるか試してみるために FastMCP ライブラリで MCP クライアントも作って試してみましょう。
クライアント側では、MCP サーバーで設定されたツールを呼び出すように
下記のようなスクリプトを記述しておきます。
import asyncio
from fastmcp import Client
from fastmcp.client.transports import PythonStdioTransport
async def main():
# サーバーを起動
transport = PythonStdioTransport(script_path="./arcgis_route.py")
# MCP クライアントを作成し、MCP サーバーに接続
async with Client(transport) as client:
# solve_route ツールの呼び出し
result = await client.call_tool("solve_route",
{
"stop_list": ["東京駅","東京タワー"],
"travelMode": "運転時間"
}
)
print("ツール呼び出し結果:", result)
if __name__ == "__main__":
asyncio.run(main())
ここでは、クライアントから MCP サーバーを起動しているため、スクリプトを実行するとルート検索の結果が表示されるようになっています。
実際に実行してみた結果は下図の通りです。
MCPクライアントを実行
ArcGIS Routing MCP サーバーが正しく動作していていることがわかったので、
次に LLM で利用できるか確認するために Claude Desktop で実行してみましょう。
Claude Desktop は Anthropic 社が提供する AI チャット アプリ Claude のデスクトップ アプリで、ローカル ファイルや外部ツールとの連携が実現できます。
Claude Desktop でローカルの MCP サーバーを実行するにはいくつか準備が必要です。
ここでは、その準備を行った後に Claude Desktop で ArcGIS Routing MCP を使ってみましょう。
Claude Desktop へサイン インし、アプリ画面の左下のアイコンをクリックすると設定項目があるのでそちらをクリックします。
設定画面へのクリック
表示されたメニューから 「開発者」 の項目をクリックし、遷移先で「設定を編集」をクリックします。
「設定を編集」をクリック
クリックするとエクスプローラーが起動し、`claude_desktop_config.json` が生成されます。
`claude_desktop_config.json` をコード エディターで開くと空の `{}` が用意されています。
この中括弧を下記のように書き換えます。
{
"mcpServers": {
"arcgis-routing-mcp": {
"command": "<python 環境への絶対パス>\\python.exe",
"args": [
"<スクリプトの絶対パス>\\arcgis_route.py"
]
}
}
}
設定後、 1度 Claude Desktop を閉じる必要がありますが、注意していただきたい点は右上の×ボタンでウィンドウを閉じるのではなく、左上のハンバーガー メニューをクリックして表示される「終了」をクリックし、アプリを完全に終了させる必要があります。Claude Desktop のタスクが残っている状態ですので、タスク マネージャー等から Claude Desktop のタスクを削除しておきます。
Claude Desktop を閉じる手順
ここまで問題なければ再度 Claude Desktop を立ち上げるとチャットの入力欄の `+` ボタンをクリックして出てくるコネクタと設定の「開発者」の項目に ArcGIS Routing MCP が追加されているはずです。
コネクタに追加された様子
「開発者」項目に追加された様子
立ち上げた Claude Desktop で ArcGIS Routing MCP を使ってルート検索をしてみましょう。
動画内でも確認していますが solve_route と書いてあるプルダウン メニューを押すと実際にリクエストした JSON とレスポンスされた JSON が閲覧できます。
最後に出力してもらった結果が正しそうかこのルート検索の結果を GeoJSON 化してもらい、ArcGIS Online の Map Viewerで描画してみましょう。
Claude に GeoJSON 化を依頼
ルートを GeoJSON で書き出した結果
出力された GeoJSON も間違いなさそうです。
ここまでで FastMCP を使うことで ArcGIS のルート検索機能 を LLM で利用できていることを確認できました。
様々な用途が考えられますが、LLM だけでは到達できないタスクを MCP で外部ツールにアクセスすることで達成できるようになりました。
最後に Visual Studio Code でも利用できるか試してみましょう。
MCP のメリットの 1 つとして挙げられるのは、あらゆる LLM に対応できるという点です。こちらを確認するために Visual Studio Code の GitHub Copilot Chat でも ArcGIS Routing MCP が使えるか試してみました。
Visual Studio Code での設定は GitHub アカウントにあらかじめサイン インをしたうえでGitHub Copilot と GitHub Copilot Chat の拡張機能をインストールしておきます。
上記の設定が完了している状態でしたら MCP サーバーを使えるように `mcp.json` を設定します。
`mcp.json` は Visual Studio Code で MCP サーバーのディレクトリを開き、同じディレクトリ内の `.vscode` のディレクトリ内に作成します。
`mcp.json` の構成は Claude Desktop と似ていますが細部が異なるのでご注意ください。
{
"servers": {
"arcgis-routing-mcp": {
"type": "stdio",
"command": "<python 環境への絶対パス>\\python",
"args": ["arcgis_route.py"],
}
}
}
上記を設定して問題なければ `mcp.json` 上で図のように `Start` などの文字列が `arcgis-routing-mcp` に表示されるようになりますので `Start` をクリックしてみます。
mcp.json の設定
作成した MCP サーバーのスクリプトや `mcp.json` の記述に問題がなければ、`Start` の箇所が `Running` と表記されます。
実際に GitHub Copilot Chat で試してみましょう。
Visual Studio Code の GitHub Copilot Chat を実行してみます。
Claude Desktopで実行したときと同等の結果が得られていそうです。
Claude Desktop でも実行したように GeoJSON 化してもらってこちらも ArcGIS Online の Map Viewerで確認してみましょう。
GitHub Copilot Chat で生成された GeoJSON
正しく結果が返されていそうですね。
このように ArcGIS Routing MCP は異なる AI チャット アプリでもスクリプトを変更することなく利用できました。
本記事では、ArcGIS のルート検索機能 を LLM で使えるように FastMCP を使って MCP サーバーを作成してみました。
今回はシンプルにルート検索のみできるようにしましたが、さらに発展して目的地や経由地として登録した場所の情報を AI に調査してもらい、ルート検索結果以外の情報を得ることができます。それらを GeoJSON などで出力し、ArcGIS Online に共有することで様々な端末で 閲覧できるようになります。また、MCP の魅力である様々な LLM に対応し、スクリプトを流用できるという点についても確認してみました。
これらを参考にぜひ ArcGIS の強力な地理空間解析を LLM で利用できるように挑戦してみていただければと思います。