Blog

ブログ

EC-CUBE 4.4 の MCP サーバを AI クライアントから使う

先にまとめ:

  • 4.4 には MCP サーバがコア同梱。読み取り専用ツール11個、既定 OFF
  • 本体だけではツールの出力が空になる。公式 API プラグイン ec-cube/api44 が必須(認証と公開範囲の定義を担当している)
  • Api44 を入れると管理画面から MCP 用のアクセストークンを発行できる。
  • トークンのスコープでMCPの行動範囲を指定できる
  • bin/consoleから人力で叩くこともできる(ツールがそのままコマンドになっている)、AI クライアントからは HTTP + トークン
  • 本番で有効にするなら TRUSTED_HOSTS の設定が必須

検証は 2026-09-08 時点の 4.4 ブランチ(efa640d)と eccube-api4 の 4.4 ブランチ、同梱の docker-compose 構成(PHP 8.2 / Apache / PostgreSQL 18)で行いました。どちらも開発中なので、正式版で挙動が変わる可能性があります。

この記事はClaude Opus 5が執筆しています。

EC-CUBE MCP はどんなものか

 MCP(Model Context Protocol)は、AI クライアントに外部データやツールを渡すための標準プロトコルです(公式サイトAnthropic の発表)。

EC-CUBE 4.4 はこのサーバ側をコアに実装しました。4.4 全体の位置づけとリリース時期は公式ロードマップ #6762 にあります(正式版は 2026年10月下旬予定)。

用意されているツールは11個で、すべて読み取り専用です。書き込み系のツールはありません。必要なスコープはツールごとに決まっていて、対応は src/Eccube/Service/Mcp/McpToolScopeMap.php の1箇所に定義されています。

ツール 内容 必要スコープ
search_products / get_product / get_product_stock 商品検索・商品詳細・在庫 mcp:product:read
search_orders / get_order / get_shipping 受注検索・受注詳細・配送 mcp:order:read
search_customers / get_customer / get_customer_orders 会員検索・会員詳細・購入履歴 mcp:customer:read
list_plugins / get_plugin プラグイン一覧・詳細 mcp:plugin:read

有効化は既定 OFF(BaseInfomcp_enabledfalse)で、店舗設定(/admin/setting/shop)のトグルから明示的に行います。無効の間は /admin/mcp が 404 を返し、エンドポイントの存在自体を明かしません。

環境を用意する

Docker Compose での環境構築は公式ドキュメントのとおりなので省略します(Docker Compose を使用したインストールシステム要件)。今回は 4.4 ブランチを clone して PostgreSQL 18 の構成で立てました。

データが無いと MCP のツールを試せないので、ダミーデータを生成します。

bin/console eccube:fixtures:generate \
  --products=50 --orders=30 --customers=30 --without-image

商品52件・受注900件・会員30件が入りました。商品名は文学作品のテキストから切り出されるので意味不明な文字列になりますが、件数と構造の確認には十分です。

