MCP サーバー 機能設計 — 全体像
| 項目 | 内容 |
|---|---|
| ステータス | 🟡 設計中 |
| 関連案件 | #13 Mappy MCP サーバー新設 |
| GitLab Issue | Mappy #58 |
| 親ドキュメント | (本ドキュメントが MCP 機能設計の親) |
| 子ドキュメント | ツールカタログ / 認証・テナンシ / 書込安全装置 |
1. 本ドキュメントの目的
MCP サーバーの機能設計の全体像を定義する。実装スタック(Python / TypeScript)の比較、コンポーネント分担、リクエストフロー、トランスポート仕様、エラーハンドリング方針をここで確定し、後続の設計書(ツールカタログ・認証・書込安全装置・監査ログ等)の前提とする。
2. システム全体像
2.1 アーキテクチャ図
2.2 コンポーネント責務
| コンポーネント | 責務 | 配置 |
|---|---|---|
| Streamable HTTP エンドポイント | MCP プロトコルの受付、長時間接続の維持、メッセージ・イベントの送受信 | MCP Server プロセス |
| OAuth 2.1 認可サーバー | クライアント登録・認可コード発行・トークン発行・scope 検証 | MCP Server プロセス(または独立) |
| Tool Dispatcher | tool 名から実装関数へのルーティング、入力バリデーション、scope 検査、結果整形 | MCP Server プロセス |
| Audit Logger | 全 tool 呼び出し・認可失敗・例外を監査ログ DB へ記録 | MCP Server プロセス |
| Laravel API(MCP用) | MCP 専用エンドポイントを /api/mcp/* 配下に追加。既存のサービス層・モデルを呼び出し | Laravel アプリ内 |
| Mappy DB | データ実体。MCP からは Laravel API 経由のみアクセス | 既存 |
| GBP API | Google Business Profile への中継。Laravel API がトークン管理 | 外部 |
| Jobs Queue | 書込系 tool の非同期処理。既存 jobs テーブルを利用 | 既存 |
DB 直参照は禁止
MCP Server から Mappy DB を直接 SELECT/UPDATE しない。全てのデータアクセスは Laravel API 経由とする。理由:
- テナンシ判定・権限制御を二重実装しない
- 既存のバリデーション・サービスロジック・監査機構を流用
- スキーマ変更時の影響範囲を最小化
3. リクエストフロー
3.1 初回接続・認証フロー(OAuth 2.1 + PKCE)
詳細は 認証・テナンシ設計 を参照。
3.2 読取系 tool 呼び出しフロー(同期)
3.3 書込系 tool 呼び出しフロー(非同期)
詳細は 書込安全装置 を参照。
4. トランスポート — Streamable HTTP
4.1 採用理由
- MCP 2025-03-26 仕様の正式トランスポート(旧 SSE は段階的廃止)
- 長時間接続を維持しつつ HTTP/2 / HTTP/3 と相性が良い
- 標準的なロードバランサ・WAF・CDN で扱える
- ChatGPT Connectors / Claude Desktop の両方が公式対応
4.2 エンドポイント
| パス | メソッド | 用途 |
|---|---|---|
/sse | GET | MCP セッション確立(イベントストリーム) |
/messages | POST | クライアントから MCP メッセージ送信 |
/oauth/authorize | GET | OAuth 認可エンドポイント |
/oauth/token | POST | アクセストークン発行 |
/oauth/register | POST | 動的クライアント登録 (RFC 7591) |
/.well-known/oauth-authorization-server | GET | OAuth メタデータ |
/health | GET | ヘルスチェック |
4.3 セッション管理
- 1 接続 = 1 セッション
- セッション ID はサーバーから発行、
Mcp-Session-Idヘッダーで返却 - 接続切断時はセッション破棄、再接続時は新セッション
- アイドルタイムアウト: 30 分
- 強制再認証: refresh_token 期限切れ時
5. 実装スタック詳細比較
提案書では両案併記としているが、本設計書では選定軸を明確化する。
5.1 案A: Python (FastMCP)
| 項目 | 内容 |
|---|---|
| MCP ライブラリ | FastMCP 2.x |
| 推奨 Python バージョン | 3.11+ |
| HTTP サーバー | Uvicorn (ASGI) |
| OAuth 2.1 実装 | Authlib または独自実装 |
| 入出力バリデーション | Pydantic v2 |
| Laravel API クライアント | httpx (非同期) |
| ロギング | structlog + JSON 出力 |
| テスト | pytest + pytest-asyncio |
| 依存管理 | Poetry または uv |
サンプルコード(tool 定義):
python
from fastmcp import FastMCP
from pydantic import BaseModel, Field
mcp = FastMCP("Mappy MCP")
class LocationListInput(BaseModel):
user_id: int = Field(..., description="対象ユーザー ID")
group_id: int | None = Field(None, description="グループで絞り込み")
limit: int = Field(50, ge=1, le=100)
@mcp.tool(scopes=["mappy:location:read"])
async def location_list(input: LocationListInput) -> list[dict]:
"""ユーザーがアクセス可能なロケーション一覧を返す"""
resp = await laravel_api.get(
"/api/mcp/locations",
params=input.model_dump(exclude_none=True),
)
return resp.json()5.2 案B: TypeScript (公式 SDK)
| 項目 | 内容 |
|---|---|
| MCP ライブラリ | @modelcontextprotocol/sdk |
| 推奨 Node バージョン | 20 LTS |
| HTTP サーバー | Express / Hono / Fastify |
| OAuth 2.1 実装 | oauth4webapi / panva/jose |
| 入出力バリデーション | Zod |
| Laravel API クライアント | undici / fetch |
| ロギング | pino |
| テスト | vitest |
| 依存管理 | pnpm または npm |
サンプルコード(tool 定義):
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({ name: "Mappy MCP", version: "1.0.0" });
const LocationListInput = z.object({
user_id: z.number().int().describe("対象ユーザー ID"),
group_id: z.number().int().optional(),
limit: z.number().int().min(1).max(100).default(50),
});
server.tool(
"location.list",
"ユーザーがアクセス可能なロケーション一覧を返す",
LocationListInput.shape,
async (input) => {
const resp = await fetch(
`${LARAVEL_API}/api/mcp/locations?${new URLSearchParams(input)}`,
);
return { content: [{ type: "text", text: await resp.text() }] };
},
);5.3 比較表
| 評価軸 | Python (FastMCP) | TypeScript (公式 SDK) | 備考 |
|---|---|---|---|
| 公式サポート | コミュニティ製(人気) | 公式 | TS が一歩リード |
| ドキュメント | 良い | 充実 | TS が一歩リード |
| 型安全性 | Pydantic (実行時) | Zod + TS (静的) | TS が強い |
| 既存社内スキル | スクレイピング・AI で蓄積 | フロント側で蓄積 | 同等 |
| AI レポート機能との同居 | ○ Python 同士 | × 言語が異なる | Python が有利 |
| PPTX/Excel 生成等の拡張 | ○ python-pptx 等 | △ Node.js 対応ライブラリは少なめ | Python が有利 |
| Laradock 内同居 | 新規コンテナ追加 | 既存 Node コンテナ流用可 | TS が有利 |
| 起動速度・メモリ | Node.js より重い | 軽量 | TS が有利 |
| Streamable HTTP 実装の成熟度 | 安定 | 安定 | 同等 |
| OAuth 2.1 ライブラリ | Authlib(豊富) | oauth4webapi(標準準拠) | 同等 |
5.4 推奨と判断基準
推奨:Python (FastMCP)
判断軸の優先順位:
- 同居・統合運用の容易性(AI レポート機能 #9 が Python 採用済み)
- 将来の拡張性(PPTX/Excel 生成、機械学習統合の選択肢)
- 社内スキル(スクレイピング系も Python)
これら 3 点で Python が優位。TypeScript の優位性(公式サポート・型安全)も有力だが、組織横断の運用統合を優先する。
最終決定は 設計レビューでチーム合意 → 製造 Issue 起票直前で確定。
6. クライアント別の挙動差
| クライアント | トランスポート | OAuth フロー | 注意点 |
|---|---|---|---|
| Claude Desktop | Streamable HTTP / stdio 両対応 | 内蔵ブラウザで PKCE フロー | macOS / Windows / Linux 対応 |
| Claude Code | Streamable HTTP / stdio 両対応 | 同上 | CLI ベース |
| ChatGPT Connectors | Streamable HTTP のみ | Web ベースのコネクタ設定画面 | リダイレクト URI を ChatGPT 側に登録 |
6.1 互換性確保のため必須
/.well-known/oauth-authorization-serverを返す- RFC 7591 動的クライアント登録(ChatGPT は事前登録不要にできる)
- PKCE 必須対応(
code_challenge_method=S256) - tool description は英語併記 or 顧客 AI の言語に追随できる構造
- リダイレクト URI のホワイトリスト:Claude/ChatGPT の公式 URI を登録
7. エラーハンドリング
7.1 エラー区分
| 区分 | HTTP / JSON-RPC | 例 | 動作 |
|---|---|---|---|
| 認証エラー | 401 | トークン無効・期限切れ | クライアントに再認証を促す |
| 認可エラー | 403 / -32604 | scope 不足 | tool 結果を isError: true で返す |
| 入力エラー | 400 / -32602 | バリデーション失敗 | 詳細メッセージ付きで返却 |
| Mappy 側 4xx | 透過 | レコード未存在等 | エラーコードを保持して返却 |
| Mappy 側 5xx | 502 / -32603 | Laravel API 障害 | Audit Log に記録、リトライ可否を判定 |
| GBP API 障害 | 502 | Google 側エラー | エラーメッセージを顧客 AI に伝達 |
| タイムアウト | 504 | 30 秒以上応答なし | クライアントに通知、書込系は batch_id で追跡 |
7.2 タイムアウト方針
| 区間 | タイムアウト |
|---|---|
| MCP Client → MCP Server | クライアント側設定(通常 30〜60 秒) |
| MCP Server → Laravel API | 10 秒(読取)/ 30 秒(書込同期処理) |
| Laravel API → GBP API | 既存設定に従う(通常 30 秒) |
| 書込系全体(ジョブ完了まで) | 非同期、batch_id で追跡 |
7.3 リトライ方針
- 読取系: 5xx のみクライアント側で 1 回まで自動リトライ可
- 書込系: idempotency_key を使った再送のみ許可。サーバー側自動リトライは禁止
- GBP API レート制限: Laravel 側で既存の rate-limiter middleware を流用
8. レート制限
| 適用範囲 | 制限 | 理由 |
|---|---|---|
| アクセストークン単位 | 60 リクエスト/分(読取) | DoS 対策 |
| アクセストークン単位 | 10 リクエスト/分(書込) | GBP API 制限への配慮 |
| クライアント ID 単位 | 1000 リクエスト/時 | クライアント丸ごとの暴走対策 |
| 同一 idempotency_key | 24 時間 | 重複書込防止 |
9. デプロイ構成(概要)
詳細は インフラ設計 を参照。
| 項目 | 内容 |
|---|---|
| 配置 | AWS(既存 Mappy インフラと同一 VPC) |
| コンテナ | ECS Fargate(または EC2) |
| ロードバランサー | ALB(HTTPS 終端) |
| ドメイン | mcp.mappy.example.com(仮) |
| TLS | ACM 証明書 |
| 共通ストア | Redis(セッション、レート制限カウンタ、idempotency キャッシュ) |
| シークレット管理 | AWS Secrets Manager |
| ログ集約 | CloudWatch Logs |
| 監視 | CloudWatch Metrics / Alarms |
10. 監査ログ
MCP Server はすべての操作を監査ログに記録する。 詳細は DB 設計(監査ログ) を参照。
記録対象:
- tool 呼び出し(入力・出力・実行時間・結果コード)
- 認可成功・失敗
- トークン発行・失効
- レート制限ヒット
- 例外・タイムアウト
11. テナンシ・スコープ(概要)
詳細は 認証・テナンシ設計 を参照。
| 階層 | 操作可能範囲 |
|---|---|
admin (is_supervisor=1) | 全 mappy_users |
admin (is_supervisor=0) | preview_user_id 対象のユーザー |
| MAIN_USER | 自分 + parent_user_id 配下 |
| MASTER_USER | parent_user_id 配下全部 |
| GROUP_USER | 自分のみ |
店舗境界:
mappy_user_available_gbp_locations経由のフィルタ- グループ単位は
mappy_groups+mappy_group_location(単数形)
12. 関連設計書
| ドキュメント | 内容 |
|---|---|
| ツールカタログ | 各 tool の入出力 schema、scope、副作用区分 |
| 認証・テナンシ設計 | OAuth 2.1 フロー詳細、scope 設計、テナンシ判定ロジック |
| 書込安全装置 | dry_run / idempotency / 確認トークン / batch ポーリング |
| DB 設計(監査ログ) | 監査ログテーブル定義 |
| DB 設計(OAuth クライアント) | OAuth クライアント・トークン管理テーブル |
| インフラ設計 | AWS 構成・コンテナ・ネットワーク |
| Issue 詳細 | 実装タスク・検収条件 |
13. 未確定事項(設計レビューで決定)
| # | 項目 | 候補 |
|---|---|---|
| 1 | 実装スタック | Python (FastMCP) / TypeScript (公式 SDK) |
| 2 | OAuth ライブラリ | Python: Authlib / 独自 TS: oauth4webapi / 独自 |
| 3 | MCP Server のホスト名 | mcp.mappy.example.com 仮 |
| 4 | admin 経由の代理操作の許可範囲 | Phase 1 では admin 自身のみ/Phase 2 で preview_user_id 対応 |
| 5 | レート制限の最終値 | 上記は暫定、本番負荷を見て調整 |
| 6 | Redis vs DynamoDB | セッション・idempotency ストアの選定 |
| 7 | クライアント側のキャッシュ可否 | tool 結果のキャッシュをクライアントが行うか |