Passing OpenRouter's metadata filters does not prove a model can complete a turn through this harness, so dead-but-advertised models kept reaching users and failing mid-stream. A new model_compatibility table holds one verdict per model, a weekly Celery sweep re-probes stale entries, and catalogue generation reads the blocked ids back so those models are never offered. Also excludes :batch variants, which advertise full chat metadata and then reject completions outright. Auto-pin now falls through to Auto when an explicitly selected global config is absent from the current worker's catalogue. Returning it handed load_llm_bundle an id it could not resolve, ending the turn with SERVER_ERROR on one of the four uvicorn workers while the identical request succeeded on the others. Catalogue refresh also re-materializes GLOBAL_MODELS, which previously kept its boot snapshot until restart. On the web side a single dropped connection surfaced as one toast per in-flight query and dozens of PostHog exceptions, with no retry button. Network errors now share a toast id, are deduped per endpoint before capture, carry host and online context, and count as retryable. Consolidates the model filter helpers duplicated between the model list and OpenRouter services into openrouter_model_normalizer, renames OoxmlDefect to OoxmlError, and includes a ruff format and import-order pass. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| .. | ||
| mcp_server | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
SurfSense MCP Server
A Model Context Protocol server that exposes SurfSense to MCP clients like Claude Code, Cursor, and Claude Desktop. It talks to a SurfSense backend purely over its REST API using a SurfSense API key — it imports no backend code.
Connect it two ways:
- Hosted (recommended) — point your client at
https://mcp.surfsense.com/mcpand pass your API key in a header. Nothing to install or keep running. - Self-host (stdio) — run the server yourself against any backend (cloud or your own). Best for self-hosters and clients without remote-server support.
Tools
Search-space selector
surfsense_list_workspaces— list the workspaces (search spaces) you can accesssurfsense_select_workspace— pick the active workspace by name or id
Scrapers (all platforms)
surfsense_web_crawl,surfsense_google_search,surfsense_reddit_scrape,surfsense_youtube_scrape,surfsense_youtube_comments,surfsense_instagram_scrape,surfsense_instagram_details,surfsense_tiktok_scrape,surfsense_tiktok_comments,surfsense_tiktok_user_search,surfsense_tiktok_trending,surfsense_google_maps_scrape,surfsense_google_maps_reviews,surfsense_indeed_scrape,surfsense_amazon_scrape,surfsense_walmart_scrape,surfsense_walmart_reviewssurfsense_list_scraper_runs,surfsense_get_scraper_run— retrieve past results in full (useful when a large result was truncated inline)
Knowledge base
surfsense_search_knowledge_base— semantic + keyword search over stored contentsurfsense_list_documents,surfsense_get_documentsurfsense_add_document,surfsense_upload_filesurfsense_update_document,surfsense_delete_document
Workspace-scoped tools default to the active workspace; pass workspace (a name
or id) to override for a single call. Ids never need to be typed by hand — the
model carries them between calls.
Get an API key
- SurfSense → API Playground → API Keys: create a personal key (
ss_pat_…). It is shown only once. - Toggle API key access on for the workspace(s) you want to use.
Connect (hosted)
Point your client at the hosted server and send the key as a Bearer token. For
clients that read an mcpServers map (Cursor, Claude Desktop, and others):
{
"mcpServers": {
"surfsense": {
"url": "https://mcp.surfsense.com/mcp",
"headers": { "Authorization": "Bearer ss_pat_your_key_here" }
}
}
}
Claude Code, from a terminal:
claude mcp add --transport http surfsense https://mcp.surfsense.com/mcp \
--header "Authorization: Bearer ss_pat_your_key_here"
Most MCP clients accept this url + headers form; check your client's docs for
its exact remote-server field.
Self-host (stdio)
Run the server yourself when you host your own backend or use a client without remote support. It uses uv:
cd surfsense_mcp
uv sync
uv run python -m mcp_server.selfcheck # verify tools register correctly
Then add it to your client. Cursor (~/.cursor/mcp.json or a project
.cursor/mcp.json):
{
"mcpServers": {
"surfsense": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/SurfSense/surfsense_mcp", "python", "-m", "mcp_server"],
"env": {
"SURFSENSE_BASE_URL": "http://localhost:8000",
"SURFSENSE_API_KEY": "ss_pat_your_token_here"
}
}
}
}
Claude Code:
claude mcp add surfsense \
-e SURFSENSE_BASE_URL=http://localhost:8000 \
-e SURFSENSE_API_KEY=ss_pat_your_token_here \
-- uv run --directory /absolute/path/to/SurfSense/surfsense_mcp python -m mcp_server
Claude Desktop: add the same mcpServers block as Cursor to
claude_desktop_config.json (Settings → Developer → Edit Config).
Configuration
See .env.example. For self-host, secrets are passed as environment variables by
the client; never commit tokens.
Backend dependency
surfsense_search_knowledge_base calls POST /api/v1/documents/search-semantic,
a thin endpoint that exposes the backend's existing hybrid retriever over REST.
All other tools use pre-existing SurfSense endpoints.