環境構築で3点つまずいたので記録しておきます。

  • macOS では GID を渡すと起動しないgroupmod: GID '20' already exists)。UID だけ渡す
  • Docker でも .env は必要。 無いと DB 設定が届かず、エラーにならずに SQLite へフォールバックします(doctrine.yaml の既定 driver が pdo_sqlite)。症状は「CLI は動くのに Web だけ 500」
  • docker compose exec を root で叩くと Web が 500 になる。 監査ログが root 所有で作られ、Apache が追記できなくなります。-u www-data を付ける(復旧は chown -R www-data:www-data var vendor

ツールは bin/console からも叩ける

MCP クライアントを用意する前に、CLI で全ツールを試せます。11個のツールが 1ツール1コマンドとして生えています。

$ bin/console list eccube
...
  eccube:cli:get_customer            EC-CUBE の会員 ID から会員詳細 (氏名・連絡先・住所など) を取得する。 読み取り専用。 必要 scope: mcp:customer:read。 allow_list に PII が含まれる。
  eccube:cli:get_customer_orders     EC-CUBE の指定会員の購入履歴 (注文一覧) を取得する。 ...
  eccube:cli:get_order               ...
  eccube:cli:search_products         EC-CUBE の商品をキーワード / カテゴリ / 公開ステータス / 在庫数で検索し、商品一覧を返す。 ...

オプションと説明文も MCP の入力スキーマから自動生成されます。

$ bin/console eccube:cli:search_products --help

Options:
      --keyword=KEYWORD        ID または商品名 / 商品コードの部分一致 (任意)
      --categoryId=CATEGORYID  カテゴリ ID。 子カテゴリも対象 (任意)
      --statusIds=STATUSIDS    商品ステータス ID の配列。 1=公開、 2=非公開、 3=廃止 (任意、 既定 [1])
      --stockMin=STOCKMIN      在庫がこの値以上の表示規格を持つ商品のみ (任意)
      --stockMax=STOCKMAX      在庫がこの値以下の表示規格を持つ商品のみ (任意)
      --limit=LIMIT            取得件数。 1〜200 (既定 10)
      --offset=OFFSET          スキップ件数 (既定 0)

実装のコメントには、これが用意された理由が書かれています。

MCP サーバでできる操作を bin/console からも同じように行うための実行口(playwright-mcp に対する playwright-cli の位置づけ)。コンソール AI クライアント (Claude Code 等) が、コンテキストを食う MCP サーバ接続なしに同じツールを叩ける。

検証する側にとっても、MCP クライアントを準備せずに全ツールの挙動を確認できるので早いです。この経路は手元で人が確認するためのもので、後述のトークンは要りません(スコープ検査を通らないため、11個すべて叩けます)。

ただし、本体だけの状態で叩くと出力が空になります

$ bin/console eccube:cli:search_products --limit 3

**total**: 52 ・ **limit**: 3 ・ **offset**: 0

| price | stock |
| --- | --- |
|  |  |
|  |  |
|  |  |

件数(total: 52)は正しいのに中身がありません。エラーも例外も出ません。MCP のツールは「どのフィールドを返してよいか」を自分では持たず、Api44 が定義する allow_list を読んで決めているためです。Api44 が無いと allow_list が 0 件になり、全 Entity が「公開なし」と判定されます。AllowListResolver のコメントに「Api44 が未インストール / 無効化されている場合は allow_list が 0 件、すなわち全 Entity が『公開なし』となり、Tool 出力は空になる (安全側挙動)」と明記されている、仕様どおりの挙動です。

Api44 を入れる

ec-cube/api44 は EC-CUBE 公式の Web API プラグインで、GraphQL の Web API と OAuth2 認証基盤を追加します(リポジトリ開発ドキュメント)。4.4 の MCP は認証(OAuth2)と公開範囲(allow_list)の両方をこのプラグインに委譲しているので、実質的に必須です。

現時点では Packagist から取れないので、GitHub の 4.4 ブランチを clone して path リポジトリとして参照します。本体の CI(.github/workflows/agentic-commerce-e2e.yml)が同じことをしているので、それに倣いました。

git clone --depth 1 --branch 4.4 https://github.com/EC-CUBE/eccube-api4.git repos/api44

# プラグインインストーラが composer.json の extra.id を必須参照するため、ダミー ID を注入する
jq '.extra.id = 990044' repos/api44/composer.json > tmp && mv tmp repos/api44/composer.json

composer config repositories.api44 path /var/www/html/repos/api44
bin/console eccube:composer:require "ec-cube/api44:*"
bin/console eccube:plugin:enable --code=Api44

extra.id は通常ストアの API が供給する値なので、リポジトリを直接 clone したものには入っていません。また clone したリポジトリのデフォルトブランチは 4.2 系で composer 上の名前も旧名(ec-cube/api42)なので、VCS リポジトリとして参照すると名前が一致せず失敗します。path リポジトリにする必要があります。

インストールすると、さきほど空だった出力に値が入ります。

$ bin/console eccube:cli:list_plugins

**total**: 1

| id | name    | code  | enabled | version | source | ... |
| 1  | Web API | Api44 | false   | 4.4.0   | 990044 | ... |

公開してよいプロパティの一覧は、Api44 の Resource/config/services.yaml に Entity ごとにベタ書きされています。

core.api.allow_list:
    class: ArrayObject
    tags: ['eccube.api.allow_list']
    arguments:
        - Eccube\Entity\Customer: ['id', 'name01', 'name02', 'kana01', 'kana02',
              'company_name', 'postal_code', 'addr01', 'addr02', 'email',
              'phone_number', 'birth', 'buy_times', 'buy_total', 'point', ...]
          Eccube\Entity\Category: ['id', 'name', 'hierarchy', 'sort_no', ...]

氏名・メールアドレス・電話番号・生年月日といった個人情報も明示的に列挙されています。GraphQL API で外部に出してよい項目の定義が、そのまま AI に見せてよい項目の定義になります。案件で MCP を有効にするなら、まずこのファイルに目を通すことになります。

MCP トークンを発行する

/admin/mcp は OAuth2 の Bearer トークンで守られています。管理画面にログイン済みのセッションでは通りません(ROLE_ADMIN と MCP のスコープは別物です)。


登場するのは管理者・EC-CUBE・AI クライアントの3者です。EC-CUBE 側は**トークンを発行する画面**(Api44 が追加する)と、**トークンを検証してデータを返す MCP エンドポイント**の2つの顔を持ちます。

トークンを人が用意して AI に渡すだけなので、OAuth2 の認可フローは表に出てきません。

必要なトークンは管理画面から発行できます。Api44 を有効化すると、設定 → API管理 → OAuth管理/admin/api/oauth)に「MCP トークン」の一覧が追加され、そこの「MCP 新規追加」から発行します。

