0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastMCP 3 + Docker でGIS MCP Serverを作る ― Step 0 Docker Composeで開発環境とテスト基盤を整える

0
Posted at

FastMCP 3 + Docker でGIS MCP Serverを作る ― Step 0 Docker Composeで開発環境とテスト基盤を整える

はじめに

前回の記事から、FastMCP 3を使ってGIS向けのMCP Serverを作っていきます。

最終的には、MCP ClientからGISデータを指定し、ベクトル処理、空間検索、ラスタ解析を実行し、その結果や処理履歴まで追える構成を目指します。

ただし、最初からGeoPandas、DuckDB Spatial、Rasterio、FastMCPのToolを全部入れると、問題が起きたときに原因を切り分けにくくなります。

そこで、最初のStep 0ではGIS処理そのものはまだ実装しません。

Docker Composeで同じ環境を再現できること、ファイルの置き場所や設定方法をそろえること、テストを毎回同じ手順で実行できることを先に整えます。

この土台を作っておけば、Step 1以降で機能を追加したときも、「Dockerの問題なのか」「GIS処理の問題なのか」「MCPの問題なのか」を切り分けやすくなります。


1. 全体の開発ステップ

GIS MCP Serverは、次の順番で作っていきます。

Step 主な内容 開発ステップ
Step 0 Docker Compose、設定、フォルダー構成、テスト基盤
Step 1 control.duckdb、Dataset Catalog、ベクトルGIS処理
Step 2 FastMCP Tool / Resource、HTTP接続
Step 3 解析結果の保存、Artifact、Provenance
Step 4 DuckDB Spatialによる空間検索・集計
Step 5 RasterioによるDEM / GeoTIFF / COG処理
Step 6 複数のGIS処理をまとめた業務ワークフロー
Step 7 配布用Docker Compose、通信方法の整理
Step 8 認証、アクセス制御、Middleware
Step 9 長時間処理、Worker、再実行
Step 10 PostgreSQL / PostGISへの拡張
Step 11 OpenLayers / PMTilesとの連携

Step 0は、この先のすべてのStepで共通して使う開発環境を作る段階です。

Step 0
  ↓
Docker Composeで同じ環境を作れる
  ↓
テストを1つの手順で実行できる
  ↓
Step 1以降の機能を少しずつ追加する

2. Step 0で作るもの

Step 0では、次の範囲を実装します。

Step 0
├─ Dockerfile
├─ compose.yaml
├─ Python / package versionの固定
├─ /workspace のフォルダー構成
├─ Docker volumeの初期化
├─ 設定値の読み込み
├─ Pydanticによる内部データの形
├─ Ruff
├─ mypy
├─ pytest
├─ coverage
├─ 構成ルールのテスト
├─ uv.lock生成用service
└─ CIの基本形

反対に、次の機能はまだ入れません。

control.duckdb
Dataset Catalog
GeoPackage / GeoJSON読込
GeoPandas
Pyogrio
Shapely
PyProj
DuckDB Spatial
Rasterio
FastMCP Tool / Resource
FastMCP HTTP Server
Result / Provenance

この段階で機能を入れすぎないのは、後で問題が見つかったときに、原因となった変更を追いやすくするためです。


3. Step 0の構成

Step 0のDocker Composeは、3つのserviceだけにしています。

20260818_f01.png

この時点ではMCP Serverを常駐させません。

Step 0で確認したいのは、次の部分です。

Docker Composeでbuildできる
        ↓
必要なversionがそろっている
        ↓
workspaceへ安全に書き込める
        ↓
コードの形式・型・Unit Testを実行できる
        ↓
Step 1へ進める

4. ホスト側へPython環境を作らない

このシリーズでは、標準の実行手順をDocker Composeにそろえます。

Host OS
  └─ Docker Engine / Docker Desktop
       └─ Docker Compose
            └─ Linuxコンテナ

Windows、Ubuntu、macOSで別々のPython環境を作るのではなく、Pythonやライブラリはコンテナ側で管理します。

ホスト側で必要になるのは、基本的にDocker EngineまたはDocker DesktopとDocker Composeです。

この方法にすると、次の違いをかなり減らせます。

  • Pythonのversion
  • packageのversion
  • 実行するcommand
  • コンテナ内部のpath
  • 後から追加するGDALやDuckDB Spatialの環境

Dockerを使ってもOS差が完全になくなるわけではありませんが、Python環境を各PCで個別に作るより再現しやすくなります。


