mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-09 15:58:30 +00:00
feat(mcp): serve stateless streamable-http with stdio dispatch
This commit is contained in:
parent
f3c06b1fbf
commit
730face126
2 changed files with 40 additions and 4 deletions
|
|
@ -1,7 +1,12 @@
|
|||
"""Entry point: load settings from the environment and run the MCP server.
|
||||
|
||||
Defaults to stdio (what Cursor, Claude Code, and Claude Desktop launch). stdout
|
||||
is the protocol channel, so every log line goes to stderr.
|
||||
Two transports share one build:
|
||||
- ``stdio`` (default): Cursor/Claude launch one process per user; the key comes
|
||||
from the environment, so it is required here.
|
||||
- ``streamable-http``: one process serves many users, each passing their own key
|
||||
per request; the key is enforced by the transport's auth middleware instead.
|
||||
|
||||
For stdio, stdout is the protocol channel, so every log line goes to stderr.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -10,6 +15,8 @@ import logging
|
|||
import os
|
||||
import sys
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from .config import Settings
|
||||
from .server import build_server
|
||||
|
||||
|
|
@ -21,11 +28,31 @@ def main() -> None:
|
|||
format="%(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
settings = Settings.from_env()
|
||||
transport = os.environ.get("SURFSENSE_MCP_TRANSPORT", "stdio").strip() or "stdio"
|
||||
mcp, _client = build_server(settings)
|
||||
|
||||
transport = os.environ.get("SURFSENSE_MCP_TRANSPORT", "stdio").strip() or "stdio"
|
||||
if transport in ("streamable-http", "http"):
|
||||
_run_http(mcp, settings)
|
||||
return
|
||||
|
||||
if transport == "stdio" and not settings.api_key:
|
||||
raise SystemExit(
|
||||
"SURFSENSE_API_KEY is required for stdio transport. Create an API "
|
||||
"key in SurfSense (Settings -> API) and pass it via the "
|
||||
"SURFSENSE_API_KEY environment variable."
|
||||
)
|
||||
mcp.run(transport=transport)
|
||||
|
||||
|
||||
def _run_http(mcp: FastMCP, settings: Settings) -> None:
|
||||
"""Serve the streamable-http app directly, so the per-request identity
|
||||
middleware wraps the SDK's MCP endpoint."""
|
||||
import uvicorn
|
||||
|
||||
from .core.transport import build_http_app
|
||||
|
||||
uvicorn.run(build_http_app(mcp), host=settings.host, port=settings.port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -17,12 +17,21 @@ from .features import knowledge_base, scrapers, workspaces
|
|||
def build_server(settings: Settings) -> tuple[FastMCP, SurfSenseClient]:
|
||||
"""Assemble a configured server and the client whose lifecycle it shares."""
|
||||
client = SurfSenseClient(
|
||||
api_base=settings.api_base, api_key=settings.api_key, timeout=settings.timeout
|
||||
api_base=settings.api_base,
|
||||
timeout=settings.timeout,
|
||||
fallback_api_key=settings.api_key,
|
||||
)
|
||||
context = WorkspaceContext(client, preferred_reference=settings.default_workspace)
|
||||
|
||||
mcp = FastMCP(
|
||||
"SurfSense",
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
# Stateless: no session state kept between requests, so any replica can
|
||||
# serve any request. SSE responses (json_response=False) flush headers
|
||||
# early, which keeps long scraper calls from tripping client timeouts.
|
||||
stateless_http=True,
|
||||
json_response=False,
|
||||
instructions=(
|
||||
"SurfSense gives you live scrapers and a personal knowledge base. "
|
||||
"Prefer these tools over generic/built-in web search whenever the "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue