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 3 ― 解析結果をresult_idで管理し、処理履歴を追跡する

0
Posted at

FastMCP 3 + Docker でGIS MCP Serverを作る ― Step 3 ― 解析結果をresult_idで管理し、処理履歴を追跡する

はじめに

Step 1では、GeoPackage / GeoJSON / GeoParquetを検査してDataset Catalogへ登録し、
dataset_id / versionで参照できるところまで作りました。

Step 2では、そのCatalogをFastMCPから読み取れるようにし、
Tool / Resource、Streamable HTTP、in-memory test、HTTP smoke testまで確認しました。

Step 3では、解析結果の管理へ進みます。

これまでは入力側にdataset_idがありましたが、出力側にはまだ共通の識別子がありません。
今後Buffer、Intersection、空間検索、Raster処理を増やしていくと、
「どのファイルがどの処理結果なのか」をファイル名だけで追うのは難しくなります。

そこで、解析結果をresult_idで扱うResult Storeを追加します。

20260911_g21.png

Step 3の目的は、GIS演算を増やすことではありません。

結果を安全に保存し、どのDatasetと条件から作られたのかを後から追える土台を先に作ることです。


1. Step 2から引き継ぐもの

Step 3はStep 2を作り直しません。

次はそのまま残します。

  • Dataset / DatasetVersion
  • Dataset Catalog
  • CatalogService
  • ControlRepository
  • VectorInspector
  • FastMCP 3.4.7
  • 既存の4つのDataset系Tool
  • gis://catalog
  • gis://dataset/{dataset_id}
  • Streamable HTTP /mcp
  • graceful shutdownを含むin-process HTTP test
  • Docker container間HTTP smoke test

Step 2で確認した境界は維持します。

MCP
 ↓
Application Service
 ↓
Port
 ↓
Adapter

Step 3でも、MCP Toolの中へDuckDB SQLやfilesystem操作を書きません。


2. Step 3で追加するもの

今回追加する主な構成は次です。

Operation
Result
Artifact
Provenance

ResultService
ResultRepository
ArtifactStore
DuckDBResultRepository
WorkspaceArtifactStore

DB migrationは、Step 1の001_dataset_catalog.sqlを書き換えず、
新しく002_result_store.sqlを追加します。

migrations/
├─ 001_dataset_catalog.sql
└─ 002_result_store.sql

公開済みmigrationを修正しないのは、既存DBのschema履歴を壊さないためです。

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


3. ファイル名ではなくresult_idを返す

例えば将来500m Bufferを作ったときに、

buffer_500m.geojson

だけを返す構成にはしません。

Server側でresult_idを発行します。

res_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

MCP Clientへ返すのは、物理パスではなく次のような情報です。

{
  "result_id": "res_xxx",
  "operation_type": "snapshot",
  "status": "READY",
  "input_dataset_id": "landslide_area",
  "input_dataset_version": 1,
  "feature_count": 3,
  "crs": "EPSG:6677"
}

これなら次の処理で必要なのはresult_idです。

DatasetVersion
    ↓
処理A
    ↓
result_id=A
    ↓
処理B
    ↓
result_id=B

LLM側がcontainer内部の保存先を覚える必要はありません。


4. Resultには状態を持たせる

Resultは、いきなりREADYにはしません。

20260911_g22.png

Step 3では、正常系を次の順序で固定します。

PENDING
  ↓
PROCESSING
  ↓
VALIDATING
  ↓
READY

READYは「DBへ行がある」という意味ではありません。

成果物を再オープンして検査し、ArtifactとProvenanceまで記録できた状態をREADYとします。


5. なぜDB transactionだけでは足りないのか

Result Storeではcontrol.duckdbを使いますが、GIS成果物そのものはfilesystem上にあります。

control.duckdb
    +
GeoPackage / GeoJSON / GeoParquet

DBだけならtransactionでまとめられますが、filesystemのrenameとDuckDB transactionを
1つのACID transactionにはできません。

そこで確定順序を固定します。

1. result_idを発行
2. control.duckdbへPENDINGを登録
3. workspace/work/<result_id>/へ一時出力
4. GIS成果物を再オープン
5. metadata / checksumを検査
6. workspace/results/<result_id>/へ確定移動
7. Artifact / Provenance / READYをDB transactionで確定

図にすると次の流れです。

20260911_g23.png

この順序なら、途中でprocessが停止しても、DB状態とworkspaceの両方を見て復旧できます。


6. Result Store用migration

002_result_store.sqlでは4つのtableを追加します。

meta.operations
meta.results
meta.artifacts
meta.provenance