5. 使用するversion

Step 0では、主要なversionを明示しています。

項目 version
Python 3.13.15
uv 0.12.5
FastMCP 3.4.7
Pydantic 2.13.4
pytest 9.1.1
pytest-cov 7.1.0
Ruff 0.16.3
mypy 2.3.0

FastMCPはStep 2からToolやResourceとして使います。

Step 0では、採用するversionをコンテナに入れて、同じ環境を再現できることだけ確認します。

GIS用のライブラリはまだ追加しません。

duckdb
geopandas
pyogrio
shapely
pyproj
rasterio

これらは必要になるStepで追加します。


6. フォルダー構成

実装は次のように分けています。

参考のために、ZIPファイルを置いておきますので、ダウンロードして活用してください。

gis_mcp_server_step0_r003/
├─ Dockerfile
├─ compose.yaml
├─ pyproject.toml
├─ .env.example
├─ .dockerignore
├─ .gitignore
├─ VERSION
│
├─ src/
│  └─ gis_mcp/
│     ├─ __init__.py
│     ├─ config.py
│     ├─ contracts.py
│     └─ services/
│        ├─ __init__.py
│        └─ runtime_service.py
│
├─ scripts/
│  ├─ init_workspace.py
│  ├─ check_runtime.py
│  ├─ validate_workspace.py
│  └─ check_step_completion.py
│
├─ tests/
│  ├─ conftest.py
│  ├─ unit/
│  └─ architecture/
│
├─ docs/
│  ├─ qiita_step0.md
│  └─ step0_design.md
│
├─ STEP0_COMPLETION.md
└─ STEP0_VALIDATION.md

まだ使わないengines/repositories/などは作っていません。

必要になる前にフォルダーだけ増やすと、どこへ処理を書くべきか分かりにくくなるためです。


7. Docker Composeのservice

workspace-init

共有volumeに必要なフォルダーを作り、後から実行する一般ユーザーが書き込めるようにします。

このserviceは初期化が終わると停止します。

test

Step 0の確認をまとめて行います。

Python構文確認
version確認
workspace書込み確認
Ruff
mypy
pytest
coverage

lock

ホスト側へuvを入れず、uv.lockを作るために使います。

docker compose --profile maintenance run --rm lock

8. 共有volumeはroot実行に頼らない

後のStepでは、GISデータや解析結果をDocker volumeへ保存します。

Applicationを常にrootで動かせば権限エラーは避けやすくなりますが、その方法にはしません。

実際に処理を動かすユーザーはUID/GID 10001で固定します。

RUN groupadd --gid 10001 app \
    && useradd --uid 10001 --gid app --create-home --shell /usr/sbin/nologin app

初期化だけworkspace-initがrootで実行し、その後のtest serviceは一般ユーザーで書込み確認を行います。

20260818_f01.png

init_workspace.pyでは、フォルダー一覧を1か所から取得しています。

def main() -> None:
    """共有workspaceを作成し、実行ユーザーが利用できる状態へ整える。"""
    root = Path(os.getenv("GIS_MCP_WORKSPACE", "/workspace")).resolve()
    uid = _required_int("GIS_MCP_APP_UID")
    gid = _required_int("GIS_MCP_APP_GID")

    paths = WorkspacePaths.from_root(root)
    paths.ensure()

    # フォルダー一覧はWorkspacePathsでまとめて管理する。
    # スクリプトごとに同じ一覧を持たせると、追加時に修正漏れが起きやすいため。
    for path in (paths.root, *paths.managed_directories):
        os.chown(path, uid, gid)

9. /workspaceの構成を先に決める

コンテナ内では、GIS関連のファイルを置く場所を/workspaceに統一します。

/workspace/
├─ control/
├─ datasets/
├─ work/
├─ results/
├─ previews/
└─ quarantine/

Step 0ではフォルダーを用意するだけです。

control.duckdbやGISデータはまだ作りません。

Python側では、WorkspacePathsにまとめています。

@dataclass(frozen=True, slots=True)
class WorkspacePaths:
    """GIS MCP Serverで共通して使うworkspaceのパスをまとめる。"""

    root: Path
    control: Path
    datasets: Path
    work: Path
    results: Path
    previews: Path
    quarantine: Path

フォルダー名を各処理へ直接書かず、1つの定義から使うことで、後から構成を変えたときの修正漏れを減らします。


10. テスト用と実際の共有volumeを分ける

通常のUnit Testでは、将来の運用データが入るvolumeを使いません。

