Spaces:
Sleeping
Sleeping
File size: 4,727 Bytes
7ecfcd4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | # 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:**
```bash
claude mcp add --transport http serpent https://<your-space>.hf.space/mcp
```
**`.mcp.json` / `claude_desktop_config.json`:**
```json
{
"mcpServers": {
"serpent": {
"type": "http",
"url": "https://<your-space>.hf.space/mcp"
}
}
}
```
**Private HF Space** β pass your token:
```json
{
"mcpServers": {
"serpent": {
"type": "http",
"url": "https://<your-space>.hf.space/mcp",
"headers": { "Authorization": "Bearer hf_..." }
}
}
}
```
**Quick check without a client:**
```bash
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 every
`include_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
bare `POST /mcp` answers 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 without
`OPS_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 in
`build_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=[...]` to `FastMCP.from_fastapi()` in `mcp_server.py` to exclude
the per-backend tools and keep `search`, `search_arxiv`, `search_patents`,
`search_google_scholar` and 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.
|