Skip to content
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4e797dd
chore: apply Rector to codeception fixture BundleCompilerPass
dotani1111 Jun 4, 2026
d0787f8
feat: MCP サーバ土台と商品/在庫 3 ツールを追加
dotani1111 Jun 4, 2026
0527b4c
test(mcp): 商品/在庫 3 Tool の DB 結合テスト
dotani1111 Jun 4, 2026
bf61603
fix(mcp): EntityArraySerializer の Doctrine Proxy 対応 + 深さデフォルトを 1 に
dotani1111 Jun 8, 2026
e708261
feat(mcp): 注文領域 3 ツールを追加 (search_orders / get_order / get_shipping)
dotani1111 Jun 8, 2026
2801d7a
feat(mcp): 顧客会員領域 3 ツールを追加 (search_customers / get_customer / get_cus…
dotani1111 Jun 8, 2026
28e78a0
feat(mcp): プラグイン管理 2 ツールを追加 (list_plugins / get_plugin)
dotani1111 Jun 8, 2026
4722657
fix(mcp): scope 不足を ToolCallException で返す
dotani1111 Jun 8, 2026
273096f
fix(mcp): Get 系 Tool の不在応答を {"found": false} に統一
dotani1111 Jun 8, 2026
80396f7
feat(mcp): IP / client_id 2 段の Rate Limiter を追加
dotani1111 Jun 8, 2026
7ed1f07
test(mcp): tools/list と allow_list の契約テストを追加
dotani1111 Jun 9, 2026
83cea02
feat(mcp): mcp チャネル直書きを禁止する PHPStan custom rule を追加
dotani1111 Jun 9, 2026
3cf35d6
test(mcp): MCP firewall が oauth2 経路を通ることを統合テスト化
dotani1111 Jun 9, 2026
03fc2c2
test(mcp): 失効 / Member 無効化で即 401 をテスト化 (AC #6 #9)
dotani1111 Jun 9, 2026
16e1b0f
test(mcp): Api44 install/enable で MCP 依存登録を担保 (AC #7)
dotani1111 Jun 9, 2026
2e1f316
refactor(mcp): scope 検査を中央 ReferenceHandler 層に集約 (設計案 A)
dotani1111 Jun 9, 2026
8dc4c9a
refactor(mcp): 監査ログの単一入口を alias 削除で保証 (PHPStan rule 撤去)
dotani1111 Jun 9, 2026
2ba2692
fix(mcp): Rate Limiter を cache 障害時 fail-closed にする
dotani1111 Jun 9, 2026
962cd6e
test(mcp): 正常 JWT 受理を 200 + JSON-RPC result で検証
dotani1111 Jun 9, 2026
9d73280
test(mcp): 全 Tool が McpToolScopeMap に scope を持つ契約テストを追加
dotani1111 Jun 10, 2026
80c89d4
fix(mcp): 監査ログ alias 削除の空振りを build 失敗で検出する
dotani1111 Jun 10, 2026
e319562
test(mcp): scope 強制の配線を回帰テストで担保
dotani1111 Jun 10, 2026
ac42582
fix(mcp): Rate Limiter の監査ログ失敗で拒否レスポンスを崩さない
dotani1111 Jun 10, 2026
3a4ca7e
fix(mcp): 監査ログ失敗時に default チャンネルへフォールバック記録する
dotani1111 Jun 10, 2026
92c7d03
test(mcp): 401 の WWW-Authenticate と経路を検証する
dotani1111 Jun 10, 2026
05f2d28
fix(mcp): 監査ログを専用ファイル mcp.log に分離する
dotani1111 Jun 11, 2026
eca5f3d
test(mcp): 監査ログが mcp.log に分離され site.log に漏れないことを担保
dotani1111 Jun 11, 2026
ddd1fd0
feat(mcp): 認証失敗(401)を mcp.log に記録する
dotani1111 Jun 11, 2026
c264a87
test(mcp): 認証失敗ログの reason 分岐 (ヘッダ有無) を検証
dotani1111 Jun 11, 2026
aca6597
Merge branch '4.4' into feature/poc-mcp
dotani1111 Jun 12, 2026
6b3b383
fix(mcp): 関連 Entity 要約の id 露出を allow_list で塞ぐ
dotani1111 Jun 14, 2026
05c29cf
fix(mcp): Tool の検索キーを正規化し日付書式を検証する
dotani1111 Jun 14, 2026
73228c8
fix(mcp): Tool 結果の data 契約違反を内部エラーとして扱う
dotani1111 Jun 14, 2026
2c610fe
test(mcp): 検索系テストを作成データの出現まで検証する
dotani1111 Jun 14, 2026
fa5fad4
style(mcp): 型注釈と重複する @param mixed $level を除去する
dotani1111 Jun 14, 2026
3ccae9a
fix(mcp): 本体を Api44/league クラスへのハード依存から切り離す
dotani1111 Jun 14, 2026
525091b
test(mcp): Api44 前提テストを mcp グループに分離し専用 CI ジョブで実走
dotani1111 Jun 14, 2026
1db2254
fix(ci): mcp ジョブの Api44 アーカイブを dist 展開可能な形式にする
dotani1111 Jun 14, 2026
ec85cd4
ci(mcp): Api44 を MCP firewall を含む feat/mcp-server-scorp で導入する
dotani1111 Jun 14, 2026
f0d801d
Merge branch '4.4' into feature/poc-mcp
dotani1111 Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/config/eccube/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Eccube\EccubeBundle::class => ['all' => true],
Symfony\AI\McpBundle\McpBundle::class => ['all' => true],
];
2 changes: 2 additions & 0 deletions app/config/eccube/packages/dev/monolog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ monolog:
level: debug
formatter: eccube.log.formatter.line
max_files: 10
# mcp は専用ハンドラ (mcp) に出すため main(site.log) からは除外する
channels: ['!mcp']
console:
type: console
process_psr_3_messages: false
Expand Down
10 changes: 10 additions & 0 deletions app/config/eccube/packages/http_discovery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory'

http_discovery.psr17_factory:
class: Http\Discovery\Psr17Factory
14 changes: 14 additions & 0 deletions app/config/eccube/packages/mcp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mcp:
app: 'EC-CUBE MCP Server'
version: '4.4.0'
description: 'EC-CUBE 4.4 の管理データ (商品/在庫・注文・顧客会員・プラグイン管理) を AI クライアントから自然言語で参照する読み取り専用 MCP サーバ。 認証認可は API プラグイン (api44) の OAuth2 / scope に委譲する。'
client_transports:
http: true
stdio: true
http:
path: '/%eccube_admin_route%/mcp'
session:
store: file
discovery:
scan_dirs:
- src/Eccube/Service/Mcp/Tool
19 changes: 19 additions & 0 deletions app/config/eccube/packages/mcp_rate_limiter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# MCP サーバの Rate Limiter 設定 (設計 §5「Rate Limiter 連携」)。
#
# 2 段構成:
# - mcp_ip: リモート IP 単位の制限 (firewall 前で消費、 認証エラー連発攻撃にも効く)
# - mcp_client: OAuth2 client_id 単位の制限 (firewall 通過後の OAuth2Token から client_id を取得して消費)
#
# 既定値は PoC レベルの控えめな値。 GA 運用開始時に再評価する。
framework:
rate_limiter:
mcp_ip:
policy: fixed_window
limit: 60
interval: '1 minute'
cache_pool: rate_limiter.cache
mcp_client:
policy: fixed_window
limit: 300
interval: '1 minute'
cache_pool: rate_limiter.cache
14 changes: 13 additions & 1 deletion app/config/eccube/packages/monolog.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
monolog:
channels: ['front', 'admin']
channels: ['front', 'admin', 'mcp']
handlers:
# MCP 監査ログ: PII を含み得るため site.log と分離した専用ファイルに、 1 レコード 1 JSON で出力する。
# fingers_crossed を挟まず info から常時書き出す (監査記録は error 連動で握り潰してはならない)。
# 保管日数は ECCUBE_MCP_LOG_RETENTION_DAYS (既定 90)。 ファイルは所有者/グループのみ読める権限にする。
mcp:
type: rotating_file
path: '%kernel.logs_dir%/%kernel.environment%/mcp.log'
channels: ['mcp']
level: info
formatter: eccube.mcp.log.formatter.json
max_files: '%env(int:ECCUBE_MCP_LOG_RETENTION_DAYS)%'
file_permission: 0640
3 changes: 2 additions & 1 deletion app/config/eccube/packages/prod/monolog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ monolog:
handler: main_rotating_file
excluded_http_codes: [404, 405]
buffer_size: 50
channels: ['!doctrine', '!event', '!php']
# mcp は専用ハンドラ (mcp) に出すため main(site.log) からは除外する
channels: ['!doctrine', '!event', '!php', '!mcp']
main_rotating_file:
type: rotating_file
max_files: 60
Expand Down
3 changes: 3 additions & 0 deletions app/config/eccube/routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ controllers:
customize_controllers:
resource: ../../../app/Customize/Controller
type: attribute
mcp:
resource: .
type: mcp

# prefix: /{_locale}
# prefix: /
Expand Down
25 changes: 25 additions & 0 deletions app/config/eccube/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ parameters:
env(ECCUBE_LOCALE): 'ja'
env(ECCUBE_TIMEZONE): 'Asia/Tokyo'
env(ECCUBE_CURRENCY): 'JPY'
env(ECCUBE_MCP_ALLOWED_ORIGINS): ''
# MCP 監査ログ (mcp.log) の保管日数 (rotating_file の世代数)。 設計 §4.2
env(ECCUBE_MCP_LOG_RETENTION_DAYS): '90'
locale: '%env(ECCUBE_LOCALE)%'
timezone: '%env(ECCUBE_TIMEZONE)%'
currency: '%env(ECCUBE_CURRENCY)%'
Expand All @@ -28,6 +31,9 @@ services:
$shoppingPurchaseFlow: '@eccube.purchase.flow.shopping'
$orderPurchaseFlow: '@eccube.purchase.flow.order'
$_orderStateMachine: '@state_machine.order'
# MCP サーバ用 (path prefix と Origin 許可リスト)
$eccubeAdminRoute: '%eccube_admin_route%'
$mcpAllowedOriginsCsv: '%env(ECCUBE_MCP_ALLOWED_ORIGINS)%'

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Expand Down Expand Up @@ -215,6 +221,25 @@ services:
Eccube\EventListener\RateLimiterListener:
arguments: [ !tagged_locator { tag: 'eccube_rate_limiter' } ]

# MCP: Api44 (ec-cube/api44) の allow_list (`eccube.api.allow_list` タグ) を集約する
Eccube\Service\Mcp\AllowListResolver:
arguments:
$allowLists: !tagged_iterator eccube.api.allow_list

# MCP: scope 強制層。 $inner (本物の Tool 実行器) は McpScopeEnforcementPass が構築する
Eccube\Service\Mcp\ScopeEnforcingReferenceHandler:
arguments:
$inner: '@eccube.mcp.reference_handler.inner'

# MCP: 監査ログとして記録するため、唯一の書き手として設定します。
Eccube\Service\Mcp\McpAuditLogger:
arguments:
$mcpLogger: '@monolog.logger.mcp'

# MCP: 監査ログ (mcp.log) は 1 レコード 1 JSON で出力する (機械可読・設計 §4.2)
eccube.mcp.log.formatter.json:
class: Monolog\Formatter\JsonFormatter

Eccube\Security\PasswordHasher\PasswordHasher:
arguments:
- '%eccube_auth_magic%'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Plugin\Bundle\DependencyInjection\Compiler;

use League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

Expand All @@ -22,8 +23,8 @@ public function process(ContainerBuilder $container)
{
$plugins = $container->getParameter('eccube.plugins.enabled');
if (!in_array('Bundle', $plugins)) {
if ($container->hasDefinition('League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener')) {
$def = $container->getDefinition('League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener');
if ($container->hasDefinition(AddClientDefaultScopesListener::class)) {
$def = $container->getDefinition(AddClientDefaultScopesListener::class);
$def->clearTags();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Plugin\Bundle\DependencyInjection\Compiler;

use League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

Expand All @@ -22,8 +23,8 @@ public function process(ContainerBuilder $container)
{
$plugins = $container->getParameter('eccube.plugins.enabled');
if (!in_array('Bundle', $plugins)) {
if ($container->hasDefinition('League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener')) {
$def = $container->getDefinition('League\Bundle\OAuth2ServerBundle\EventListener\AddClientDefaultScopesListener');
if ($container->hasDefinition(AddClientDefaultScopesListener::class)) {
$def = $container->getDefinition(AddClientDefaultScopesListener::class);
$def->clearTags();
}
}
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"symfony/lock": "^7.4",
"symfony/mailer": "^7.4",
"symfony/maker-bundle": "^1.0",
"symfony/mcp-bundle": "^0.9",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"symfony/monolog-bridge": "^7.4",
"symfony/monolog-bundle": "^3.1",
"symfony/options-resolver": "^7.4",
Expand Down
Loading
Loading