Skip to content

fix(mcp): make /mcp reachable for remote clients + admin-status card#569

Merged
CybotTM merged 3 commits into
mainfrom
fix/mcp-remote-access
Jul 6, 2026
Merged

fix(mcp): make /mcp reachable for remote clients + admin-status card#569
CybotTM merged 3 commits into
mainfrom
fix/mcp-remote-access

Conversation

@CybotTM

@CybotTM CybotTM commented Jul 5, 2026

Copy link
Copy Markdown
Member

End-to-end testing of the deployed Phase 5 MCP server (#567) revealed the /mcp endpoint 403s every remote client — the direct tool tests bypass the HTTP layer, so they missed it. Two distinct causes, plus the missing admin-status card.

/mcp was 403 for valid tokens

  1. Fail-closed subscriber. RequireScopeSubscriber denies a token request to any controller without #[RequireScope]. MCP multiplexes many per-tool scopes (enforced in each tool by ScopeGuard), so a single controller-level scope can't express it. Added a SelfEnforcesScope marker the subscriber honours to skip the gate for controllers that enforce scopes per call.
  2. DNS-rebinding host guard. The SDK's StreamableHttpTransport hardcodes allowedHosts=[localhost], 403ing a real domain; the bundle's McpController is final with no config. Override mcp.server.controller with McpEndpointController that rebuilds the secure middleware stack (CORS + DNS-rebinding + protocol-version) with the deployment's host(s) allowlisted via MCP_ALLOWED_HOSTS.

New functional test drives the full HTTP stack (firewall → controller → transport): initialize succeeds for an allowed host, disallowed host → 403, bogus token → 401.

Admin status

Added the missing MCP subsystem card to /ui/admin/status (endpoint, transport, live tool count via a tagged locator, per-tool auth) + i18n across all five locales.

Deploy note

Prod must set MCP_ALLOWED_HOSTS=tt.netresearch.de (the mounted env shadows .env).

All four PHP gates green; 27 backend + 418 frontend tests pass.

CybotTM added 2 commits July 6, 2026 01:56
End-to-end testing of the deployed endpoint surfaced two 403s the direct tool
tests (which bypass the HTTP layer) missed:

1. RequireScopeSubscriber fail-closed denied /mcp because the bundle controller
   declares no #[RequireScope]. But MCP multiplexes many per-tool scopes
   (enforced by ScopeGuard), so a single controller scope can't express it. Add
   a SelfEnforcesScope marker the subscriber honours to skip the fail-closed
   gate for controllers that enforce scopes per call.
2. The SDK's StreamableHttpTransport hardcodes the DNS-rebinding guard to
   allowedHosts=[localhost], 403ing a real domain. The bundle's McpController is
   final and exposes no config, so override mcp.server.controller with
   McpEndpointController that rebuilds the secure middleware stack with the
   deployment's host(s) allowlisted (MCP_ALLOWED_HOSTS / app.mcp_allowed_hosts).

Functional test drives the full HTTP stack (firewall + controller + transport):
initialize succeeds for an allowed host, a disallowed host is 403, a bogus token
is 401.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
… count)

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings July 5, 2026 23:57
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes remote-client access to the Symfony MCP Streamable HTTP endpoint at /mcp by bypassing controller-level scope gating (while keeping per-tool scope enforcement) and by making the SDK’s DNS-rebinding Host protection configurable for real deployment hostnames. It also adds an MCP subsystem card to the admin status UI with full i18n coverage.

Changes:

  • Introduce a SelfEnforcesScope marker and update RequireScopeSubscriber to allow explicitly-marked controllers to enforce scopes per call (needed for MCP tool multiplexing).
  • Replace the bundle’s final MCP controller with McpEndpointController, rebuilding the transport middleware stack with configurable MCP_ALLOWED_HOSTS.
  • Add an end-to-end functional test for /mcp (allowed host OK, disallowed host 403, bogus token 401) and add an MCP subsystem card (+ translations) to /ui/admin/status.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Mcp/McpHttpEndpointTest.php New functional test exercising the full HTTP /mcp stack (auth + host guard + transport).
tests/Controller/GetStatusActionTest.php Extends subsystem-card expectations to include the new mcp card.
src/Security/ApiToken/SelfEnforcesScope.php Adds an explicit marker interface for controllers that enforce scopes internally/per call.
src/Security/ApiToken/RequireScopeSubscriber.php Skips the fail-closed #[RequireScope] gate for SelfEnforcesScope controllers.
src/Mcp/McpEndpointController.php New /mcp controller with configurable DNS-rebinding allowed hosts.
src/Controller/Admin/GetStatusAction.php Adds MCP subsystem card (endpoint, transport, tool count, auth model).
frontend/src/pages/AdminStatus.tsx Adds UI label/description entries for the MCP subsystem.
frontend/messages/{en,de,es,fr,ru}.json Adds localized strings for the MCP subsystem label/description.
config/services.yaml Adds app.mcp_allowed_hosts parameter and overrides mcp.server.controller with McpEndpointController.
.env Adds default MCP_ALLOWED_HOSTS configuration and documentation.

Comment thread src/Mcp/McpEndpointController.php
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.38710% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.22%. Comparing base (1de605f) to head (6f85d89).

Files with missing lines Patch % Lines
src/Mcp/McpEndpointController.php 0.00% 15 Missing ⚠️
src/Security/ApiToken/RequireScopeSubscriber.php 80.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #569      +/-   ##
============================================
- Coverage     83.34%   83.22%   -0.13%     
- Complexity     3332     3337       +5     
============================================
  Files           236      237       +1     
  Lines          9061     9091      +30     
============================================
+ Hits           7552     7566      +14     
- Misses         1509     1525      +16     
Flag Coverage Δ
integration 54.81% <48.38%> (-0.03%) ⬇️
unit 47.21% <0.00%> (-0.16%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@CybotTM CybotTM merged commit c1454c7 into main Jul 6, 2026
26 checks passed
@CybotTM CybotTM deleted the fix/mcp-remote-access branch July 6, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants