Skip to content

feat(mcp): browser hello page with working middleware and config-driven content#40471

Merged
aminghadersohi merged 10 commits into
apache:masterfrom
aminghadersohi:amin/mcp-hello-page
May 28, 2026
Merged

feat(mcp): browser hello page with working middleware and config-driven content#40471
aminghadersohi merged 10 commits into
apache:masterfrom
aminghadersohi:amin/mcp-hello-page

Conversation

@aminghadersohi
Copy link
Copy Markdown
Contributor

@aminghadersohi aminghadersohi commented May 27, 2026

SUMMARY

Adds a browser-friendly hello page for the MCP endpoint. When a user opens the MCP URL in a browser, they see a styled HTML page with setup instructions instead of a 405 Method Not Allowed.

Implementation: BrowserHelloMiddleware (BaseHTTPMiddleware) intercepts GET/HEAD requests with a browser Accept: text/html header before they reach FastMCP's router. Wired in server.py for both single-pod (mcp.run()) and multi-pod (mcp.http_app() + uvicorn) paths.

Features:

  • Auth-aware config snippet: omits Authorization header when auth is off, includes it when auth is on
  • Branding from Flask config: title and description use APP_NAME; logo shown if APP_ICON is an absolute URL or if SUPERSET_WEBSERVER_ADDRESS is set (used to resolve relative icon paths)
  • Config-driven via MCP_HELLO_PAGE dict — deployments can override title, server_key, show_transport, clients, logo_url, and app_name

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Screenshot 2026-05-27 at 1 42 18 PM

Before: 405 Method Not Allowed for any browser GET to /mcp

After: 200 HTML page with setup instructions. Config snippet adapts to auth state:

  • Auth off: {"url": "<this-url>", "transport": "streamable-http"}
  • Auth on: includes "Authorization": "Bearer <your-api-key>"

TESTING INSTRUCTIONS

  1. Start MCP server: superset mcp run --port 5008 --debug
  2. Browser visit http://localhost:5008/mcp — should show the HTML hello page
  3. Browser visit http://localhost:5008/ — should also show the hello page
  4. curl -s -H "Accept: text/html,*/*" http://localhost:5008/mcp — should return 200 HTML
  5. curl -s -H "Accept: application/json" http://localhost:5008/mcp — should return 405 (unaffected)
  6. Set MCP_AUTH_ENABLED = True in config, restart — config snippet should include Authorization header
  7. Set MCP_HELLO_PAGE = {"title": "My MCP", "server_key": "my-superset"} — page should reflect overrides

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

aminghadersohi and others added 8 commits May 27, 2026 11:44
When a browser opens the MCP endpoint (Accept: text/html without
application/json or text/event-stream), return a 200 HTML page
explaining what the endpoint is and how to configure it in Claude
Desktop, Claude Code, or Cursor. API and SSE clients continue to
receive the existing JSON 401 response unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…HEAD

- Introduce MCPJWTVerifier(JWTVerifier) base class that registers
  _auth_error_handler as the Starlette on_error callback; previously the
  callback was only wired inside DetailedJWTVerifier (MCP_JWT_DEBUG_ERRORS=True),
  so the HTML page was never shown in the default configuration
- mcp_config.py non-debug path now uses MCPJWTVerifier instead of bare
  JWTVerifier; DetailedJWTVerifier inherits MCPJWTVerifier
- Add _prefers_browser_html() helper: checks method (GET/HEAD only) and
  Accept header (case-insensitive); prevents POST/OPTIONS with text/html
  from incorrectly receiving a 200 HTML response
- Rename _json_auth_error_handler -> _auth_error_handler, return type
  narrowed to Response (Starlette base class, matching on_error signature)
- Add tests: POST+text/html -> 401, HEAD+text/html -> 200, uppercase Accept

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The PR renamed _json_auth_error_handler to _auth_error_handler in
jwt_verifier.py (to reflect that it now returns HTML for browsers
rather than always JSON), but test_jwt_verifier.py still imported
the old name, causing a collection-time ImportError that failed all
unit tests.
…ig-driven page

- Replace on_error hook approach (dead code — BearerAuthBackend never raises
  AuthenticationError) with a Starlette BaseHTTPMiddleware that intercepts
  browser GET/HEAD requests before FastMCP's router returns 405
- Works regardless of MCP_AUTH_ENABLED — no longer requires auth to be on
- Auth-aware: omits Authorization header from config snippet when auth is off
- Config-driven via MCP_HELLO_PAGE dict: override title, server_key,
  show_transport, and clients list per deployment
- Escape server_key via superset.utils.json.dumps() to prevent invalid JSON
  in the config snippet when the key contains quotes or backslashes
- HTML-escape title and client names to prevent XSS from crafted MCP_HELLO_PAGE config
- Restrict BrowserHelloMiddleware to the MCP path only (default /mcp) so
  unrelated routes return their real 404/405 instead of the hello page
- Derive auth_enabled from the actual auth provider returned by
  _create_auth_provider() so MCP_AUTH_FACTORY deployments show the correct
  Authorization header even when MCP_AUTH_ENABLED is False
Derive title, server_key, and description app name from APP_NAME config.
Include logo from APP_ICON if it is an absolute URL. All values are
overridable per-deploy via MCP_HELLO_PAGE config dict.
…SET_WEBSERVER_ADDRESS

