住所補正用のMCPサーバを構築してみた
はじめに
前回の こちらの記事 では、LLMを活用して構造化されたデータを扱う方法についてご紹介しましたが、その中で「住所の存在チェックや自動補正ができたら便利だな」と感じる場面がありました。
というのも、住所情報は市町村の合併や区画整理などで日々変化しており、LLMのような静的な知識をベースとしたモデルでは、最新の住所に対応できないという課題があります。そのため、住所の正しさをリアルタイムに確認したり、補正したりできる外部連携の仕組みがあると便利です。
一般的には、郵便局の住所データを使って補正するケースが多いと思いますが、こちらは自前で定期的にデータを取り込み・更新する必要があり、やや手間がかかります。
そこで今回は、Geoloniaが提供しているGeolonia 住所データツール v2の公開住所APIを使って、住所補正に対応したMCPサーバを構築してみた、という内容になります。すでにGoogle Maps API版のMCPサーバはこちらで提供されていますが、APIコストがかかるため、Geolonia 住所データツール v2を活用してみることにしました。
MCPサーバとは
詳細は、こちらに記載がありますが、introductionを引用すると、
MCP(Model Context Protocol) は、アプリケーションが大規模言語モデル(LLM)にコンテキスト(外部データやツール)を渡す方法を標準化するためのオープンプロトコルです。
例えるなら、「AIアプリケーションのためのUSB-Cポート」のような存在です。
USB-Cがあらゆるデバイスと周辺機器を統一された方法で接続できるように、MCPはLLMをさまざまなデータソースやツールに接続する標準化された方法を提供します。
Geolonia 住所データツール v2とは
株式会社 Geoloniaが公開している Geolonia 住所データツール v2(japanese-addresses-v2) は、日本の住所データをJSON形式で提供している公開APIです。
このAPIを利用することで、都道府県・市区町村・大字町・丁目といった住所要素を階層的に取得できます。たとえば以下のようなAPIエンドポイントにリクエストすることで東京都の市区町村の情報を取得することができます。
$ curl -s "https://japanese-addresses-v2.geoloniamaps.com/api/ja/東京都.json"
output
{"meta":{"updated":1735102668},"data":[{"code":131016,"city":"千代田区","city_k":"チヨダク","city_r":"Chiyoda-ku","point":[139.753634,35.694003]},{"code":131024,"city":"中央区","city_k":"チュウオウク","city_r":"Chuo-ku","point":[139.772003,35.670587]},{"code":131032,"city":"港区","city_k":"ミナトク","city_r":"Minato-ku","point":[139.751599,35.658071]},{"code":131041,"city":"新宿区","city_k":"シンジュクク","city_r":"Shinjuku-ku","point":[139.703463,35.69389]},{"code":131059,"city":"文京区","city_k":"ブンキョウク","city_r":"Bunkyo-ku","point":[139.752473,35.707976]},{"code":131067,"city":"台東区","city_k":"タイトウク","city_r":"Taito-ku","point":[139.779984,35.712607]},{"code":131075,"city":"墨田区","city_k":"スミダク","city_r":"Sumida-ku","point":[139.801497,35.710724]},{"code":131083,"city":"江東区","city_k":"コウトウク","city_r":"Koto-ku","point":[139.817365,35.672859]},{"code":131091,"city":"品川区","city_k":"シナガワク","city_r":"Shinagawa-ku","point":[139.73025,35.609066]},{"code":131105,"city":"目黒区","city_k":"メグロク","city_r":"Meguro-ku","point":[139.698118,35.64141]},{"code":131113,"city":"大田区","city_k":"オオタク","city_r":"Ota-ku","point":[139.715987,35.56126]},{"code":131121,"city":"世田谷区","city_k":"セタガヤク","city_r":"Setagaya-ku","point":[139.653208,35.646481]},{"code":131130,"city":"渋谷区","city_k":"シブヤク","city_r":"Shibuya-ku","point":[139.697948,35.663982]},{"code":131148,"city":"中野区","city_k":"ナカノク","city_r":"Nakano-ku","point":[139.663738,35.707268]},{"code":131156,"city":"杉並区","city_k":"スギナミク","city_r":"Suginami-ku","point":[139.636414,35.699546]},{"code":131164,"city":"豊島区","city_k":"トシマク","city_r":"Toshima-ku","point":[139.716702,35.726321]},{"code":131172,"city":"北区","city_k":"キタク","city_r":"Kita-ku","point":[139.733657,35.752805]},{"code":131181,"city":"荒川区","city_k":"アラカワク","city_r":"Arakawa-ku","point":[139.783372,35.736083]},{"code":131199,"city":"板橋区","city_k":"イタバシク","city_r":"Itabashi-ku","point":[139.709246,35.751245]},{"code":131202,"city":"練馬区","city_k":"ネリマク","city_r":"Nerima-ku","point":[139.651725,35.7356]},{"code":131211,"city":"足立区","city_k":"アダチク","city_r":"Adachi-ku","point":[139.804584,35.774945]},{"code":131229,"city":"葛飾区","city_k":"カツシカク","city_r":"Katsushika-ku","point":[139.847213,35.74343]},{"code":131237,"city":"江戸川区","city_k":"エドガワク","city_r":"Edogawa-ku","point":[139.868312,35.706648]},{"code":132012,"city":"八王子市","city_k":"ハチオウジシ","city_r":"Hachioji-shi","point":[139.316075,35.66657]},{"code":132021,"city":"立川市","city_k":"タチカワシ","city_r":"Tachikawa-shi","point":[139.407846,35.713981]},{"code":132039,"city":"武蔵野市","city_k":"ムサシノシ","city_r":"Musashino-shi","point":[139.565938,35.717837]},{"code":132047,"city":"三鷹市","city_k":"ミタカシ","city_r":"Mitaka-shi","point":[139.559883,35.683308]},{"code":132055,"city":"青梅市","city_k":"オウメシ","city_r":"Ome-shi","point":[139.274994,35.788171]},{"code":132063,"city":"府中市","city_k":"フチュウシ","city_r":"Fuchu-shi","point":[139.47766300000004,35.668921]},{"code":132071,"city":"昭島市","city_k":"アキシマシ","city_r":"Akishima-shi","point":[139.353773,35.705705]},{"code":132080,"city":"調布市","city_k":"チョウフシ","city_r":"Chofu-shi","point":[139.540683,35.650628]},{"code":132098,"city":"町田市","city_k":"マチダシ","city_r":"Machida-shi","point":[139.438527,35.546559]},{"code":132101,"city":"小金井市","city_k":"コガネイシ","city_r":"Koganei-shi","point":[139.502989,35.699479000000004]},{"code":132110,"city":"小平市","city_k":"コダイラシ","city_r":"Kodaira-shi","point":[139.477474,35.728496]},{"code":132128,"city":"日野市","city_k":"ヒノシ","city_r":"Hino-shi","point":[139.394981,35.671347]},{"code":132136,"city":"東村山市","city_k":"ヒガシムラヤマシ","city_r":"Higashimurayama-shi","point":[139.468484,35.754624]},{"code":132144,"city":"国分寺市","city_k":"コクブンジシ","city_r":"Kokubunji-shi","point":[139.462252,35.710943]},{"code":132152,"city":"国立市","city_k":"クニタチシ","city_r":"Kunitachi-shi","point":[139.441366,35.683912]},{"code":132187,"city":"福生市","city_k":"フッサシ","city_r":"Fussa-shi","point":[139.326744,35.738601]},{"code":132195,"city":"狛江市","city_k":"コマエシ","city_r":"Komae-shi","point":[139.578696,35.634814]},{"code":132209,"city":"東大和市","city_k":"ヒガシヤマトシ","city_r":"Higashiyamato-shi","point":[139.426591,35.745328]},{"code":132217,"city":"清瀬市","city_k":"キヨセシ","city_r":"Kiyose-shi","point":[139.526422,35.785761]},{"code":132225,"city":"東久留米市","city_k":"ヒガシクルメシ","city_r":"Higashikurume-shi","point":[139.529673,35.757992]},{"code":132233,"city":"武蔵村山市","city_k":"ムサシムラヤマシ","city_r":"Musashimurayama-shi","point":[139.387421,35.754906]},{"code":132241,"city":"多摩市","city_k":"タマシ","city_r":"Tama-shi","point":[139.446366,35.636959]},{"code":132250,"city":"稲城市","city_k":"イナギシ","city_r":"Inagi-shi","point":[139.504603,35.637938]},{"code":132276,"city":"羽村市","city_k":"ハムラシ","city_r":"Hamura-shi","point":[139.311042,35.767168]},{"code":132284,"city":"あきる野市","city_k":"アキルノシ","city_r":"Akiruno-shi","point":[139.294046,35.728959]},{"code":132292,"city":"西東京市","city_k":"ニシトウキョウシ","city_r":"Nishitokyo-shi","point":[139.538159,35.725499]},{"code":133035,"county":"西多摩郡","county_k":"ニシタマグン","county_r":"Nishitama-gun","city":"瑞穂町","city_k":"ミズホマチ","city_r":"Mizuho-machi","point":[139.354054,35.771989]},{"code":133051,"county":"西多摩郡","county_k":"ニシタマグン","county_r":"Nishitama-gun","city":"日の出町","city_k":"ヒノデマチ","city_r":"Hinode-machi","point":[139.257405,35.742115]},{"code":133078,"county":"西多摩郡","county_k":"ニシタマグン","county_r":"Nishitama-gun","city":"檜原村","city_k":"ヒノハラムラ","city_r":"Hinohara-mura","point":[139.148852,35.72684]},{"code":133086,"county":"西多摩郡","county_k":"ニシタマグン","county_r":"Nishitama-gun","city":"奥多摩町","city_k":"オクタママチ","city_r":"Okutama-machi","point":[139.096214,35.80952]},{"code":133612,"city":"大島町","city_k":"オオシママチ","city_r":"Oshima-machi","point":[139.355619,34.750058]},{"code":133621,"city":"利島村","city_k":"トシマムラ","city_r":"Toshima-mura","point":[139.282584,34.529354]},{"code":133639,"city":"新島村","city_k":"ニイジマムラ","city_r":"Niijima-mura","point":[139.256556,34.377138]},{"code":133647,"city":"神津島村","city_k":"コウヅシマムラ","city_r":"Kozushima-mura","point":[139.134426,34.205371]},{"code":133817,"city":"三宅村","city_k":"ミヤケムラ","city_r":"Miyakejima Miyake-mura","point":[139.47974700000003,34.075779]},{"code":133825,"city":"御蔵島村","city_k":"ミクラジマムラ","city_r":"Mikurajima-mura","point":[139.596009,33.897156]},{"code":134015,"city":"八丈町","city_k":"ハチジョウマチ","city_r":"Hachijojima Hachijo-machi","point":[139.789079,33.112797]},{"code":134023,"city":"青ヶ島村","city_k":"アオガシマムラ","city_r":"Aogashima-mura","point":[139.763337,32.466537]},{"code":134210,"city":"小笠原村","city_k":"オガサワラムラ","city_r":"Ogasawara-mura","point":[142.191904,27.09438]}]}%
また、定期的に更新されているため、モデルのカットオフに依存せず、外部の正しい住所情報に追随する仕組みを構築できるのではと想定しています。
ただし、商用利用する際は、自身でホスティングするまたは有償で管理・ホスティングするサービスが提供されていますので、そのような方法で運用することが推奨されています。
MCPサーバの作成
環境作成
uvをインストール
$ curl -LsSf https://astral.sh/uv/install.sh | sh
python仮想環境作成
$ uv init japanese-address-collector
$ cd japanese-address-collector/
$ uv venv
$ source .venv/bin/activate
モジュールのインストール
$ uv add "mcp[cli]" httpx python-Levenshtein
MCPサーバ
MCPサーバ用コード(address_check.py)の実装
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
import Levenshtein
import re
import logging
# Initialize FastMCP server
mcp = FastMCP("address_checker")
GEOLONIA_BASE = "https://japanese-addresses-v2.geoloniamaps.com/api/ja"
# List of prefectures
PREFECTURES = [
"北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
"茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県",
"新潟県", "富山県", "石川県", "福井県", "山梨県", "長野県",
"岐阜県", "静岡県", "愛知県", "三重県",
"滋賀県", "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県",
"鳥取県", "島根県", "岡山県", "広島県", "山口県",
"徳島県", "香川県", "愛媛県", "高知県",
"福岡県", "佐賀県", "長崎県", "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県"
]
def get_closest(query: str, candidates: list[str]) -> str:
"""Return the candidate with the smallest Levenshtein distance"""
distances = [(cand, Levenshtein.distance(query, cand)) for cand in candidates]
return min(distances, key=lambda x: x[1])[0]
async def fetch_json(url: str) -> dict[str, Any] | None:
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, timeout=10.0)
response.raise_for_status()
return response.json()
except Exception:
return None
async def fetch_city_list(prefecture: str) -> list[str]:
"""Fetch list of cities (municipalities) from a prefecture"""
url = f"{GEOLONIA_BASE}/{prefecture}.json"
data = await fetch_json(url)
if not data or "data" not in data:
return []
return [entry["city"] for entry in data["data"] if "city" in entry]
async def fetch_address_data(pref: str, city: str) -> dict[str, Any] | None:
"""Fetch actual address data"""
url = f"{GEOLONIA_BASE}/{pref}/{city}.json"
return await fetch_json(url)
@mcp.tool()
async def check_address_exists(prefecture: str, city: str, oaza: str, chome: str) -> str:
"""
Check if an address exists using Levenshtein distance for correction
"""
# Correct prefecture
corrected_pref = get_closest(prefecture, PREFECTURES)
# Fetch city candidates and correct city name
cities = await fetch_city_list(corrected_pref)
if not cities:
return f"Failed to fetch city list for {corrected_pref}."
corrected_city = get_closest(city, cities)
# Fetch address data
result = await fetch_address_data(corrected_pref, corrected_city)
if not result or "data" not in result:
return f"Failed to fetch address data for {corrected_pref}{corrected_city}."
data = result["data"]
# Correct oaza (neighborhood name)
oaza_candidates = list({entry.get("oaza_cho") for entry in data if "oaza_cho" in entry})
corrected_oaza = get_closest(oaza, oaza_candidates)
for entry in data:
if entry.get("oaza_cho") == corrected_oaza and entry.get("chome") == chome:
return (
f"Corrected address found:\n"
f"{corrected_pref}{corrected_city}{corrected_oaza}{chome}"
)
return (
f"The input address “{prefecture}{city}{oaza}{chome}” was not found.\n"
f"The corrected address is “{corrected_pref}{corrected_city}{corrected_oaza}” but {chome} was not found."
)
if __name__ == "__main__":
mcp.run(transport="stdio")
ロジックの解説
MCPサーバにおける住所補正の主なロジックは以下の通りです:
-
都道府県の補正
日本の47都道府県の固定リストからLevenshtein距離を用いて最も近い名称を選定します。 -
市区町村の補正
Geolonia 住所データツール v2 APIから対象都道府県に属する市区町村リストを取得し、同様に最も近い候補を選定します。 -
大字・町の補正
該当市区町村の住所データを取得し、 oaza_cho に含まれる町名の候補群から最も近いものを同様に選定します。 -
存在チェック
補正後の住所(都道府県+市区町村+大字+丁目)が、住所データ上に存在するかを最終的に確認し、結果を返します。
全体として、ユーザーから与えられた曖昧な住所入力を自動補正し、Geolonia 住所データツール v2ベースで存在確認できる仕組みをMCP経由で構築しています。
Claude for Desktopで実行
Claude for DesktopをMCPクライアントとして、実行してみます。
Claude for Desktopは、こちらからダウンロードできます。
MCPサーバ設定ファイルの編集
Claude for Desktopを起動して、claude_desktop_config.jsonにMCPサーバの実行コマンドを追記します。
{
"mcpServers": {
"address_check": {
"command": "{uvの絶対パス}",
"args": [
"--directory",
"{仮想環境の絶対パス}",
"run",
"address_check.py"
]
}
}
}
実行結果
Claude for Desktopで「東京都中央区日本橋本町三丁目」の住所を例に、住所の存在確認を何パターンか試してみました。
正常パターン:存在する正しい住所の場合

異常パターン1:「東京都」の「都」の記載がない場合

異常パターン2:「中央区」の「区」の記載がない場合

異常パターン3:「東京都」の記載がない場合

異常パターン4:「東京都中央区」の記載がない場合

まとめ
本記事では、Geolonia 住所データツール v2を活用し、公開APIかつ定期更新可能な日本住所データを使って、簡易的な住所補正用のMCPサーバをFastMCPで構築してみました。
誤った住所を補正して、正しい住所を提案する流れを実現できました。
実際には、toolsの利用で、1往復分コストが追加でかかるため、コストは要検討ですが、
番地や建物名レベルでの補完・正規化も対象に含めていくことで、LLMによる住所処理の精度をより高めることができると考えています。
参考
・https://claude.ai/download
・https://modelcontextprotocol.io/introduction
・https://github.com/modelcontextprotocol/servers-archived/tree/main/src/google-maps
・https://github.com/geolonia/japanese-addresses-v2
Discussion