Skip to content

Commit 5ba44a2

Browse files
feat(mcp): serve HTML landing page on browser GET to MCP endpoint (#1072)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 9369864 commit 5ba44a2

1 file changed

Lines changed: 30 additions & 4 deletions

File tree

airbyte/mcp/http_main.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import os
3838
from urllib.parse import urlparse
3939

40+
from fastmcp_extensions import register_landing_page
41+
4042
from airbyte.mcp.server import (
4143
DEFAULT_HTTP_HOST,
4244
DEFAULT_HTTP_PORT,
@@ -47,6 +49,18 @@
4749

4850
logger = logging.getLogger(__name__)
4951

52+
# Human-facing landing page shown when a browser GETs the MCP endpoint.
53+
MCP_LANDING_TITLE = "Airbyte MCP Server"
54+
MCP_LANDING_DOCS_URL = "https://docs.airbyte.com/ai-agents/"
55+
56+
57+
def _get_server_url() -> str:
58+
"""Return the public base URL from `MCP_SERVER_URL`, defaulting to localhost."""
59+
return os.getenv(
60+
MCP_SERVER_URL_ENV,
61+
f"http://localhost:{DEFAULT_HTTP_PORT}",
62+
)
63+
5064

5165
def main() -> None:
5266
"""Start the Airbyte MCP server with HTTP transport."""
@@ -55,12 +69,24 @@ def main() -> None:
5569
# When deployed behind a path-stripping LB (MCP_SERVER_URL has a path
5670
# component like /cloud-mcp), serve the MCP endpoint at root so the
5771
# public URL is just the base path. Otherwise keep the FastMCP default.
58-
server_url = os.getenv(
59-
MCP_SERVER_URL_ENV,
60-
f"http://localhost:{DEFAULT_HTTP_PORT}",
61-
)
72+
server_url = _get_server_url()
6273
mcp_path = "/" if urlparse(server_url).path.strip("/") else "/mcp"
6374

75+
# The advertised endpoint must match where the MCP route is actually mounted:
76+
# the bare server URL when mounted at root, otherwise the server URL + mcp_path.
77+
endpoint_url = server_url if mcp_path == "/" else server_url.rstrip("/") + mcp_path
78+
79+
# Serve a browser-friendly landing page on GET at the MCP path. In stateless
80+
# mode FastMCP only binds POST/DELETE there, so this GET route does not
81+
# interfere with MCP traffic.
82+
register_landing_page(
83+
app,
84+
path=mcp_path,
85+
title=MCP_LANDING_TITLE,
86+
endpoint_url=endpoint_url,
87+
docs_url=MCP_LANDING_DOCS_URL,
88+
)
89+
6490
if getattr(app, "auth", None) is None:
6591
logger.warning(
6692
"HTTP transport starting without authentication: no interactive "

0 commit comments

Comments
 (0)