Skip to content

Commit 86344a2

Browse files
VinciGit00claude
andcommitted
feat: redirect browser visits to /mcp toward the docs
The /mcp path is the live MCP streamable-HTTP endpoint (JSON-RPC POST, SSE GET). Add BrowserRedirectMiddleware (HTTP mode only) that 302-redirects only human browser navigations — GET/HEAD on /mcp with Accept: text/html — to https://docs.scrapegraphai.com/services/mcp-server/introduction, leaving real MCP traffic untouched. Target overridable via MCP_DOCS_URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2143518 commit 86344a2

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

.agent/system/project_architecture.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,18 @@ scrapegraph-mcp
566566
**Server Transport:**
567567
- **stdio** - Standard input/output (default for MCP)
568568
- Communication via JSON-RPC over stdin/stdout
569+
- **http** - Remote deployment (set `MCP_TRANSPORT=http`). Streamable-HTTP endpoint
570+
served at `/mcp`; health check at `/health`. Used by the Render deployment at
571+
`mcp.scrapegraphai.com`.
572+
573+
**Browser redirect (`/mcp`):**
574+
- `/mcp` is the live MCP streamable-HTTP endpoint (JSON-RPC `POST`, SSE `GET`
575+
with `Accept: text/event-stream`).
576+
- `BrowserRedirectMiddleware` (in `server.py`, HTTP mode only) redirects **only**
577+
human browser navigations — `GET`/`HEAD` on `/mcp` (or `/mcp/`) with
578+
`Accept: text/html` — to the docs (`302`). Real MCP traffic is untouched.
579+
- Target is `https://docs.scrapegraphai.com/services/mcp-server/introduction`,
580+
overridable via the `MCP_DOCS_URL` env var.
569581

570582
### Production Considerations
571583

src/scrapegraph_mcp/server.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
from pydantic import AliasChoices, BaseModel, Field
8383
from smithery.decorators import smithery
8484
from starlette.requests import Request
85-
from starlette.responses import JSONResponse
85+
from starlette.responses import JSONResponse, RedirectResponse
8686

8787
# Configure logging
8888
logging.basicConfig(
@@ -94,6 +94,47 @@
9494
# Matches scrapegraph-py v2 (env.py): https://v2-api.scrapegraphai.com/api
9595
DEFAULT_API_BASE_URL = "https://v2-api.scrapegraphai.com/api"
9696

97+
# Where to send humans who open the /mcp endpoint in a browser.
98+
DOCS_URL = os.getenv(
99+
"MCP_DOCS_URL",
100+
"https://docs.scrapegraphai.com/services/mcp-server/introduction",
101+
)
102+
103+
104+
class BrowserRedirectMiddleware:
105+
"""Redirect browser visits to the MCP endpoint to the docs.
106+
107+
The ``/mcp`` path is the real MCP streamable-HTTP endpoint: clients POST
108+
JSON-RPC there and open ``GET`` streams with ``Accept: text/event-stream``.
109+
A person pasting ``https://mcp.scrapegraphai.com/mcp`` into a browser sends
110+
``GET`` with ``Accept: text/html`` instead — that (and only that) is
111+
redirected to the documentation so real MCP traffic is left untouched.
112+
"""
113+
114+
def __init__(self, app, docs_url: str = DOCS_URL) -> None:
115+
self.app = app
116+
self.docs_url = docs_url
117+
118+
async def __call__(self, scope, receive, send) -> None:
119+
if scope["type"] == "http" and self._is_browser_navigation(scope):
120+
response = RedirectResponse(self.docs_url, status_code=302)
121+
await response(scope, receive, send)
122+
return
123+
await self.app(scope, receive, send)
124+
125+
def _is_browser_navigation(self, scope) -> bool:
126+
if scope["method"] not in ("GET", "HEAD"):
127+
return False
128+
if scope["path"].rstrip("/") != "/mcp":
129+
return False
130+
accept = ""
131+
for name, value in scope.get("headers", []):
132+
if name == b"accept":
133+
accept = value.decode("latin-1").lower()
134+
break
135+
# Real MCP SSE streams advertise text/event-stream; browsers ask for HTML.
136+
return "text/html" in accept and "text/event-stream" not in accept
137+
97138

98139
def _api_base_url() -> str:
99140
# SGAI_API_URL mirrors scrapegraph-py v2; SCRAPEGRAPH_API_BASE_URL is a legacy alias.
@@ -2041,7 +2082,14 @@ def main() -> None:
20412082
port = int(os.getenv("PORT", "8000"))
20422083
logger.info(f"Starting ScapeGraph MCP server in HTTP mode on {host}:{port}")
20432084
print(f"Starting ScapeGraph MCP server in HTTP mode on {host}:{port}")
2044-
mcp.run(transport="http", host=host, port=port)
2085+
from starlette.middleware import Middleware
2086+
2087+
mcp.run(
2088+
transport="http",
2089+
host=host,
2090+
port=port,
2091+
middleware=[Middleware(BrowserRedirectMiddleware)],
2092+
)
20452093
else:
20462094
# Local stdio mode (Claude Desktop, Cursor, etc.)
20472095
server_path = os.path.abspath(__file__)

0 commit comments

Comments
 (0)