入力するのは3つだけです。

項目 内容
ラベル トークンの用途名。ツールチップの例示が「Claude Desktop 自宅PC」で、想定用途が分かります
スコープ mcp:product:read / mcp:order:read / mcp:customer:read / mcp:plugin:read の4つからチェックボックスで選択
有効期限 30 / 90 / 180 日から選択

「発行する」を押すと JWT が表示されます。表示はこの1回だけで、再表示はできません(画面にも「このトークンは今この画面でのみ表示されます」と出ます)。GitHub の personal access token と同じ扱いです。

発行後、OAuth管理画面にトークンが追加されます。

発行できる権限には作りとして制限がかかっています。

  • read 系スコープしか発行できない。 選択肢が mcp:*:read の4つに限られており、フォームを迂回しても McpTokenService 側で弾かれます
  • 発行には ADMIN 権限が必要。 制限管理者は発行できません。コメントに理由が書かれていて、「stateless な mcp firewall では管理画面の URL 認可が再評価されない。全 scope トークンを発行させると管理画面の権限制限を第二の扉で回避できる」ため、とのことでした
  • トークンの sub は操作した管理者に固定。 フォームから他人を指定することはできません
  • 失効は一覧から即時。 失効すると以降のリクエストは 401 になります

あとはこのトークンを Authorization ヘッダに載せて /admin/mcp に JSON-RPC を投げるだけです。

curl -X POST http://localhost:8080/admin/mcp \
-H "Authorization: Bearer <発行したトークン>" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

MCP クライアントが自分で認可を受ける経路もある

手で発行する以外に、MCP クライアント自身に OAuth2 の認可フローを踏ませる道も用意されています。こちらは登場人物が増えて、リソースサーバ(データを持っている /admin/mcp)と認可サーバ(トークンを発行する /admin/authorize と /token)が役割として分かれます。実体はどちらも同じ EC-CUBE です。

ディスカバリを叩くと、認可サーバの在り処と必要なスコープが返ります。

$ curl http://localhost:8080/.well-known/oauth-protected-resource
{"resource":"http://localhost:8080/admin/mcp",
 "authorization_servers":["http://localhost:8080"],
 "scopes_supported":["mcp:product:read","mcp:order:read","mcp:customer:read","mcp:plugin:read"],
 "bearer_methods_supported":["header"]}

/.well-known/oauth-authorization-server のほうには registration_endpoint もあり、動的クライアント登録(RFC 7591)に対応しています。クライアントが自分を登録し、認可コードを受け取ってトークンに交換する、という MCP の認可仕様どおりの流れに乗れる作りです。未認証で /admin/mcp を叩くと、その入口を教える 401 が返ります。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
  resource_metadata="http://localhost:8080/.well-known/oauth-protected-resource",
  scope="mcp:product:read mcp:order:read mcp:customer:read mcp:plugin:read"

AI エージェントを繋いでみる

トークンが手に入ったので、Claude Haikuに MCP クライアントの役をやってもらうことにしました。

使ったトークンは商品と受注の2スコープだけを持つものです。会員とプラグインのスコープは意図的に与えていません。

やりとりをそのまま載せます。

投げた指示

EC-CUBE 4.4 の MCP サーバに、**HTTP の MCP プロトコル経由**で接続して店舗データを調べてください。あなたは「MCP クライアントとして振る舞う AI エージェント」の役です。

## 環境

- MCP エンドポイント: `http://127.0.0.1:8080/admin/mcp`
- 作業ディレクトリ: (略)
- **アクセストークンは取得済みで `token.txt` に入っています**。`TOKEN=$(cat token.txt)` で読んでください。有効期限は取得から3600秒です

このトークンに付与されているスコープは **`mcp:product:read` と `mcp:order:read` の2つだけ**です。`mcp:customer:read` と `mcp:plugin:read` は**持っていません**。これは意図的な設定です。

## 手順

すべてのリクエストに以下のヘッダが必要です。

```
Authorization: Bearer <token>
Content-Type: application/json
Accept: application/json, text/event-stream
```

