GeoSpark について
GeoSpark は、LLM 向けに検証可能な地理空間計算ツールを提供します。空間分析を必要とする開発者やエージェントを対象に、幾何エンジンで距離やトポロジー、座標参照系を計算し、結果に出典、単位、再現用の記録を付けます。
主な機能
- 01測地距離とトポロジーを計算
- 02座標参照系を変換
- 03ジオコーディング、ルート、等時間圏を提供
- 04Overture Maps と環境データを検索
- 05出典と単位付きの構造化結果を返却
活用シーン
- 01地点間距離や点とポリゴンの関係を検証
- 02移動時間に基づく到達範囲を分析
- 03場所を比較して候補地を選定
- 04ローカルの地理空間データレイヤーを処理
MCP 対応
ローカルおよびホスト型 MCP サーバーを提供し、空間計算、場所データ、分析用の読み取り専用ツールを公開します。ローカルサーバーはローカルファイルのレイヤーツールにも対応します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://geospark.terrascout.app/mcpclaude mcp add --transport http maz2580-geospark https://geospark.terrascout.app/mcp{"mcpServers":{"maz2580-geospark":{"type":"http","url":"https://geospark.terrascout.app/mcp"}}}Cursor で開く{"servers":{"maz2580-geospark":{"type":"http","url":"https://geospark.terrascout.app/mcp"}}}VS Code で開く{"mcpServers":{"maz2580-geospark":{"type":"http","url":"https://geospark.terrascout.app/mcp"}}}{"mcpServers":{"maz2580-geospark":{"serverUrl":"https://geospark.terrascout.app/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
GeoSpark
あらゆる LLM に、検証可能な空間推論を。
距離、トポロジー、CRS は言語モデルが推測するのではなく、ジオメトリエンジンが計算します。オープンソースで、どこでも実行でき、ツールを呼び出せるあらゆるモデルで動作します。
LLM に 「エッフェル塔からルーヴル美術館までの距離は?」 や 「この点はあのポリゴンの内側ですか?」 と質問すると、選んだモデルによって回答は異なります。ベンチマークを実施した 9 種類のモデルでは、小型のオープンウェイトモデル(3.8B~9B)は距離を数値で答える質問の約 95% に誤答しました。最先端の API の正答率は、25%(Gemini 2.5 Pro)から 95%(GPT-5.4)までさまざまでした。
GeoSpark は、モデルが回答を計算するためのツールを提供します。WGS84 楕円体上の測地線距離、正確なトポロジー、CRS 変換、ジオコーディング、標高、リアルタイムの環境データを扱えます。これらのツールを使うことで、有効なツール呼び出しを行ったすべてのモデルが、同じ質問に対して 76~96% の正答率を達成しました。自分の CPU 上で動く 7B モデルが、有料の最先端 API と同等の性能を発揮し、すべての回答に検証可能な決定論的トレースが付属します。
試してみる
インストール不要:ウェブサイト。 geospark.terrascout.app では、 質問したり、到達可能なエリアを描画したり、あらゆる場所の周辺にある Overture Maps のデータを調べたりできます。各結果には 出典、注意事項、単位が表示され、GeoJSON 形式でダウンロードできます。
インストール不要:ホスト型 API(公開されており、キーは不要です。API リファレンス)
curl -X POST https://geospark.terrascout.app/api/v1/distance \
-H "Content-Type: application/json" \
-d '{"lat_a": 48.8584, "lon_a": 2.2945, "lat_b": 48.8606, "lon_b": 2.3376}'
# {"distance_m": 3172.01, "distance_km": 3.172, "note": "Geodesic distance on WGS84 ellipsoid"}
Claude または任意の MCP クライアントで、インストール不要:ホスト型 MCP サーバー
この URL を使ってカスタムコネクターを追加します(Claude の場合:設定 → コネクター → カスタムコネクターを追加):
https://geospark.terrascout.app/mcp
公開されており、キーは不要です。以下のローカルサーバーと同じ 15 個のツールを備え、ホスト型 API と同じレート制限が適用されます。ファイルを読み取ることはありません。独自のファイルを使う場合は、サーバーをローカルで実行してください。
Claude Desktop または任意の MCP クライアントで、お使いのコンピューター上で実行
pip install "geospark-ai[mcp]"
MCP クライアントの設定に GeoSpark を追加します。Claude Desktop の場合、macOS では
~/Library/Application Support/Claude/claude_desktop_config.json、Windows では
%APPDATA%\Claude\claude_desktop_config.json です:
{ "mcpServers": { "geospark": { "command": "geospark-mcp" } } }
これにより、モデルで読み取り専用の 15 個のツールを利用できます。計算: calculate_distance(サーバーがジオコーディングする地名間、または座標間の距離)、
check_spatial_relationship および spatial_operation(バッファー、面積、長さ、重心、凸包)。
場所とデータ: geocode、reverse_geocode、get_elevation、route、get_weather、
get_air_quality、get_active_fires、overture_features。分析: isochrone、travel_time_matrix、location_report、select_site。
結果は出力スキーマを備えた構造化コンテンツとして返され、通常は GeoJSON 形式です。出典、ライセンス、帰属表示などの来歴情報に加え、単位と警告も含まれます。GeoSpark が信頼できる回答を返せない場合(無効なジオメトリ、未知の場所、データソースの停止など)、ツールは理由を示すエラーを返すため、モデルは推測せずにユーザーへ伝えられます。サーバーは MCP SDK 2.x(プロトコル 2026-07-28)を使用しており、古いプロトコルバージョンのクライアントでも引き続き動作します。
Python ライブラリとして
pip install geospark-ai # core
pip install "geospark-ai[api,mcp]" # + REST server and MCP server
pip install "geospark-ai[all]" # everything
Docker で独自のサーバーを実行
docker run -p 8000:8000 ghcr.io/maz2580/geospark:latest
# then open http://localhost:8000
これは開発モードで実行され、オペレーター向けのルートが公開されます。ご自身のマシンで使う分には問題ありません。公開サーバーでは、-e GEOSPARK_ENV=production -e GEOSPARK_ADMIN_TOKEN=<secret> を追加してください(セルフホスティングを参照)。
できること
| 空間エンジン | 測地距離、面積、長さ(WGS84上でpyprojを使用)。Shapelyによるトポロジーチェック(contains、within、intersects、touches、crosses、overlaps、disjoint)。形状を保持するバッファー。union、intersection、difference、centroid、convex hull。すべての入力ジオメトリのCRS変換 |
| 場所と地形 | ジオコーディングと逆ジオコーディング(OpenStreetMap Nominatim。利用ポリシーに従い、1秒あたり1リクエスト以下でキャッシュを使用)。Copernicus GLO-30による標高(利用できない場合はOpen Elevationにフォールバック。鉛直基準系を考慮:NAVD88 / EGM96 / WGS84)。ルーティング(OSRM)。等時間圏(徒歩、自転車、車でN分以内に到達できる範囲)と所要時間行列(Valhalla) |
| ライブデータ | Overture Mapsの場所、建物、道路、行政区域(キー不要。DuckDB経由)。天気と予報(Open-Meteo、キー不要)。大気質(OpenAQ v3、無料のキーが必要)。活動中の火災(NASA FIRMS、無料のキーが必要)。衛星シーン検索(STAC) |
| エージェント | GeoAgent(平易な英語の目標から複数ステップの分析を実行)、SpatialReport(場所の詳細レポート:施設、アクセシビリティ、標高、天気)、SiteSelector(複数の基準による立地ランキング)、および目標に応じて適切なものに振り分けるコーディネーター |
| インターフェース | Pythonライブラリ、CLI(geospark …)、REST API(ドキュメント化された52の操作。リファレンスは/docs)、MCP サーバー(geospark-mcp)、ウェブプレイグラウンド |
| メモリとワークフロー | 事実、エピソード、矛盾検出機能を備えた空間メモリ。階層型コンテキストストア。Flows(自然言語の目標から構築できるDAGワークフロー) |
| GeoSpark Bench | トポロジー、距離、複数ステップの推論、変化、マルチモーダルタスクにわたる535問の再現可能な質問と、計算済みの正解データ |
計画中で、まだ実装されていない機能: 傾斜、可視領域、ラスターバンド演算、変化検出。これらの操作はプロトコルに存在しますが、現時点では回答の代わりに「まだ実装されていません」という明確なエラーを返します。ロードマップをご覧ください。
クイックスタート
正解データ付きジオメトリ
from geospark.engine.spatial_reasoner import SpatialReasoner
park = {"type": "Polygon", "coordinates": [[[2.29, 48.85], [2.30, 48.85], [2.30, 48.86], [2.29, 48.86], [2.29, 48.85]]]}
point = {"type": "Point", "coordinates": [2.295, 48.855]} # GeoJSON order: [lon, lat]
SpatialReasoner.check_relationship(park, point, "contains") # True
SpatialReasoner.calculate_distance(
{"type": "Point", "coordinates": [2.2945, 48.8584]}, # Eiffel Tower
{"type": "Point", "coordinates": [2.3376, 48.8606]}, # Louvre
) # 3172.01 (metres, geodesic)
エンジンを使ったクエリ
from geospark import Engine
from geospark.protocol import SpatialOperation, SpatialQuery
engine = Engine(tools=["geocoder", "terrain"])
place = engine.execute(SpatialQuery(operation=SpatialOperation.GEOCODE,
metadata={"query": "Federation Square, Melbourne"}))
print(place.features[0].geometry) # {'type': 'Point', 'coordinates': [144.969..., -37.817...]}
# Inputs in another CRS are transformed to WGS84 first, including the second geometry
buffered = engine.execute(SpatialQuery(operation=SpatialOperation.BUFFER,
geometry={"type": "Point", "coordinates": [2.2945, 48.8584]},
radius_m=100))
print(buffered.features[0].properties["area_sq_m"]) # ≈ 31,365
すべての回答は検証でき、QGISで開けます
result = engine.execute(SpatialQuery(operation=SpatialOperation.GEOCODE, metadata={"query": "Springfield"}))
result.status # "ok" | "partial" | "cannot_answer"
result.warnings # ["Ambiguous place name: 'Springfield, …, Illinois…'; '…Massachusetts…' …"]
result.provenance # source, licence (ODbL-1.0), attribution, retrieved_at, method
result.spatial_context.units # {"distance_m": "metre", ...}
import json
json.dump(result.to_geojson(), open("springfield.geojson", "w")) # RFC 7946, drag into QGIS
GeoSparkは推測せず、回答を拒否します。自己交差するポリゴン、範囲外の座標、または[lat, lon]の順序が入れ替わっている可能性がある場合は、自信ありげな誤った数値ではなく、理由とともにcannot_answerを返します。APIでは?format=geojson(またはAccept: application/geo+json)を追加してください。CLIではgeocode、elevation、query、reportに--geojson out.geojsonを追加してください。場所レポートは、場所と距離付きのすべての近隣施設を含むマップレイヤーになります。
APIキー不要のオープンマップデータ:Overture Maps
from geospark.tools.overture.client import query # pip install "geospark-ai[overture]"
query("divisions", 48.8606, 2.3376) # the Louvre is in: France › Île-de-France › Paris › Paris 1er Arrondissement
query("places", -37.8180, 144.9691, radius_m=200, category="cafe") # nearest first, geodesic metres
query("buildings", -37.8180, 144.9691, radius_m=150) # footprints, height, geodesic footprint area
query("transportation", -37.8180, 144.9691, radius_m=150) # road/path segments, geodesic length
GeoSparkはDuckDBを使い、S3上のOverture Maps GeoParquetを直接クエリします。リリースのSTACインデックスを使って対象エリアを含むファイルだけを読み込むため、ダウンロードもキーも不要です。結果には、使用したOvertureリリースと、返されたレコードのライセンスが表示されます。同じデータは、CLI(geospark overture places -37.818 144.969 --category cafe)、REST API(POST /api/v1/overture/{theme})、MCPツールoverture_featuresからも利用できます。
独自のデータ
pip install "geospark-ai[io]"
geospark layer info suburbs.gpkg # features, fields, CRS, geodesic area
geospark layer count-within crashes.csv suburbs.gpkg --geojson counts.geojson
geospark layer nearest schools.shp hospitals.geojson
geospark layer intersect parcels.gpkg flood_zones.geojson
対応形式:GeoJSON、GeoPackage、Shapefile(または.zip)、GeoParquet、経度・緯度列を含むCSV、FlatGeobuf、KML、GML、File Geodatabase。CRSを推測することは決してありません:
- 投影座標系のファイル(例:MGA zone 55)は再投影され、元のCRSが報告されます。
.prjのないShapefileは、--crs EPSG:28355を指定するまで受け付けません。- 無効な形状は、
--repairを指定するまで受け付けません。
結果には各ファイルのSHA-256が含まれるため、まったく同じデータを使って分析を再現できます。Pythonではgeospark.io.layers(load_layer、count_within、nearest、intersect)を使用してください。ローカルMCP サーバー(geospark-mcp)では同じ機能をlayer_*ツールとして提供しています。ホスト型の/mcpエンドポイントはファイルを読み込みません。
再現可能な分析
geospark record report "Federation Square, Melbourne" --out fedsq.json
geospark replay fedsq.json
# REPRODUCED: Same result as recorded.
# ... or DATA CHANGED: openstreetmap-overpass returned different data for the same request (step 6 ...)
# ... or CODE CHANGED: versions changed: {'shapely': ('2.0.0', '2.2.0')}
実行記録には、次の情報が記録されます。
- 環境:GeoSpark、Python、pyproj/PROJ、Shapely/GEOS のバージョン
- すべてのデータリクエスト(Nominatim、Overpass、Overture、Valhalla、天気、高度)とすべてのエンジン計算。それぞれに入力と出力のフィンガープリントが付けられます
- 最終結果のフィンガープリント
geospark replay は分析を再実行し、再現できたかどうかを知らせます。再現できなかった場合は、異なるデータを返したソース、またはコードやライブラリのバージョンが変更されたことを示します。Python では、geospark.runs.record_run(...) と replay(...) を使います。
自然言語での質問
result = engine.ask("How far is the Eiffel Tower from Big Ben?")
print(result.spatial_context.summary)
print(result.sources) # the model, then each tool it called
ask はまずローカルの Ollama モデルを使い、OPENROUTER_API_KEY が設定されている場合は無料の OpenRouter モデルにフォールバックします。
エージェント
from geospark.agents import AgentCoordinator, GeoAgent, SiteSelector, SpatialReport
GeoAgent().run("Find all hospitals within 2km of the Eiffel Tower").findings["nearby"]
report = SpatialReport().analyze("Federation Square, Melbourne", radius_m=1000)
print(report.weather, report.accessibility, report.data_gaps)
best = SiteSelector().find(within="Zurich", near=["hospital", "school"], facility_type="pharmacy").best
AgentCoordinator().run("Find the best location for a cafe in Melbourne CBD near schools")
エージェントは LLM を使って目的を読み取ります。LLM がなくても、ルールベースのパーサーが、上記のような複数単語の地名、「2km」のような半径、施設の種類を含むリクエストを処理します。
メモリ、コンテキスト、フロー
from geospark.memory import SpatialIntelligence
intel = SpatialIntelligence()
intel.remember_fact("Eiffel Tower is at 48.8584 N, 2.2945 E", source="user")
intel.remember_episode("PM2.5 was 120 in Delhi", importance=0.8, source="tool:air_quality")
intel.recall("Where is the Eiffel Tower?", limit=5) # semantic with Ollama embeddings, keyword match without
intel.find_contradictions()
from geospark.flows import ChatFlowSession, FlowRunner, make_ollama_chat_fn
session = ChatFlowSession(llm_fn=make_ollama_chat_fn("qwen2.5:7b"))
result = session.run("Geocode Valencia, Spain then check its elevation")
if result.flow:
FlowRunner().run(result.flow) # a validated DAG, built one tool call at a time
コマンドライン
geospark distance -37.8136 144.9631 -33.8688 151.2093 # Melbourne → Sydney: 713.86 km
geospark check contains '<geojson A>' '<geojson B>'
geospark geocode "Tokyo Tower, Japan"
geospark elevation -37.8177 144.9691
geospark isochrone -37.8180 144.9691 --minutes 5,10,15 --mode walk
geospark ask "Is Tokyo closer to Seoul or Beijing?"
geospark agent "Find all parks within 2km of Big Ben"
geospark report "Federation Square, Melbourne"
geospark site-select --within "Paris" --near "school,hospital" --facility pharmacy
geospark multi-agent "Analyze Federation Square" --stream
geospark data weather "Melbourne, Australia"
geospark data air-quality "Delhi" # needs OPENAQ_API_KEY
geospark data fires "Amazon Rainforest" # needs FIRMS_MAP_KEY
geospark flow list
geospark flow build "Geocode Valencia then check its elevation" --run
geospark info # versions and optional extras
ベンチマーク結果
GeoSpark Bench v2 では、3 つのプロトコル(質問のみ、段階的な推論(CoT)、GeoSpark の MCP ツールを追加した質問)で 9 つのモデルを実行しました。モデルは、オープンウェイトの 9B 以下の 5 つ(Qwen 2.5 7B、Llama 3.1 8B、Gemma 2 9B、Mistral 7B、Phi-3.5 3.8B)、gpt-oss 20B、および最先端の API 3 つ(GPT-5.4、Claude Sonnet 4.5、Gemini 2.5 Pro)です。信頼区間は Wilson 95% で、呼び出しごとのコスト台帳(約 41 ドル)もチェックインされています。分析の全容は、現在査読中の論文にまとめられています。
数値距離(distance_absolute、モデルごとに n = 80。正解は pyproj.Geod.inv() の測地線距離の 10% 以内):
| モデル | ティア | LLM のみ | 段階的な推論 | + GeoSpark ツール |
|---|---|---|---|---|
| Qwen 2.5 7B | オープン 9B 以下 | 5.0% | 20.0% | 76.2% |
| Llama 3.1 8B | オープン 9B 以下 | 5.0% | 21.2% | 7.5% ᵃ |
| Gemma 2 9B | オープン 9B 以下 | 37.5% ᵇ | 6.2% | — |
| Mistral 7B | オープン 9B 以下 | 2.5% | 2.5% | 0.0% ᵃ |
| Phi-3.5 3.8B | オープン 9B 以下 | 1.2% | 6.2% | — |
| gpt-oss 20B | オープン中規模 | 93.8% | 93.8% | 91.2% |
| GPT-5.4 | 最先端 | 95.0% | 90.0% | 95.0% |
| Claude Sonnet 4.5 | 最先端 | 73.8% | 92.5% | 96.2% |
| Gemini 2.5 Pro | 最先端 | 25.0% | 55.0% | 85.0% |
ᵃ ツールは利用可能でしたが、適切に使われませんでした。Llama は返された座標の km と m を取り違え、Mistral はプレーンテキストで回答しました。ᵇ Gemma の正解は、よく知られた長距離の組み合わせであり、計算ではなく学習データの想起によるものです。— はツールを使った評価が行われていないことを示します。
わかったこと
- ツールは差を縮めます。 LLM のみの場合、正解率は 1~95% にわたります。ツールを使うと、有効なツール呼び出しを行うすべてのモデルの正解率が 76~96% に収まります。Qwen は 71 ポイント向上しました。GPT-5.4 はもともと 95% だったため、向上しませんでした。
- 集計スコアでは偏りが見えなくなります。 トポロジーにおける Qwen の 56% は、「はい」と答えることによるものです。Qwen は
intersects(データが true に偏っている)で 93%、disjointで 3% のスコアです。GPT-5.4 はtouchesで 0% です。GeoSpark を使うと、エンジンが計算するため、すべての述語が 100% になります。 - 段階的なプロンプトは万能ではありません。 改善するモデルもあれば、悪化するモデルもあります。
- 知識を問う質問にツールは不要です。 「2015 年から 2023 年の間にアマゾンでは森林が減少しましたか?」という質問には、どの規模のモデルでも、ツールなしで適切に回答できます。
python -m geospark.bench run を実行して、自分で再実行できます。データセットは python -m geospark.bench.generate_datasets から同一の内容で再生成できます。データの出典についてはデータセットの README を、すべての表についてはベンチマークレポート を参照してください。
セルフホスト
git clone https://github.com/Maz2580/geospark && cd geospark
cp .env.example .env # set GEOSPARK_ENV=production and GEOSPARK_ADMIN_TOKEN
docker compose -f docker-compose.prod.yml up -d
ホスト型 API は、ガードレールを設けたうえで、キーなしでオープンに利用できるよう設計されています。
- 管理者トークンは、運用者向けルート(監査、使用状況、フローの保存)を保護します。LLM ゲートウェイには、このトークンまたは取り消し可能なパートナーキーが必要です。
- 公開ユーザーが選択できるのは、ローカルモデルまたは無料モデルだけなので、有料 API のクレジットが消費されることはありません。
- 各訪問者には、メモリとコンテキストのデモ用に、サイズ制限があり、自動的に期限切れになる専用サンドボックスが割り当てられます。
- レート制限には実際のクライアント IP を使用し、LLM 処理には同時実行数の上限を設けています。
- 入力には上限を設け、監査ログにはハッシュ化した IP を保存します。
すべての状態は GEOSPARK_DATA_DIR の下に保存されます。Nominatim の公開利用制限を回避するには、GEOSPARK_NOMINATIM_URL を独自のサーバーに設定してください。詳細は
deploy/DEPLOY.md と
.env.example をご覧ください。
ロードマップ
- v0.7(リリース済み):検証可能な回答。
- すべての結果に、出典、ライセンス、タイムスタンプ、CRS、単位が含まれます。
- 推測ではなく、「回答できません」という応答を返します。
- ジオコーダーの信頼度や DEM の精度など、データ品質フラグを表示します。
- QGIS にそのまま読み込める GeoJSON FeatureCollection を提供します。
- MCP サーバーを 2026 年仕様に更新し、構造化 GeoJSON 出力とホスト型 HTTP エンドポイントに対応しました。
- v0.8(リリース済み):データと分析機能を拡充。
- DuckDB 経由で Overture Maps の建物、場所、道路をキーなしで利用できます。
- 実際の等時間圏と移動時間行列に対応します。
- 独自の GeoJSON、GeoPackage、GeoParquet を利用できます。
- 再現可能な実行マニフェストに対応します。
- ウェブサイトを再設計し、すべての結果に Proof パネル(出典、注意事項、単位、GeoJSON のダウンロード)を追加しました。また、Ask の回答では、モデルが記憶から入力した座標を使用しません。
- 継続中: 外部ベンチマーク(GPSBench、GeoBenchX)、QGIS ブリッジ、および MCP ツールの拡充。
データの帰属表示
GeoSpark はジオメトリをローカルで計算しますが、公開サービスからデータを取得します。これらのデータをもとに作成した結果には提供元のライセンスが適用されるため、公開する際は提供元を明記してください。
- OpenStreetMap(ジオコーディング、施設、地図タイル):© OpenStreetMap contributors、ODbL
- Copernicus DEM GLO-30(標高):© DLR e.V. 2010-2014 および © Airbus Defence and Space GmbH 2014-2018、欧州連合および ESA による COPERNICUS のもとで提供
- Open-Meteo(天気):CC BY 4.0
- OpenAQ(大気質)および NASA FIRMS(火災):各提供元の利用条件をご確認ください。大気質の結果には各観測所のライセンスが含まれます。
標高の結果には、コピーしてそのまま使える attribution フィールドが含まれます。すべての一覧は
NOTICE にあります。
開発
git clone https://github.com/Maz2580/geospark && cd geospark
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,api,mcp]"
pytest # 1003 tests; offline, never touch ~/.geospark
ruff check geospark tests
貢献を歓迎します。CONTRIBUTING.md をご覧ください。セキュリティ上の問題は、 SECURITY.md に記載の方法で非公開に報告してください。アーキテクチャの概要は docs/ARCHITECTURE.md に、リリースノートは CHANGELOG.md にあります。
引用
GeoSpark または GeoSpark Bench が研究に役立った場合は、引用してください。GitHub の 「このリポジトリを引用」 ボタンでは、CITATION.cff が使用されます。
作者
Mazdak Ghasemi Tootkaboni が作成(University of Melbourne) · ORCID 0000-0001-8084-5270 · GitHub @Maz2580
ライセンス
Apache 2.0。著作権 2024-2026 Mazdak Ghasemi Tootkaboni。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。