MCP サーバ機能 — 実装設計
- Dominant language
- PHP
- Stars
- 788
- Forks
- 719
- Avg merge
- 3d 20h
- Merged PRs (30d)
- 45
Description
# [提案] MCP サーバ機能 — 実装設計
> EC-CUBE に組み込む **読み取り専用 MCP (Model Context Protocol) サーバ機能** について、**最終的にどう実装するか** を決定するための設計提案です。個別フェーズの進捗ではなく、GA (正式リリース) で到達すべき構成をまとめ、レビューと合意の土台とします。
>
> **認証認可は独自実装せず、既存の API プラグイン (`ec-cube/api42`) の OAuth2 基盤を再利用します。** MCP 固有の scope と公開フィールド定義 (allow_list) も API プラグインが持ち、MCP サーバ本体は本体同梱とする **2 層構成**です。
>
> **API プラグインのバージョン**: 現行の `ec-cube/api42` は EC-CUBE 4.2 / 4.3 系向けです。本提案は EC-CUBE 4.4 が対象のため、**その 4.4 対応版(仮称 `api44`)へのバージョンアップが前提**になります。本文では 4.4 対応版を `api44` と表記します。
| 項目 | 値 |
|------|-----|
| 対象バージョン | **EC-CUBE 4.4** (Symfony 7.4 / Doctrine ORM 3.x / Doctrine DBAL 3.x / PHP 8.2+) |
| 配布形態 | **MCP サーバ本体=本体同梱** / **認証認可=api44(API プラグイン)に集約** |
| 認証基盤 | **API プラグインの OAuth2 を再利用**。EC-CUBE 4.4 では 4.4 対応版(**仮称 api44**)が必要 |
| 受入基準 | [§8](#8-受入基準-acceptance-criteria) |
## 1. 概要 (Overview)
EC-CUBE の業務データを **AI から自然言語で参照できる** ようにする読み取り専用機能です。本提案の主題は **MCP サーバ**(`/admin/mcp`)— 外部 AI クライアント(Claude Desktop / Cursor / VS Code (Copilot) 等の MCP 対応クライアント)からの自然言語問い合わせを受け、EC-CUBE の Service レイヤを呼び出して結果を返す **薄いアダプタ層**。MCP サーバ自身は LLM を内包しません。
最終構成の要点:
- 対象は **商品/在庫・注文・顧客会員・プラグイン管理** の 4 領域、**全ツール読み取り専用**。
- トランスポートは **Streamable HTTP (`/admin/mcp`)** のみ。
- 認証は **api44 の OAuth2(Bearer access token)を再利用**。独自のトークン機構は実装しない。認証を api44 が担うため、**api44 は必須依存**(未導入では MCP を利用できない)。
- 認可は **領域別 read scope ベース**(`mcp:product:read` / `mcp:order:read` / `mcp:customer:read` / `mcp:plugin:read`)。トークンの scope と Tool の要求 scope を AND 評価し、不足すれば 403。
- 公開フィールドは **api44 の allow_list(エンティティ別)を流用**。氏名・住所等の PII も含むため、**「この AI クライアントに当該領域を見せてよいか」を scope 付与(運用)で制御**する。値のマスキングは行わない(GraphQL API と同じ姿勢)。
- 全呼び出しを **監査ログ**に記録する。
---
## 2. 背景と目的
- **背景**: Agentic Commerce(AI エージェント経由の商取引)の潮流に対応し、AI から EC-CUBE の状況を安全に参照できる標準インターフェースの価値が高まりつつあると考えます。管理画面のオペレーションを、AI による状況把握 (在庫確認、受注状況、顧客サポートの事前把握など) で補助することが狙いです。
- **方針**: 本機能は **全フェーズ読み取り専用**。書き込み (ステータス変更、説明文編集等) は従来どおり管理画面から行い、本機能の対象外とします。
- **目的**:
1. 業務データを、AI クライアントから統一プロトコル (MCP) で安全に参照可能にする。
2. **既存の OAuth2 基盤(api44)を再利用**して二重実装を避け、認証・認可・公開フィールド定義の責務を API プラグインに集約する。
3. **最小権限の原則**を領域別 scope で担保し、トークン漏洩時の被害を当該領域に限定する。
4. 公開フィールドの範囲と露出可否を、**allow_list(何を)と scope(誰に)**で統制する。
---
## 3. アーキテクチャ構成図
### 3.1 2 層構成
認証認可・scope・公開フィールド定義はすべて API プラグイン(api44)が持ち、MCP サーバ本体はそれを利用します。
```mermaid
flowchart TB
subgraph L1["api44 (API プラグイン) — 認証認可を担う"]
OA["OAuth2 サーバ
/token・/authorize
auth_code + PKCE + refresh"]
RS["Resource Server
公開鍵で Bearer 検証"]
SC["MCP scope
mcp:product:read 等"]
AL["allow_list
エンティティ別の公開フィールド
(GraphQL と共用)"]
FW["^/admin/mcp 用 firewall を
admin の前に prepend (oauth2)"]
end
subgraph L2["MCP サーバ (本体同梱)"]
EP["/admin/mcp
(symfony/mcp-bundle)"]
TOOLS["ツール群 (読み取り専用)"]
end
FW -.保護.-> EP
SC -.IsGranted で認可.-> TOOLS
AL -.公開フィールドを決定.-> TOOLS
EP --> TOOLS
```
> **依存関係**: MCP サーバ本体(L2)の認証・認可・公開フィールド定義は **api44(L1)に依存**します。**api44 は必須依存**で、未導入では MCP を利用できません。サーバ本体を本体同梱とするため core が api44(プラグイン)に依存する形になりますが、認証認可を一箇所に集約する利点を優先してこの構成とします。
### 3.2 コンポーネント全体像
```mermaid
flowchart TB
subgraph Client["MCP クライアント"]
Editor["Claude Desktop / Cursor / VS Code 等"]
SaaS["外部 AI エージェント"]
end
Transport["Streamable HTTP
/admin/mcp"]
ORIGIN["Origin / Content-Type 検証"]
Auth["OAuth2 Bearer 認証
(api44 resource server で検証)"]
SCOPE["scope 認可
(api44 / league の IsGranted)"]
subgraph Tools["ツール層 (読み取り専用)"]
T1["商品/在庫"]
T2["注文"]
T3["顧客会員"]
T4["プラグイン管理"]
end
Core[("EC-CUBE Core
Service / Repository / DB")]
ALLOW["allow_list
(公開フィールドを決定)"]
subgraph Observability["横断的関心事"]
AUDIT["McpAuditLogger
(mcp チャネル / 単一エントリポイント)"]
RATE["Rate Limiter"]
end
Client --> Transport --> ORIGIN --> Auth
Auth --> SCOPE
SCOPE --> Tools
Tools <-->|read| Core
Tools -->|allow_list で絞った JSON-RPC result| Client
ALLOW -.公開項目.-> Tools
Tools -.記録.-> AUDIT
SCOPE -.認可結果.-> AUDIT
Auth -.認証結果.-> AUDIT
ORIGIN -.異常.-> AUDIT
RATE -.制限超過.-> AUDIT
ORIGIN -.IP/ルート制限.-> RATE
Auth -.client_id 制限.-> RATE
```
> **読み方の補足**: 認証は api44 の OAuth2 Bearer で行います。Tool の出力フィールドは **api44 の allow_list** が決め、値はマスクせず返します。
### 3.3 認証付きツール呼び出しのフロー (シーケンス図)
```mermaid
sequenceDiagram
autonumber
actor C as MCP Client
participant Guard as Origin/CT ガード
(kernel リスナ)
participant Rate as Rate Limiter
participant Auth as oauth2 firewall
(api44 resource server)
participant EP as /admin/mcp
(MCP コントローラ)
participant Voter as scope 認可
(api44 / IsGranted)
participant Tool as MCP Tool
(例: get_order)
participant Repo as Service /
Repository
participant Audit as McpAuditLogger
Note over C,Auth: ▼ セキュリティ層 (コントローラ到達前)。失敗時は EP に届く前に応答を返す
C->>Guard: POST /admin/mcp tools/call get_order
Authorization: Bearer
alt Origin / Content-Type 不正 (CSRF / 非 JSON 等)
Guard->>Audit: warning (origin_invalid)
Guard-->>C: 415 / 403
else OK
Guard->>Rate: consume(ip / route) — 認証前は送信元ベース
end
alt 流量超過
Rate->>Audit: warning (rate_limited)
Rate-->>C: 429 rate_limited
else OK
Rate->>Auth: Bearer トークンを公開鍵で検証 + 失効確認
end
alt 認証失敗 (署名不正 / 期限切れ / 失効)
Auth->>Audit: warning (token_invalid) ※security イベント経由
Auth-->>C: 401 unauthorized
else OK
Auth->>EP: 認証済みリクエストを委譲
(Member + scope を保持)
end
Note over EP,Audit: ▼ コントローラ以降 (認証済み)
EP->>Rate: consume(client_id) — クライアント単位
alt 流量超過
Rate->>Audit: warning (rate_limited)
EP-->>C: 429 rate_limited
else OK
Rate-->>EP: OK
end
EP->>Voter: isGranted('ROLE_OAUTH2_MCP:ORDER:READ')
Note over Voter: トークンの scope ∧ Tool 必要 scope
(例: get_order は mcp:order:read を要求)
alt 認可拒否
Voter->>Audit: warning (scope_denied)
EP-->>C: 403 insufficient_scope
else grant
Voter-->>EP: grant
end
Note over EP,Audit: ▼ ここから正常系。すべての check を通過
EP->>Tool: 実行
Tool->>Repo: read-only クエリ
Repo-->>Tool: エンティティ (例: Order)
Tool->>Tool: allow_list で公開フィールドに整形
Tool->>Audit: info (tool, args, success, duration_ms)
Tool-->>EP: 結果
EP-->>C: JSON-RPC result
```
> **認証の主体**: Bearer トークンの検証は **api44 の resource server**(`league/oauth2-server-bundle`)が担い、api44 が prepend した `^/admin/mcp` 用 firewall(`oauth2: true`)配下で作動する。MCP サーバ本体に独自の Authenticator は持たない。
>
> **監査ログの記録主体**: 図中で `Audit` へ向かう主体(Guard / Rate / Auth / Voter / Tool)は `LoggerInterface` を直接呼ばず、必ず `McpAuditLogger` 経由で書く([§5](#5-主要なコンポーネント・クラスとその責務))。
---
## 4. スコープとトークンの扱い
認証認可を api44 に委譲するため、**MCP 専用のテーブルは追加しません**。OAuth2 のクライアント・アクセストークン・リフレッシュトークンの永続化はすべて `league/oauth2-server-bundle`(api44 が同梱)が管理します。
### 4.1 scope 定義(api44 が持つ)
api44 の OAuth2 は GraphQL 用に scope `read` / `write` を持ちます。MCP 用には、GraphQL と**衝突しないよう名前空間を分離**した領域別 read scope を api44 に追加します。scope は `role_prefix: ROLE_OAUTH2_` で role に変換されます。
| scope | 変換後の role | 対象領域 |
|---|---|---|
| `mcp:product:read` | `ROLE_OAUTH2_MCP:PRODUCT:READ` | 商品/在庫の読み取りツール |
| `mcp:order:read` | `ROLE_OAUTH2_MCP:ORDER:READ` | 注文の読み取りツール |
| `mcp:customer:read` | `ROLE_OAUTH2_MCP:CUSTOMER:READ` | 顧客会員の読み取りツール |
| `mcp:plugin:read` | `ROLE_OAUTH2_MCP:PLUGIN:READ` | プラグイン管理の読み取りツール |
> **read のみ**: 本機能は全ツール読み取り専用のため、write scope は定義しない(書き込みツールを将来追加する場合に別途検討、[§9](#9-将来の拡張メモ本提案の対象外))。
> **名前空間分離の理由**: api44 の素の `read` / `write` を流用すると、GraphQL 用に発行したトークンで MCP ツールも叩けてしまう(逆も同様)。`mcp:` 接頭辞で分離し、GraphQL の認可と MCP の認可を独立させる。
> **役割名のコロン**: scope 名(`mcp:product:read`)はそのまま大文字化されて role になるため、role 名にコロンが残る(`ROLE_OAUTH2_MCP:PRODUCT:READ`)。Symfony 上は動作するが慣習的ではない。scope 名は定数で集中管理し、必要なら後から一括変更できるようにする。
### 4.2 監査ログ (テーブルではなくファイル)
監査ログは DB ではなく Monolog の `mcp` チャネル (JSON フォーマット) に `McpAuditLogger` 経由で出力します。
**ログ項目:**
| フィールド | 内容 |
|------------|------|
| `timestamp` | ISO 8601 |
| `client_id` | OAuth2 クライアント識別子(認証成功時のみ)。**「どのクライアント/用途のトークンか」を識別する主キー** |
| `member_id` | 認証成功時のみ記録。トークンを承認したシステム管理者の ID(OAuth2 では発行者は常に管理者)。クライアント/用途の識別は `client_id` で行う |
| `client_ip` | リモート IP |
| `request_id` | ULID (リクエスト相関) |
| `tool_name` | 呼び出された Tool 名 (認可拒否時も記録) |
| `tool_args` | 引数 |
| `result_status` | `success` / `validation_error` / `origin_invalid` / `token_invalid` / `scope_denied` / `rate_limited` / `internal_error`([§3.3](#33-認証付きツール呼び出しのフロー-シーケンス図) の warning 名と一致) |
| `duration_ms` | 実行時間 |
> `result_status` は**監査ログ内部の値**です。[§4.3](#43-エラーレスポンス形式) でクライアントに返す `error` フィールド(`unauthorized` / `insufficient_scope` / `rate_limited`)とは用途が異なります(前者は記録用、後者はクライアント向け応答)。
> **ログ内の PII**: `tool_args` や結果には個人情報が含まれ得る(マスクしない方針のため)。ログファイルは OS のファイルパーミッションで読み取りを制限する([§7.1](#71-セキュリティ))。
**保管期間:** 既定 **90 日**。環境変数 `ECCUBE_MCP_AUDIT_RETENTION_DAYS` で運用側が上書き可能。不正利用や情報漏洩の調査では発覚までに時間がかかる場合があり、短すぎると追跡できないため、既定はやや長めに設定する。
### 4.3 エラーレスポンス形式
MCP クライアントへの応答は JSON-RPC 2.0 準拠。認証・認可・流量制限は HTTP レイヤで以下の形式を返す。
```json
// 401 (認証失敗 / トークン不正・期限切れ・失効)
{ "error": "unauthorized", "message": "Invalid or expired access token" }
// WWW-Authenticate: Bearer
// 403 (scope 不足)
{ "error": "insufficient_scope", "required_scope": "mcp:order:read" }
// 429 (Rate Limit 超過)
{ "error": "rate_limited", "retry_after_seconds": 60 }
// Retry-After: 60 / X-RateLimit-Remaining: 0
```
> **応答の産出主体**: 401 は api44 の oauth2 firewall(resource server)が返す(既定の応答をエントリポイントで上記 JSON 形式に整える)。403 `insufficient_scope` と 429 は MCP コントローラ側で返す。
---
## 5. 主要なコンポーネント・クラスとその責務
| コンポーネント | 配置 | 責務 |
|----------------|------|------|
| `#[AsMcpTool]` 属性 + Tool クラス群 | 本体 | 各ツールを宣言 (name / description / 入出力スキーマ / **必要 scope**)。4 領域・計 11 ツールを read-only で提供し、Service・Repository を呼ぶ |
| Tool レジストリ (`symfony/mcp-bundle`) | 本体 | `mcp.tool` タグで Tool を自動収集し、`tools/list` / `tools/call` に公開 |
| **OAuth2 認証基盤** (`league/oauth2-server-bundle`) | api44 | Bearer access token の発行 (`/token`)・認可コードフロー (`/authorize`)・公開鍵による検証 (resource server)・失効管理。**MCP は独自の Authenticator を持たず、これに委譲** |
| **MCP scope + firewall 配線** | api44 | 領域別 read scope を OAuth2 の `scopes.available` に追加し、`admin` firewall の**前**に `^/admin/mcp` 用 firewall(`stateless: true` / `oauth2: true` / `provider: member_provider`)を prepend する。core の `security.yaml` は変更しない(api44 が `^/api` で行う手法と同型) |
| **scope 認可** | api44 | 認可判断は **league の標準機構(`IsGranted` / `AuthorizationCheckerInterface`)に一本化**。各 Tool の必要 scope(例: `get_order` → `mcp:order:read`)を照合し、不足すれば 403 `insufficient_scope`。MCP 専用のカスタム Voter は持たない |
| **allow_list** (`core.api.allow_list`) | api44 | エンティティ別の公開フィールド定義(FQCN → 公開プロパティ名)。**GraphQL と共用**。MCP の Tool 出力もこの定義に従ってフィールドを絞る(氏名・住所等の PII も対象に含まれる) |
| `McpAuditLogger` | 本体 | **構造化監査ログを書く唯一のエントリポイント (Single Source of Truth)**。Origin ガード / 認証 / scope 認可 / Rate Limiter / Tool は `LoggerInterface` を直接呼ばず、必ずこれを経由する。認証成否のみ api44 側で起きるため、`^/admin/mcp` の認証イベントをリスナで拾って転送する |
| Rate Limiter 連携 | 本体 | Symfony RateLimiter で制限。キーは IP / Tool ルート / client_id で、**認証前(IP・ルート)と認証後(client_id)の 2 段**で適用([§3.3](#33-認証付きツール呼び出しのフロー-シーケンス図) の図と一致)。ヘッダ `X-RateLimit-Remaining` / `Retry-After` を返却。既定値は控えめにし、具体数値は GA 運用開始時に確定 |
| Origin / Content-Type ガード (EventListener) | 本体 | `Content-Type: application/json` を強制し、許可リスト外 Origin を 403。CSRF 対策 |
| 管理 UI: OAuth2 クライアント管理 | api44 | クライアントの **登録 / 一覧 / 失効**、および付与 scope の選択。**MCP 専用のトークン管理 UI は実装しない**(api44 の既存 UI を流用) |
| 管理 UI: MCP 設定 | 本体 | 機能の有効/無効、許可 Origin、ログレベルの設定 |
| CLI: `league:oauth2-server:create-client` | api44 / league | コマンドラインでの OAuth2 クライアント発行 (CI / 自動化向け)。`--grant-type=authorization_code --grant-type=refresh_token`、`--scope=mcp:order:read`(必要な領域の read scope を指定)、`--redirect-uri=`、デスクトップ系は `--public`(PKCE)。**MCP 独自の発行コマンドは実装しない** |
### ツール一覧
4 領域・全 11 ツール(読み取り専用)。商品/在庫・注文・顧客会員が各 3、プラグイン管理が 2。各ツールは**対象領域の read scope**を要求します。
> **追加提案歓迎**: 以下は初期セットです。ツール追加の提案は本 Issue のコメントでお寄せください。
**商品/在庫**(必要 scope: `mcp:product:read`)
| ツール | 説明 |
|--------|------|
| `search_products` | 商品をキーワード/カテゴリ/公開ステータス/在庫数で**検索**(最大件数指定・ページング対応) |
| `get_product` | 商品 ID または商品コードから**詳細を取得**(規格/画像/カテゴリを含む) |
| `get_product_stock` | 商品/商品規格(ProductClass)単位の**在庫数を取得**(規格あり/なし両対応、`stock_unlimited` フラグを反映) |
**注文**(必要 scope: `mcp:order:read`)
| ツール | 説明 |
|--------|------|
| `search_orders` | 注文をステータス/期間/金額レンジ/顧客 ID で**検索**(出力は allow_list の項目・概要のみ) |
| `get_order` | 注文番号または注文 ID から**詳細を取得**(明細/配送状況/支払状況。allow_list に含まれる氏名・住所等も返る) |
| `get_shipping` | 注文に紐づく**配送情報を取得**(出荷ステータス/配送日/追跡番号/配送先) |
**顧客会員**(必要 scope: `mcp:customer:read`)
| ツール | 説明 |
|--------|------|
| `search_customers` | 会員をメール/登録期間/ステータスで**検索**(一覧は allow_list の項目) |
| `get_customer` | 会員 ID から**詳細を取得**(氏名・連絡先・住所など allow_list の項目を返す) |
| `get_customer_orders` | 指定会員の**購入履歴を取得**(受注一覧、明細は概要のみ) |
**プラグイン管理**(必要 scope: `mcp:plugin:read`)
| ツール | 説明 |
|--------|------|
| `list_plugins` | **インストール済みプラグインの一覧**。`Plugin` Entity の主要フィールド(`code` / `name` / `version` / `enabled` / `initialized`)を返す |
| `get_plugin` | プラグイン ID または `code` から**詳細を取得**。`Plugin` Entity に加えて、`app/Plugin//composer.json` を読んで `description`(機能概要)と `require`(依存関係)を含める(**file IO + JSON 読み込みが発生する点に注意**) |
> **プラグイン管理の境界**: `mcp:plugin:read` で読めるのは**インストール状況とメタ情報まで**(一覧・有効/無効・バージョン・依存関係)。**個別プラグインの設定値(API キー等の機微データを含み得る)には踏み込まない**。設定値の参照は別ツールとして将来検討する([§9 `get_plugin_settings`](#9-将来の拡張メモ本提案の対象外) 参照)。
> **出力フィールドの規約**: Tool が返すフィールドは **api44 の allow_list に列挙された項目のみ**(未列挙は返さない)。値はマスクしないため、PII を含む領域(注文・顧客会員)の露出可否は **scope 付与で運用統制**する。
---
## 6. スコープ外 (今回の実装に含めないこと)
- **書き込み系ツール** (受注ステータス変更、商品編集等)。本機能は全フェーズ読み取り専用。
- **出力値のマスキング/匿名化**。公開フィールドは allow_list で、露出可否は scope で統制する方針のため、値の変換は行わない(GraphQL API と同じ)。
- **LLM の内包**。本機能はアダプタ層であり、推論はクライアント側 (外部 AI クライアントが呼ぶ LLM プロバイダ) が担う。
- **フロント系(購入フロー)等**は対象外(本機能は管理画面業務の効率化を目的とするため)。
- **管理画面組み込みの AI チャット UI**。本機能は外部 MCP クライアント向けの API レイヤであり、管理画面内 UI は対象外。
- **OAuth2 の動的クライアント登録 (RFC 7591)**。`league/oauth2-server-bundle` は動的登録に対応しないため、MCP クライアント用の OAuth2 クライアントは **`league:oauth2-server:create-client` での provisioning(CLI、CI 可)または api44 管理 UI での手動登録**で運用する([§9](#9-将来の拡張メモ本提案の対象外) 参照)。
---
## 7. 技術的考慮事項
### 7.1 セキュリティ
- **認証の委譲**: トークンの発行・検証・失効は api44 の OAuth2 基盤(`league/oauth2-server-bundle`)に委譲する。MCP 側で独自の資格情報を保存・照合しない。
- **アクセストークン**: OAuth2 アクセストークンは JWT。resource server は公開鍵で署名を検証し、**加えて毎リクエスト失効を照合**する(league の `BearerTokenValidator` が `isAccessTokenRevoked` を確認)。純粋なステートレス検証ではないため、**失効・無効化は次のリクエストで即時反映**される。漏洩時の被害は **TTL(短命)+ refresh token** で時間的にも限定する。
- **PKCE**: デスクトップ系などの public client は **認可コード横取りを防ぐため PKCE(S256)を用いる**(`create-client --public`、平文 PKCE は許可しない)。
- **最小権限 (領域別 scope)**: トークンには必要な領域の read scope のみを付与し(既定は scope なし=全 deny)、Tool の要求 scope と AND 評価。scope を絞れば当該領域しか読めない。OAuth2 トークンは常にシステム管理者に紐づくため Member 権限では領域を絞れず、**scope が唯一の領域制御手段**である。
- **公開フィールドと PII**: Tool が返すフィールドは api44 の allow_list で定義し、値はマスクしない。allow_list には氏名・住所・メール・電話等の PII が含まれるため、**当該領域の scope を付与したトークンには PII が生で渡る**。「この AI クライアントに当該領域を見せてよいか」を**トークン発行時に判断する運用**で統制する。
- **監査**: 全呼び出しを構造化ログに記録(client_ip / request_id 付き)。認可拒否・rate_limited も記録対象。
- **監査ログ書き込み失敗時の挙動**: EC-CUBE 標準の Monolog 挙動に従う(書き込み失敗を握りつぶさない)。ログ出力の失敗は例外として表面化させ、監査証跡を欠いたまま処理を継続させない。
- **監査ログファイルの保護**: `mcp` チャネルの出力ファイルは PII を含み得るため、OS のファイルパーミッションで読み取りを制限する(ローテーション後の旧ファイルも同様)。
- **通信**: HTTPS 必須(非 HTTPS の接続は拒否)。`/admin/mcp` をインターネット直接公開する場合は WAF / FW で送信元制限を必須とする。
- **CSRF / インジェクション**: Content-Type 強制 + Origin 検証。Tool 出力は JSON のまま返し、商品説明文等を介したプロンプトインジェクションの影響を抑える。
- **GA の絶対条件**: scope 認可機構(api44 の `IsGranted` による領域別 scope の必須化)の実装。認可機構が未稼働の状態での外部公開は行わない。
### 7.2 後方互換性
- **既存スキーマ非変更**: MCP 固有テーブルは追加しない。OAuth2 関連テーブルは api44 のインストールで作られる。
- **既存認証と共存**: api44 が追加する firewall は `^/admin/mcp` のみを対象とし、`admin` firewall の**前**に置かれる。それ以外の `/admin/*` は従来どおり `form_login`(Cookie)が処理する。
- **`LoginThrottling` との相互作用**: MCP の OAuth2 認証は **admin firewall とは別の stateless firewall** で処理されるため、admin の login id ベース throttling のカウンタには干渉しない。流量制御は本機能の Rate Limiter(IP / client_id 単位)で行う。
- **機能の無効化**: 管理画面の「MCP 設定」で機能 OFF にしてルート・ツールを停止できる。api44 を無効化すると、追加した scope・firewall が消える。`/admin/mcp` ルートは admin Cookie firewall 配下に戻るが、**OAuth2 認証と scope を発行できないため MCP ツールは利用できない**(api44 が必須依存である理由)。
### 7.3 拡張性・保守性
- **scope 命名規約**: MCP の scope は `mcp:<領域>:read` 形式(`mcp:product:read` 等)。api44 の GraphQL scope(`read` / `write`)とは名前空間で分離。領域追加は Tool 追加 + 新 scope の追加で対応。scope 名は定数で集中管理する。
- **認証基盤の更新独立性**: 認証認可・scope・allow_list を api44 に集約することで、OAuth2 周りの脆弱性対応を本体リリースサイクルと独立して出せる。api44 への依存が前提になる点はトレードオフだが、api44 はメンテナ管理下のため許容する。
- **横断層の分離**: 監査・レート制限を横断層として分離。各 Tool は業務ロジックに専念。
- **メトリクス・ダッシュボード**: 監査ログ集計を基盤とした可視化は本提案スコープ外。
---
## 8. 受入基準 (Acceptance Criteria)
GA リリースの Done 判定。すべて満たした時点で本機能を一般公開する。
- [ ] `tools/list` が全 11 ツールを返す。
- [ ] 各 Tool は **対象領域の read scope を持たないトークンで呼ぶと 403 `insufficient_scope`** を返す ([§4.3](#43-エラーレスポンス形式) の形式)。例: `mcp:order:read` を持たないトークンでは `get_order` が 403。自動テストで全 Tool × 領域 scope を網羅。
- [ ] **Tool の出力フィールドが api44 の allow_list と一致**する(未列挙フィールドが漏れない)。契約テストで検証。
- [ ] **`McpAuditLogger` を経由しない `mcp` チャネル書き込みが存在しない** (静的解析で `LoggerInterface` 直呼び出しを禁止)。
- [ ] `league:oauth2-server:create-client` で領域別 read scope(例 `--scope=mcp:order:read`)付きの OAuth2 クライアントを発行でき、PKCE 付き public client として認可コードフローでアクセストークンを取得できる。
- [ ] **トークン失効が即 401**: クライアント/トークンを失効(または期限切れ)させると、次のリクエストで 401(league の `isAccessTokenRevoked` 照合による)。
- [ ] api44 の **有効化で領域別 read scope(`mcp:product:read` 等)と `^/admin/mcp` firewall が有効化**され、**無効化で消える**。無効化後は `/admin/mcp` が admin Cookie firewall 配下に戻る。
- [ ] `LoginHistory` / `LoginThrottling` を含む既存 admin firewall 機能が、本機能・api44 導入前後で挙動を変えない (回帰テストで検証)。
- [ ] **発行 Member の無効化が即時反映**: 発行 Member が削除・無効化された場合、resource server が `member_provider` でのユーザ解決に失敗し、当該トークンは **即 401** になる(発行時スナップショットに依存しない)。
---
## 9. 将来の拡張メモ(本提案の対象外)
本提案のスコープ外で、方向性として認識しておく項目です。必要になった時点で別 Issue として提案します。
| 項目 | 想定内容 |
|------|----------|
| **書き込み系ツール** | 受注ステータス変更、在庫補正、商品編集等。本機能は全フェーズ読み取り専用方針のため、書き込みを行う場合は write 用 scope を別途定義した上で別機構として企画 |
| **動的クライアント登録 (RFC 7591)** | MCP クライアントが OAuth2 クライアントを自動登録する用途。`league/oauth2-server-bundle` が非対応のため、当面は CLI provisioning / 管理 UI 登録で運用。需要が文書化された時点で別 Issue を起こす |
| **`get_plugin_settings`(プラグイン設定値の参照)** | 個別プラグインの設定値を読むツール。API キー等の機微データを含み得るため、別 scope を含めて別 Issue で設計 |
Contributor guide
Assessment
This issue has not been assessed yet.