Spaces:
Sleeping
MCP server support
SERPent now speaks two protocols from the same deployment:
| Audience | Endpoint |
|---|---|
| REST clients / Swagger UI | https://<host>/ (docs), POST /serp/..., /scrap/..., /ops/... |
| MCP clients (Claude, Cursor, agents) | https://<host>/mcp β streamable HTTP transport |
What changed
| File | Change |
|---|---|
mcp_server.py |
New. Builds the MCP server from the FastAPI app and mounts it. |
app.py |
Two lines: from mcp_server import mount_mcp_server and mcp = mount_mcp_server(app) after the include_router calls. |
requirements.txt |
Added fastmcp>=3.0,<4. |
Dockerfile |
Unchanged β same image, same port 7860, same CMD. |
How it works
FastMCP.from_fastapi() reads the app's OpenAPI schema and turns every route
into an MCP tool. When a tool is called, the request is dispatched back into
the same FastAPI app in-process over an ASGI transport β no network hop, no
second server, no duplicated business logic. Add an endpoint, get a tool.
The 12 tools generated today:
search search_arxiv search_google_scholar
search_patents search_brave search_bing
search_duck scrap_patent scrap_patents
ops_keyword_search ops_get_patent ops_get_patents_bulk
Tool names come from the Python handler names rather than FastAPI's generated
operation ids, so agents see search_arxiv instead of
search_arxiv_serp_search_arxiv_post. Descriptions and JSON schemas come from
your docstrings and Pydantic models β improving a docstring improves the tool.
To pin a specific tool name, set operation_id="..." on the route decorator.
Connecting a client
Claude Code / any streamable-HTTP client:
claude mcp add --transport http serpent https://<your-space>.hf.space/mcp
.mcp.json / claude_desktop_config.json:
{
"mcpServers": {
"serpent": {
"type": "http",
"url": "https://<your-space>.hf.space/mcp"
}
}
}
Private HF Space β pass your token:
{
"mcpServers": {
"serpent": {
"type": "http",
"url": "https://<your-space>.hf.space/mcp",
"headers": { "Authorization": "Bearer hf_..." }
}
}
}
Quick check without a client:
curl -X POST http://localhost:7860/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"curl","version":"1"}}}'
Or with the FastMCP CLI: fastmcp inspect http://localhost:7860/mcp
Configuration
All optional, all environment variables:
| Variable | Default | Purpose |
|---|---|---|
MCP_ENABLED |
1 |
Set to 0 to run REST-only. |
MCP_PATH |
/mcp |
Where the MCP endpoint lives. |
MCP_STATELESS |
1 |
Stateless HTTP β no session affinity needed behind the HF Spaces proxy. Set 0 for resumable SSE streams. |
MCP_ALLOWED_HOSTS |
* |
Comma-separated Host allow-list (DNS-rebinding protection). |
MCP_ALLOWED_ORIGINS |
* |
Comma-separated Origin allow-list (browser clients). |
Notes and caveats
- Ordering matters.
mount_mcp_server(app)must come after everyinclude_router()call. Routes registered after it are not exposed as tools, and are not reachable at all β the MCP app is mounted at the root so that a barePOST /mcpanswers directly instead of 307-redirecting to/mcp/(a redirect some MCP clients mishandle). - Lifespans are combined, so Playwright still starts up and shuts down exactly as before, alongside the MCP session manager.
- The
ops_*tools are exposed but return 503 withoutOPS_CONSUMER_KEY/OPS_CONSUMER_SECRET. The server instructions tell agents to prefer the regular tools, which already fall back to OPS. - Auth. The MCP endpoint inherits whatever protects the Space. If you later
want per-token auth on MCP only, FastMCP takes an
auth=provider inbuild_mcp_server(). - Tool count. 12 tools with six overlapping search backends is a lot of
surface for a model to choose from. If tool selection gets noisy, pass
route_maps=[...]toFastMCP.from_fastapi()inmcp_server.pyto exclude the per-backend tools and keepsearch,search_arxiv,search_patents,search_google_scholarand the scrapers.
Verified
Tested against fastmcp 3.4.7 / fastapi 0.141.1: server boots, all 12 tools
list over HTTP with correct schemas, tool calls round-trip through the real
endpoints, and the existing REST routes (/, /openapi.json, /redoc,
/serp/*) are unaffected.