test serviceでは、テスト用の/workspaceをtmpfsにしています。

/workspace
  └─ テスト専用

/runtime-workspace
  └─ 共有volumeの書込み確認専用

こうしておけば、Step 1以降でDatasetやResultが増えても、Unit Testが共有データを誤って変更する可能性を下げられます。


11. 設定値は1か所で確認する

AppConfigでは、環境変数を読み込んだ直後に基本的なチェックを行います。

@dataclass(frozen=True, slots=True)
class AppConfig:
    """Step 0の確認処理で使う基本設定。"""

    workspace: WorkspacePaths
    log_level: str

例えば、workspaceには絶対pathだけを使います。

workspace_root = Path(os.getenv("GIS_MCP_WORKSPACE", "/workspace"))
if not workspace_root.is_absolute():
    raise ConfigurationError("GIS_MCP_WORKSPACE must be an absolute path")

相対pathを許すと、実行した場所によって保存先が変わるためです。

ログレベルも、起動時に使える値か確認します。

設定ミスを処理の途中で見つけるのではなく、できるだけ早く止めるようにしています。


12. Pydanticは最小限から使う

Step 0では、内部状態を表すFoundationStatusだけを用意しています。

class FoundationStatus(BaseModel):
    """Docker Composeとテスト環境の状態を表すデータ。"""

    model_config = ConfigDict(extra="forbid", frozen=True)

    component: Literal["gis-mcp-foundation"] = "gis-mcp-foundation"
    step: Literal["step0"] = "step0"
    state: Literal["READY", "DEGRADED"]
    workspace_writable: bool
    python_version: str
    fastmcp_version: str
    notes: tuple[str, ...] = ()

extra="forbid"にしているので、定義していない項目が紛れ込んだ場合はエラーになります。

frozen=Trueにしているので、作成後に値を勝手に書き換えることもできません。

DatasetやMCP Tool用のデータ形式は、それぞれのStepで追加します。


13. 外部ライブラリを使う場所を増やしすぎない

RuntimeServiceは、workspaceの準備状態を確認する小さなPythonクラスです。

FastMCPやDuckDB、GeoPandasには直接依存させていません。

class RuntimeService:
    """Step 0で必要な実行環境とworkspaceの状態を確認する。"""

このように、外部ライブラリを使わなくても書ける処理は普通のPythonとして残しておくと、Unit Testを短時間で実行できます。

tests/architecture/では、この構成ルールが崩れていないか確認します。

tests/architecture/で行う構成ルールのテストは難しい仕組みではなく、

まだ使わないライブラリを、Step 0のコードから誤ってimportしていないか

を機械的に確認するテストです。


14. バージョンも実際のコンテナ内で確認する

Dockerfileにversionを書いてあるだけではなく、実際にコンテナ内から確認します。

check_runtime.pyでは、次を検査します。

EXPECTED_PYTHON = "3.13.15"
EXPECTED_UV = "0.12.5"
EXPECTED_PACKAGES = {
    "fastmcp": "3.4.7",
    "pydantic": "2.13.4",
}

また、Step 0ではまだ使わないGISライブラリが入っていないことも確認します。

FORBIDDEN_STEP0_PACKAGES = (
    "duckdb",
    "geopandas",
    "pyogrio",
    "shapely",
    "pyproj",
    "rasterio",
)

必要なStepになったら、この確認内容も変更します。


15. Step 0の確認を1つのcommandへまとめる

自動で確認できる項目は、scripts/check_step_completion.pyから順番に実行します。

Python構文確認
    ↓
version確認
    ↓
テスト用workspace確認
    ↓
共有volume書込み確認
    ↓
Ruff format
    ↓
Ruff lint
    ↓
mypy
    ↓
pytest + coverage

どこか1つでも失敗した場合は、その場で終了します。

全部通ると、最後に次を表示します。

STEP0_COMPLETION=PASS

ホスト側から実行するcommandは1つです。

docker compose --profile test run --rm test

16. 正常な場合だけでなく、設定ミスもテストする

Step 0のUnit Testでは、正常に動くことだけを確認していません。

例えば次もテストします。

  • workspaceに相対pathを指定した場合
  • log levelに使えない値を指定した場合
  • Pydanticに未定義の項目を渡した場合
  • 作成済みのPydanticデータを書き換えようとした場合
  • 一部のフォルダーへ書き込めない場合
  • packageが見つからない場合
  • 存在しないフォルダーを誤って書込み可能と判定しないこと