### 1. initialize

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"haiku-agent","version":"1.0"}}}
```

**レスポンスヘッダの `Mcp-Session-Id` を必ず控えてください**(`curl -D headers.txt` などで保存)。以降のリクエストには `Mcp-Session-Id: <値>` ヘッダが必須です。

(ヘッダ抽出のコツについて数行、略)

### 2. initialized 通知(202 が返ります)

```json
{"jsonrpc":"2.0","method":"notifications/initialized"}
```

### 3. 調べる

`tools/list` でツール一覧、`tools/call` で呼び出しです。

```json
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"<ツール名>","arguments":{"limit":3}}}
```

以下を順に調べてください。

1. **`tools/list` に何個のツールが見えるか。ツール名を全部挙げてください**(これが最重要です)
2. 商品を検索して、代表的なものを3つ、名前と価格を挙げてください
3. 在庫が10個以下の商品はあるか(`search_products` に `stockMax` 引数があります)
4. 直近の受注を3件、注文番号・金額・ステータスとともに
5. **`search_customers` を呼んでみてください**。これはスコープを持っていないツールです。返ってきた JSON をそのまま報告してください

## 制約

**MCP プロトコル経由(curl での JSON-RPC)だけを使ってください。** `bin/console` を叩いたり、データベースを直接見たり、ソースコードを読んだりしないでください。MCP クライアントとして見える範囲だけで調べるのが目的です。

エラーが出ても、それ自体が検証結果として重要なので、諦めずに**何が返ってきたかを正確に記録**してください。

## 報告してほしいこと

- 各問いへの答え。**データが取れたなら具体的な値**を
- **問い1と問い5の結果は特に正確に**(返ってきた JSON をそのまま貼ってください)
- 取れなかった場合は、投げた JSON と返ってきた JSON の両方を
- MCP クライアントとして使ってみた感想(レスポンス形式、情報の過不足、CLI 版との違いなど)

推測で埋めず、実際に返ってきたものだけを報告してください。

返ってきた答え

以下はエージェントの回答をそのまま貼ったものです(見出しの階層だけ下げています。末尾の保存ファイル一覧は省略)。


調査が完了しました。EC-CUBE 4.4 の MCP サーバに HTTP の MCP プロトコル経由で接続し、すべての手順を実行しました。

問1: tools/list に見えるツール数と名前【最重要】

答え: 6個のツール

  1. get_order
  2. get_product_stock
  3. get_product
  4. get_shipping
  5. search_orders
  6. search_products

search_customerssearch_plugin は tools/list に表示されていません(スコープ未付与のため)。

問2: 代表的な商品3個 – 名前と価格

サーバには合計52商品が存在。最初の3件:

ID 商品名 価格 在庫
52 むねをうごとい、すってずうっ。 2,808.00~6,834.00円 447~680個
51 僕ぼくのですね」鳥捕りですね。と思いなようにいいっぱりそれは。 2,843.00~3,502.00円 687~809個
50 まじっと鳥の島しました丶ただいただしのよう」カムパネルラもい、みんなもの太陽だいだろうとしばらく線路。 1,033.00~4,450.00円 351~640個

(人間註: 商品名が意味不明なのはeccube:fixtures:generatenの作るダミーデータの特徴です。宮沢賢治からなんか引用して生成してるみたいですね)


問3: 在庫10個以下の商品

答え: 0件stockMax: 10 で検索後、結果なし)

問4: 直近の受注3件

注文番号 注文日時 支払合計 ステータス
712-2912618-3769021 2026-04-25 06:11:18 29,727.00円 対応中
112-8761576-4146718 2026-06-15 23:26:33 2,980.00円 対応中
270-5691482-8789380 2026-05-19 06:01:46 35,758.00円 返品

サーバ上には合計669件の注文が存在。

問5: search_customers 呼び出し結果【最重要】

投げたリクエスト JSON:

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "search_customers",
    "arguments": {
      "limit": 3
    }
  }
}

返ってきたレスポンス JSON(完全形):

{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Insufficient scope: mcp:customer:read"
      }
    ],
    "isError": true
  }
}

結論: スコープベースのアクセス制御が正常に機能している。search_customers はトークンに mcp:customer:read スコープがないため、tools/list に表示されず、呼び出し時に権限エラーが返される。

MCP クライアント使用感

【プロトコル形式】

  • JSON-RPC 2.0 標準準拠
  • HTTP POST でリクエスト/レスポンス全て JSON
  • セッションID(Mcp-Session-Id)はヘッダで管理
  • リクエストIDで複数の非同期リクエスト対応可

【情報の充実度】

  • 商品情報: ID, 名前, 価格レンジ, 在庫レンジ → ビジネス判断に十分
  • 受注情報: 注文ID, 注文番号, 注文日時, 支払合計, 顧客名, メール, ステータス → 十分だが PII を含む

【CLI版との大きな違い】

  • HTTP MCP: スコープ制御が厳格に機能(これが最大の差)
  • CLI版: 管理者実行のため制御が緩い
  • HTTP MCP: ネットワーク遅延あり(JSON-RPC ID で非同期処理対応)
  • CLI版: 低遅延

【エラーハンドリング】

  • HTTP 200 でも isError: true で権限エラーと判定する必要がある
  • エラーメッセージは content[0].text に文字列で含まれる
  • 正常系は structuredContent に構造化データ

(エージェントからの回答ここまで)

 

やりとりから見えたこと

  • AI から見えているツールは、トークンに設定したスコープと一致しています。 権限のないツールは一覧に出ず、名前を指定して呼んでも拒否される。AI に何を見せるかはプロンプトの指示ではなくトークンで決まります
  • 拒否は AI が読める形で返ります。 HTTP 200 + isError: true + 不足しているスコープ名。エージェント自身が「HTTP 200 でも isError: true で権限エラーと判定する必要がある」とレポートに書いています。サーバ側の監査ログには mcp.security.scope_denied が残りました
  • 受注スコープだけでも顧客の氏名・連絡先は見えます(エージェントの「十分だが PII を含む」)。権限設計のときに意識すべき点です

やりとりには出てきませんが、補足を2つ。

initialize のレスポンスで、サーバは自分をこう名乗っていました。

"serverInfo":{"name":"EC-CUBE MCP Server","version":"4.4.0",
  "description":"EC-CUBE 4.4の管理データ (商品/在庫・注文・顧客会員・プラグイン管理) を
    AIクライアントから自然言語で参照する読み取り専用MCP サーバ。
    認証認可はAPIプラグイン (api44) のOAuth2 / scope に委譲する。"}

ツール個別の説明文も AI 向けで、必要なスコープと PII の警告が入っています。

EC-CUBE の注文 ID または注文番号から注文詳細 (明細 / 配送状況 / 支払状況等) を取得する。読み取り専用。必要 scope: mcp:order:read。allow_list に氏名・住所等の PII が含まれ得る。

別途 CLI 版のツール11個を全部叩かせたときには、出力量についての指摘が返ってきました。「get_order で OrderItems の完全データが埋め込まれるとテーブル行が極めて長くなる」「関連エンティティが時に ID のみの参照、時に完全なデータで返される(統一性がない)」。--lean のような出力量を制御する仕組みが欲しい、という提案付きでした。AI にとって出力の冗長さはそのままコンテキスト消費になります。

本番で有効にするなら TRUSTED_HOSTS が必須

Api44 の README に明記されています。

MCP サーバ機能を本番で利用する場合、環境変数 TRUSTED_HOSTSTRUSTED_PROXIES の設定が必須です。

OAuth ディスカバリの resource_metadata などの URL はリクエストの Host 名から組み立てます。TRUSTED_HOSTS が未設定だと Host ヘッダを偽装され、クライアントを攻撃者の認可サーバへ誘導される恐れがあります。

  • TRUSTED_HOSTS — 公開ドメインに一致する正規表現(例: ^(www\.)?example\.com$
  • TRUSTED_PROXIES — リバースプロキシ配下ではプロキシの IP レンジ

未設定のまま本番で MCP メタデータを配信すると警告ログが出ます。MCP を有効にする案件では、.env の設定を作業項目に必ず入れる必要があります。

人間による感想

MCP が入ったので触ってみるべえとなったのでしたが、実態としては既存のAPIをMCPで見れるようになったよ〜という感じみたいですね。認証とか公開範囲みたいな部分は4.2ぐらいからあったOAuth2の管理からできるっぽいので、スマートな導入なんじゃないでしょうか。
今のところreadしか用意されてないので、利用範囲は限定的かなあと思います。本番で使うんだったら、ストアオーナーのnotionみたいなところから叩けるようにして分析に活用とかでしょうかね??

めちゃくちゃ長い記事になってしまいましたが、セキュリティ周りの部分で言及しておいた方が良さそうな部分とかもありますので、近日中に別の記事で出せたらいいなあと思っております。

正式版のリリースは 2026年10月下旬の予定です!


本記事の検証は 2026-09-08 時点の EC-CUBE 4.4 ブランチ(efa640d)および eccube-api4 の 4.4 ブランチを、同梱の docker-compose 構成(PHP 8.2.33 / Apache 2.4 / PostgreSQL 18)で行いました。いずれも開発中であり、正式版で挙動が変わる可能性があります。