Files
Pietro Zullo 30b23271d5 fix(python): respect configured inspector path (#1176)
* fix(python): respect configured inspector path

* fix(python): treat inspector path as a base prefix

* fix(python): format inspector fixes for ruff

* fix(ci): run conformance with node 22

* fix(ci): fail conformance runs with empty results

* chore(ci): drop inline conformance result validation

* docs(python): clarify inspector path behavior
2026-03-09 14:24:53 +01:00

88 lines
3.5 KiB
Python

import logging
import os
from urllib.parse import urlencode
import httpx
from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse, Response
logger = logging.getLogger(__name__)
# Override with INSPECTOR_CDN_BASE_URL (e.g. http://localhost:2967) for E2E or local dev
INSPECTOR_CDN_BASE_URL = os.environ.get(
"INSPECTOR_CDN_BASE_URL",
"https://unpkg.com/@mcp-use/inspector@latest/dist/web",
).rstrip("/")
INDEX_URL = f"{INSPECTOR_CDN_BASE_URL}/index.html"
async def _inspector_index(
request: Request,
mcp_path: str = "/mcp",
inspector_path: str = "/inspector",
):
"""Serve the inspector index.html file with autoconnect parameter."""
# Get the server URL from the request
server_url = f"{request.url.scheme}://{request.url.netloc}{mcp_path}"
# Check if server or autoConnect parameter is already present
server_param = request.query_params.get("server")
autoconnect_param = request.query_params.get("autoConnect")
if not server_param and not autoconnect_param:
# Redirect to add the autoConnect parameter (note: capital C)
autoconnect_url = (
f"{request.url.scheme}://{request.url.netloc}{inspector_path}?{urlencode({'autoConnect': server_url})}"
)
return RedirectResponse(url=autoconnect_url, status_code=302)
# Fetch the CDN file
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(INDEX_URL, follow_redirects=True)
if response.status_code == 200:
html = response.text.replace('src="/inspector/assets/', f'src="{INSPECTOR_CDN_BASE_URL}/assets/')
html = html.replace('href="/inspector/assets/', f'href="{INSPECTOR_CDN_BASE_URL}/assets/')
return HTMLResponse(html)
else:
logger.warning(
f"Failed to fetch inspector from CDN: {INDEX_URL} returned status {response.status_code}"
)
except Exception as e:
logger.exception(f"Failed to fetch inspector from CDN: {INDEX_URL} - {e}")
# CDN failed - return error page (no redirect to avoid loop)
return HTMLResponse(
content=f"""
<html>
<head><title>Inspector Unavailable</title></head>
<body style="font-family: sans-serif; padding: 2rem;">
<h1>Inspector CDN Unavailable</h1>
<p>Could not load the inspector from CDN: <code>{INDEX_URL}</code></p>
<p>The <code>index.html</code> file may be missing from the npm package.</p>
<p style="margin-top: 2rem;">Server URL: <code>{server_url}</code></p>
</body>
</html>
""",
status_code=503,
)
async def _inspector_static(request: Request):
"""Serve static files from the CDN."""
path = request.path_params.get("path", "")
cdn_url = f"{INSPECTOR_CDN_BASE_URL}/{path}"
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(cdn_url, follow_redirects=True)
if response.status_code == 200:
return Response(content=response.content, media_type=response.headers.get("Content-Type", "text/plain"))
else:
logger.warning(
f"Failed to fetch static file from CDN: {cdn_url} returned status {response.status_code}"
)
except Exception as e:
logger.exception(f"Failed to fetch static file from CDN: {cdn_url} - {e}")
return HTMLResponse("File not found", status_code=404)