小さな設定ミスを早い段階で見つけられるようにしておくと、GIS機能を追加した後の調査が楽になります。


17. coverageは90%以上を基準にする

Step 0では、branch coverageを90%以上にしています。

[tool.coverage.run]
branch = true
source = ["gis_mcp"]

[tool.coverage.report]
fail_under = 90

ただし、coverageの数字を上げること自体が目的ではありません。

重要なのは、設定値の異常やworkspaceの権限など、後で問題になりやすい分岐を実際にテストすることです。


18. uv.lockもDocker Composeから作る

ホスト側へuvを入れないため、lock fileもDocker Composeから作ります。

docker compose --profile maintenance run --rm lock

最初は.envを次の状態にします。

UV_FROZEN=0

uv.lockを作成してGitへ追加した後は、

UV_FROZEN=1

へ変更します。

その後は、pyproject.tomluv.lockが一致しない状態でbuildを通さないようにします。


19. 実行手順

19.1 ZIPを展開

unzip gis_mcp_server_step0_r003.zip
cd gis_mcp_server_step0_r003

19.2 .envを作成

Linux / macOS:

cp .env.example .env

PowerShell:

Copy-Item .env.example .env

LinuxでUID/GIDが1000以外の場合は、必要に応じて.envHOST_UIDHOST_GIDを変更します。

19.3 uv.lockを作成

docker compose --profile maintenance run --rm lock

uv.lockをGitへ追加した後、.envを次へ変更します。

UV_FROZEN=1

19.4 Composeファイルを確認

docker compose config

19.5 test imageを最初からbuild

docker compose build --no-cache test

19.6 Step 0のテストを実行

docker compose --profile test run --rm test

最後に、

STEP0_COMPLETION=PASS

が表示されれば、自動確認は完了です。


20. Step 0の完了条件

Step 1へ進む前に、次を確認します。

  • docker compose config が成功する
  • test imageを --no-cache でbuildできる
  • STEP0_COMPLETION=PASS が表示される
  • Python / uv / FastMCP / Pydanticのversionが一致する
  • Ruffが成功する
  • mypyが成功する
  • Unit Testが成功する
  • 構成ルールのテストが成功する
  • branch coverageが90%以上になる
  • テスト用workspaceへ書き込める
  • 共有volumeへ一般ユーザーで書き込める
  • uv.lockをGitへ追加している
  • UV_FROZEN=1でbuildできる
  • Step 1以降で使うGISライブラリがまだ入っていない

詳しい確認項目はSTEP0_COMPLETION.mdにまとめています。


21. Step 0で作ったテストは次のStepでも使う

Step 1へ進んでも、Step 0で作ったRuff、mypy、Unit Testなどは残します。

Step 0の確認
    +
Step 1で追加する確認
    +
Step 2で追加する確認
    +
...

機能が増えるたびに、必要なテストを追加します。

一方で、Step 0だけで必要だった「DuckDBがまだ入っていないこと」のような確認は、DuckDBを正式に導入した後は内容を変えます。

例えばStep 1では、

DuckDBを使ってはいけない

ではなく、

DuckDBを使う場所を決めた範囲に限定できている

という確認へ変えます。


22. Step 0を先に作る理由

Step 0には、まだGISらしい画面や空間解析はありません。

それでも、ここを先に作る理由は単純です。

これから、

DuckDB
GeoPandas
Pyogrio
Shapely
PyProj
FastMCP
DuckDB Spatial
Rasterio
GDAL

と依存するものが増えていきます。

そのたびに、Python環境や実行方法、保存場所まで変わってしまうと、GIS機能を作るより環境調査に時間を取られます。

そこで最初に、

Docker Compose
フォルダー構成
設定方法
テスト方法
version管理

をそろえておきます。

Step 0は、後の開発を速く進めるための準備です。


23. 次はStep 1へ

Step 1では、初めてGISデータを登録して読み込むところへ進みます。

20260818_f01.png

追加する予定の主な機能は次です。

  • DuckDB
  • control.duckdb
  • DBの初期化・更新方法
  • Dataset / DatasetVersion
  • Dataset Catalog
  • GeoPackage / GeoJSON
  • GeoPandas / Pyogrio
  • Shapely / PyProj
  • CRS / Geometryの確認
  • 小さなGISテストデータ

Step 0で作ったDocker Composeとテスト基盤の上へ、これらを順番に追加していきます。


参考資料


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?