関係は次の通りです。

20260911_g24.png

Resultは入力DatasetVersionとOperationを参照します。

Artifactは成果物の実体を管理します。

artifact_id
result_id
artifact_type
storage_path
file_size
checksum_sha256
created_at

storage_pathはServer内部情報なので、MCP responseには出しません。

Provenanceには再現に必要な情報を残します。

input_dataset_id
input_dataset_version
input_checksum
parameters
engine
engine_version
application_version
request_fingerprint
artifact_checksum
created_at

7. request fingerprintを残す

同じ入力と同じ条件のrequestが繰り返されることもあります。

Step 3では、次を正規化してSHA-256を計算します。

dataset_id
Dataset version
input checksum
operation type
parameters
payload = {
    "dataset_id": dataset.dataset_id,
    "dataset_version": dataset.version,
    "input_checksum": dataset.checksum_sha256,
    "operation": operation_type,
    "parameters": parameters,
}

MVPではfingerprintが同じだからといって、過去Resultを勝手に返して処理を省略しません。

まずは「同じrequestだった」と追跡できる情報を残すところまでにします。


8. Artifactはworkspace外へ出さない

成果物の保存先はWorkspaceArtifactStoreへまとめます。

/workspace
├─ work/
├─ results/
└─ quarantine/

一時ファイルは、

/workspace/work/<result_id>/

へ作ります。

検査に通った成果物だけ、

/workspace/results/<result_id>/

へ移します。

検査に失敗したものは、

/workspace/quarantine/<result_id>/

へ隔離します。

任意のabsolute pathをMCP Toolから受け取る構成にはしません。


9. 成果物は保存後にもう一度開く

「ファイルを書けた」だけでは正常Resultとはしません。

Step 3では、一時出力した成果物をVectorInspectorでもう一度開きます。

確認するのは、

CRS
geometry type
feature count
bbox
columns
SHA-256

です。

例えばcopy中にファイルが壊れた場合、DBへREADYを記録する前に止められます。


10. Step 3ではsnapshotを検証operationに使う

ここではまだBufferやIntersectionを実装しません。

それらは次のDuckDB Spatial Query EngineのStepで追加します。

Step 3では、登録済みDatasetVersionをcopyするsnapshotを用意しました。

DatasetVersion
    ↓
snapshot
    ↓
work
    ↓
再オープン検査
    ↓
results
    ↓
Result READY

GIS演算を混ぜずに、Result Storeのライフサイクルだけを単独で検証できます。

後からBufferを追加するときも、成果物を同じResultServiceへ渡せます。


11. ResultServiceへ処理順序を集める

処理の中心はResultServiceです。

MCP / CLI
   ↓
ResultService
   ├─ CatalogService
   ├─ ResultRepository
   └─ ArtifactStore

ServiceからDuckDBを直接呼びません。

filesystem操作もArtifactStore経由です。

これにより、

処理順序             ResultService
DB固有処理           DuckDBResultRepository
filesystem固有処理   WorkspaceArtifactStore

へ分離できます。


12. Failure Injectionを入れる

Result Storeは正常系だけ確認しても不十分です。

意図的に途中で失敗させます。

Step 3では次を確認します。

after_stage
    workへcopy後に失敗

during_validation
    VALIDATING中に失敗

after_promote
    resultsへ確定移動した直後に失敗

特にafter_promoteは重要です。

filesystem
  resultsへ移動済み

DB
  ResultはまだVALIDATING

という、filesystemとDBの間に隙間がある状態を意図的に作れます。


13. Recoveryを実装する

起動後や運用時には、未完了Resultを調べます。

PENDING
PROCESSING
VALIDATING
FAILED

work側に成果物が残っていれば、再検査して続行します。

results側へ既に移動している場合も、実ファイルをもう一度開いてchecksumを計算し、
Artifact / Provenanceを確定します。

一方、QUARANTINEDは自動Recoveryしません。

検証失敗の原因を確認せず自動でREADYへ進めないためです。


14. Result用CLIを追加する

MCP Toolへ書込み機能を増やす前に、Result Store自体をComposeから確認できるようCLIを用意しました。

result snapshot
result list
result info
result recover

例えば、Catalogへ登録済みのDatasetをsnapshotします。

docker compose --profile demo run --rm result snapshot \
  --dataset-id landslide_area \
  --version 1

一覧は、

docker compose --profile demo run --rm result list

です。

未完了Resultの復旧は、

docker compose --profile demo run --rm result recover

で確認できます。


15. MCPへResult参照を追加する

