Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 10 additions & 4 deletions server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=(
Expand All @@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down