はじめに

GitHub Copilot SDK を使った小さな Web アプリを作ってみました。

テーマは、住所を入れると「その場所を買う前にざっくり確認する」ための短いレポートを返す PoC です。

使った構成は次の通りです。

  • フロントはシンプルな Web UI
  • バックエンドは Go
  • ユーザー認証は GitHub OAuth
  • 推論の実行は GitHub Copilot SDK
  • 外部データは mlit-geospatial-mcp

実装してみて面白かったのは、単に SDK をつなぐよりも、

  • Copilot SDK と CLI の関係
  • Docker での動かし方
  • MCP の切り分け方法
  • LLM に「厳密 JSON」を返させる難しさ

の方がずっと学びになったことでした。

この記事では、最終コードの紹介というより、Go + Copilot SDK + MCP を実際に動かした話をまとめます。

作ったもの

入力は住所だけです。

たとえば 福岡県福岡市中央区大濠一丁目 を入れると、

  • 周辺の価格感
  • 洪水系のハザード
  • 用途地域ベースの街の性格
  • 駅距離などから見た生活利便
  • 人口集中地区などから見た将来性

を短いレポートとして返します。

データ自体は国土交通省系 API を直接叩くのではなく、MCP サーバーとして公開されている chirikuuka/mlit-geospatial-mcp を使いました。

本アプリケーションを動かす前に不動産情報ライブラリのAPI Keyを発行してもらう必要があります。 https://www.reinfolib.mlit.go.jp/help/apiManual/

アーキテクチャ

大まかな流れはこうです。

  1. ブラウザで住所を入力する
  2. Go バックエンドが国土地理院 geocoder で緯度経度に変換する
  3. GitHub OAuth で取得したユーザー token を使って Copilot SDK セッションを作る
  4. SDK から mlit-geospatial-mcp/get_multi_api を呼ぶ
  5. 返ってきたデータをもとにレポート本文を生成する

最初は「Copilot SDK を使うなら SDK が全部やってくれるだろう」と思っていましたが、実際にはもう少し現実的な構成理解が必要でした。 リポジトリはこいちら https://github.com/trknhr/copilot-sdk-property-snapshot

学び1: Copilot SDK は CLI とセット

最初にハマったのはここです。

Go の GitHub Copilot SDK を使うなら、SDK が直接モデルを叩くイメージを持ちがちですが、実際には Copilot CLI を土台として使う設計 でした。

つまり役割はだいたいこうです。

  • GitHub OAuth token: 認証主体
  • Copilot SDK: Go からセッションを扱うラッパ
  • Copilot CLI: 実際のランタイム

この理解がないまま進めると、copilot コマンドが見つからずに落ちたときに「SDK の設定が悪いのか?」と迷います。

今回の PoC では最終的に、アプリ用コンテナに Copilot CLI を埋め込む構成にしました。

理由は、ユーザーごとの GitHub OAuth token をそのまま使いたかったからです。

shared な headless CLI service を別コンテナで立てる構成も考えましたが、今回の目的では「誰の Copilot 利用として扱うか」が曖昧になりやすかったので見送りました。

学び2: Docker では「同梱する CLI」と「別 service の CLI」を要件で分ける

Docker 化で考えた選択肢は大きく 2 つでした。

  • app + copilot-cli の 2 service 構成
  • app コンテナ内に Copilot CLI を埋め込む構成

今回のように、

  • Web アプリとして提供したい
  • ただし認証主体はユーザーごとにしたい

というケースでは、app コンテナに CLI を持たせる方が自然でした。

逆に、

  • 社内サービスとして 1 つの実行主体でよい
  • サービス側の token でまとめて動かしたい

なら headless CLI を別 service に分ける方が運用しやすいと思います。

「Docker では別 service に分けるのが正義」と思い込みやすいのですが、Copilot SDK まわりは認証主体の設計の方が先でした。

学び3: MCP の切り分けコマンドを用意すると、LLM とツールの責任がすぐ分かる

途中で UI にはずっと データ不足 が出ていました。

最初は mlit-geospatial-mcp 側が空なのかと思ったのですが、実際にはそうではありませんでした。

切り分けのために、コンテナ内で MCP ツールを直接叩く確認スクリプトを用意しました。

docker compose exec app /opt/mlit-geospatial-mcp/.venv/bin/python /app/scripts/check_mlit_mcp.py \
  --address "東京都千代田区丸の内一丁目9番" \
  --target-apis 3,5,15,26,30 \
  --timeout 30

これで分かったのは、

  • 地価公示・地価調査のポイント: 7 features
  • 用途地域: 1 feature
  • 洪水浸水想定区域: 1 feature
  • 人口集中地区: 1 feature

は返っていることでした。

一方で 駅別乗降客数 は、

cannot access local variable 'target_geom' where it is not associated with a value

という upstream 側エラーで落ちていました。

この時点で、

  • MCP が全滅しているわけではない
  • 少なくとも主要データは取れている
  • データ不足 の原因は別にある

と切れたのはかなり大きかったです。

MCP を使うなら、LLM を介さずツールだけ叩く導線は最初から作っておいた方がいいと感じました。

学び4: 厳密な JSON を返させるよりmarkdown を返させる

今回は正直な話GitHubCopilotのプレミアムリクエストが上限に達していたため、上限にあたってても使えるgpt-4.1を使用しました。そのせいなのかjsonでのデータの返却がかなり難しかったです。

高性能なモデルならもう少しstrictなJSONを返せたと思います。が今回は安く使えることにこだわりました。

まとめ

今回の PoC でいちばん面白かったのは、「Copilot SDK を使って何か作れた」ことよりも、Copilot SDK を組み込んだアプリの設計で何を先に固定すべきかがかなりはっきり見えたことでした。

特に次の 4 つは、今後も再利用できる知見だと思っています。

  • Copilot SDK は CLI を含めた実行構成として設計する
  • Docker 構成は認証主体から逆算する
  • MCP はツール単体で直接確認できるようにしておく
  • 安いLLMは strict JSON より markdown の方が速くて壊れにくい

GitHub Copilot SDK は、ユーザーのCopilotの枠を使えるのでサービス提供側のLLMの使用量を気にすることがなくとても経済的です。

今回の題材は不動産の住所チェックでしたが、同じ考え方は社内ツールや調査支援にもそのまま持ち込めそうです。

参考リンク


この記事は Zenn にも転載しています。