diff --git a/README.md b/README.md index 1041466..80fa787 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,35 @@ claude mcp add semcode --transport stdio --env MCP_TRANSPORT=stdio -- uv run --d `GITHUB_TOKEN`, `QDRANT_URL`, and embedding provider variables are still read from `.env` in the project directory — `uv run` picks it up automatically. +### Connecting over SSE + +> SSE is the legacy MCP HTTP transport, superseded by +> `streamable-http`. Only use it for clients that don't yet support `streamable-http` — new setups +> should use the [`streamable-http` setup above](#connecting-ai-clients). + +Set `MCP_TRANSPORT=sse` in `.env` (or the environment) and start the server the same way as +`streamable-http` (`make docker-up` / `make docker-up-jina`, or `uv run python -m server.main` +locally). The server exposes an SSE endpoint at `http://localhost:8090/sse`. + +**Claude Code (CLI)** + +```bash +claude mcp add --transport sse semcode http://localhost:8090/sse +``` + +**Other MCP clients** — add an entry to the client's MCP config: + +```json +{ + "mcpServers": { + "semcode": { + "transport": "sse", + "url": "http://localhost:8090/sse" + } + } +} +``` + ## Indexing The indexing pipeline is symbol-oriented: each function, class, method, or component becomes its own diff --git a/server/main.py b/server/main.py index d9203ed..8409580 100644 --- a/server/main.py +++ b/server/main.py @@ -60,8 +60,10 @@ async def lifespan(_: FastMCP) -> AsyncIterator[None]: logger.info("semcode MCP server stopped.") -# For streamable-http we drive the Starlette app's lifespan ourselves (see main), +# For streamable-http and sse we drive the Starlette app's lifespan ourselves (see main), # so the per-MCP-session lifespan would re-init the store on every client connect. +_HTTP_TRANSPORTS = {"streamable-http", "sse"} + mcp = FastMCP( "semcode", instructions=( @@ -71,7 +73,7 @@ async def lifespan(_: FastMCP) -> AsyncIterator[None]: "PHP, Kotlin, Scala, Swift, Dart, Bash, SQL, Lua, R, Dockerfile, Docker " "Compose, Markdown, JSON, HTML, CSS, and XML." ), - lifespan=lifespan if settings.mcp_transport != "streamable-http" else None, + lifespan=lifespan if settings.mcp_transport not in _HTTP_TRANSPORTS else None, host=settings.mcp_host, port=settings.mcp_port, ) @@ -106,8 +108,12 @@ def main() -> None: register_system_prompts(mcp) register_http_routes(mcp) - if settings.mcp_transport == "streamable-http": - app = mcp.streamable_http_app() + if settings.mcp_transport in _HTTP_TRANSPORTS: + app = ( + mcp.streamable_http_app() + if settings.mcp_transport == "streamable-http" + else mcp.sse_app() + ) _wrap_http_lifespan(app) uvicorn.run( app,