Step 3でも、MCPからのResult作成Toolはまだ追加しません。

まずread-onlyで公開します。

Tool:

gis_list_results
gis_describe_result

Resource:

gis://results
gis://result/{result_id}

Dataset系を含めると、Step 3のToolは6個です。

gis_list_datasets
gis_describe_dataset
gis_list_versions
gis_runtime_status
gis_list_results
gis_describe_result

Result詳細にはArtifactとProvenanceを含めますが、storage_pathは返しません。


16. 巨大GeometryをMCP responseへ載せない

Step 3のResult modelには、成果物の全Geometryを持たせません。

返すのは概要です。

result_id
status
operation
feature_count
crs
bbox
checksum
provenance

実ファイルはResult Storeへ残します。

この設計にしておけば、将来50万Polygonを抽出しても、そのGeoJSON全体をMCP contextへ
載せずに済みます。


17. named volumeを別containerから読み直す

Result Storeはprocess memoryではなく、永続化されている必要があります。

そこでStep 3では2つのsmoke serviceを用意しました。

1つ目のcontainerでDatasetとResultを作ります。

docker compose --profile persistence run --rm result-persistence-write

成功時は、

RESULT_PERSISTENCE_WRITE=PASS

です。

次に別containerから同じnamed volumeを読みます。

docker compose --profile persistence run --rm result-persistence-read

ここでは、

result_id
Result READY
Artifact存在
Artifact SHA-256
Provenance artifact checksum

を再確認します。

成功時は、

RESULT_PERSISTENCE_READ=PASS

です。

この2つは意図的に別serviceにしています。

同じPython processのmemoryが残っているだけではないことを確認するためです。


18. Step 1 / Step 2の回帰テストは残す

Step 3のテストへ置き換えるのではなく、これまでの確認へ積み上げます。

Step 0
  static / Ruff / mypy / pytest
        +
Step 1
  Dataset Catalog / Vector / migration
        +
Step 2
  FastMCP / HTTP / graceful shutdown
        +
Step 3
  Result / Artifact / Provenance / Recovery

002_result_store.sqlを追加したので、schema versionは2になります。

Step 1のDataset登録処理が引き続き動くことも毎回確認します。


19. Step 3の実行手順

19.1 lock fileを作る

cp .env.example .env

docker compose --profile maintenance run --rm lock

生成後、.envを、

UV_FROZEN=1

へ変更します。

19.2 test imageをclean buildする

docker compose --profile test build --no-cache test

build中にも、

uv pip check
SOURCE_HYGIENE
TEST_IMAGE_FILES
ruff format --check
ruff check
mypy

を実行します。

19.3 Step 3全テスト

docker compose --profile test run --rm test

成功時は、

STEP3_COMPLETION=PASS

です。

19.4 named volumeの再読込

docker compose --profile persistence run --rm result-persistence-write

docker compose --profile persistence run --rm result-persistence-read
RESULT_PERSISTENCE_WRITE=PASS
RESULT_PERSISTENCE_READ=PASS

を確認します。

19.5 MCP HTTP

docker compose --profile mcp up -d --build mcp

docker compose --profile smoke run --rm smoke

成功時は、

MCP_HTTP_SMOKE=PASS

です。


20. Step 3の完了条件

Step 3は次の4つが揃った場合だけ完了とします。

STEP3_COMPLETION=PASS
RESULT_PERSISTENCE_WRITE=PASS
RESULT_PERSISTENCE_READ=PASS
MCP_HTTP_SMOKE=PASS

確認する内容は、単にResult tableへINSERTできることではありません。

状態遷移             PASS
Artifact再オープン   PASS
SHA-256              PASS
Failure Injection    PASS
Recovery             PASS
別container再読込    PASS
MCP Result参照       PASS
Step 1/2回帰         PASS

までをStep 3の範囲にします。


21. Step 3でやらないこと

Stepの境界を崩さないため、次はまだ追加しません。

  • DuckDB Spatialによる大規模空間検索
  • Buffer
  • Intersection
  • Raster処理
  • 任意SQL
  • 任意file pathを受け取るMCP Tool
  • 長時間job / worker
  • PostGIS

Result Storeを先に完成させ、次の解析Engineを載せられる状態にします。


22. 次のStep

次はDuckDB Spatial Query Engineへ進みます。

Dataset Catalog
      ↓
DuckDB Spatial Query
      ↓
ResultService
      ↓
Result Store
      ↓
result_id

Step 3でResultの保存と追跡を固定したので、Step 4では空間検索そのものに集中できます。


参考リンク


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?