Root "/" now also triggers the browser hello page, not just "/mcp".
Logo URL is constructed from SUPERSET_WEBSERVER_ADDRESS + APP_ICON when
APP_ICON is a relative path (the Superset default).
BaseHTTPMiddleware only runs for routes FastMCP registers (/mcp), so
the path check was never triggered for /. Remove the path restriction
and rely on the Accept header check alone — MCP/API clients never
send Accept: text/html so they are unaffected.
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 27, 2026

Code Review Agent Run #5fddba

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: 1db710d..fd4524e
    • superset/mcp_service/jwt_verifier.py
    • superset/mcp_service/mcp_config.py
    • superset/mcp_service/server.py
    • tests/unit_tests/mcp_service/test_jwt_verifier.py
    • tests/unit_tests/mcp_service/test_jwt_verifier_browser_hello.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@dosubot dosubot Bot added the change:backend Requires changing the backend label May 27, 2026
Comment thread superset/mcp_service/server.py Outdated
@bito-code-review
Copy link
Copy Markdown
Contributor

The PR comment indicates a logic error where the MCP hello page's authentication hint is derived from Flask config flags instead of the actual runtime auth configuration. This can lead to incorrect instructions for users regarding the inclusion of Authorization headers. To resolve this, the auth_enabled flag should be derived from the effective runtime auth configuration (the instantiated provider/factory config auth) rather than just from the presence of config flags. This change will ensure the hello page accurately reflects the actual authentication state of the running MCP app.

…not config flags

MCP_AUTH_FACTORY may return None (auth disabled at runtime despite the
factory being configured). use_factory_config auth lives in factory_config,
not Flask config at all. Both cases produced a wrong auth hint on the
hello page. Fix: set auth_provider in both run_server branches (factory
path reads factory_config["auth"]) and drop the config-flag fallback
from _build_starlette_middleware.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 0% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.13%. Comparing base (fb60662) to head (7533eb2).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/jwt_verifier.py 0.00% 60 Missing ⚠️
superset/mcp_service/server.py 0.00% 26 Missing ⚠️
superset/mcp_service/mcp_config.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40471      +/-   ##
==========================================
- Coverage   64.18%   64.13%   -0.06%     
==========================================
  Files        2592     2592              
  Lines      139257   139381     +124     
  Branches    32335    32361      +26     
==========================================
+ Hits        89385    89392       +7     
- Misses      48337    48452     +115     
- Partials     1535     1537       +2     
Flag Coverage Δ
hive 39.15% <0.00%> (-0.07%) ⬇️
mysql 58.63% <0.00%> (-0.11%) ⬇️
postgres 58.71% <0.00%> (-0.11%) ⬇️
presto 40.83% <0.00%> (-0.08%) ⬇️
python 60.26% <0.00%> (-0.11%) ⬇️
sqlite 58.35% <0.00%> (-0.11%) ⬇️
unit 100.00% <ø> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a browser-friendly “hello” HTML page for Superset’s MCP HTTP endpoint so browser navigations to /mcp (or /) show setup instructions instead of a 405 Method Not Allowed, while preserving RFC 6750-compliant JSON 401 behavior for API clients.

Changes:

  • Introduces BrowserHelloMiddleware (Starlette BaseHTTPMiddleware) to intercept browser-like GET/HEAD requests and return a styled HTML setup page.
  • Updates the default MCP auth factory to use a Superset-specific MCPJWTVerifier / _auth_error_handler that can return HTML for browser navigations and JSON 401 otherwise.
  • Adds unit tests validating content negotiation behavior for _auth_error_handler.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
superset/mcp_service/jwt_verifier.py Adds HTML hello page generation, browser detection logic, middleware, and new auth error handler / verifier base class.
superset/mcp_service/server.py Wires the new Starlette middleware into both mcp.run() and mcp.http_app() paths; derives page config from Flask config.
superset/mcp_service/mcp_config.py Switches the default non-debug auth provider to Superset’s MCPJWTVerifier to enable the new handler/page.
tests/unit_tests/mcp_service/test_jwt_verifier.py Updates existing tests to reference _auth_error_handler instead of the removed _json_auth_error_handler.
tests/unit_tests/mcp_service/test_jwt_verifier_browser_hello.py Adds unit tests for HTML-vs-JSON behavior based on Accept and HTTP method.

Comment thread superset/mcp_service/jwt_verifier.py
Comment thread superset/mcp_service/server.py Outdated
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 27, 2026

Code Review Agent Run #20d94f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: fd4524e..50a70be
    • superset/mcp_service/server.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

- XSS: html.escape() the entire config snippet before embedding in HTML;
  json.dumps() does not escape < / > so a crafted server_key could break
  out of the <pre><code> block. Use raw <> in _build_config_snippet and
  escape the whole string at the call site.
- TypeError: validate MCP_HELLO_PAGE is a dict before merging; log a
  warning and fall back to {} when it is an unexpected type.
@aminghadersohi aminghadersohi merged commit 0dc58d1 into apache:master May 28, 2026
58 checks passed
@bito-code-review
Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

aminghadersohi added a commit to aminghadersohi/superset that referenced this pull request May 29, 2026
The browser-friendly hello page (apache#40471) re-added MCPJWTVerifier to
jwt_verifier.py. Update _build_jwt_verifier to use MCPJWTVerifier for
the non-debug path, matching the current jwt_verifier.py interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants