SERPent / docs /README_MCP.md
Almaatla's picture
Upload README_MCP.md
7ecfcd4 verified
|
Raw
History Blame Contribute Delete
4.73 kB
# 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.