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:

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 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.