---
title: "MCP Endpoint Tutorial"
---

# MCP Endpoint Tutorial

**Build:** `cargo build --release --features mcp-endpoint` for the 16 core tools; add `code-graph` (10 more tools) and/or `graph-rag` (1 more tool). The prebuilt binaries and the container image use the default features and do not include MCP.
**Routes:** `POST /mcp`, `GET /mcp/ws`, `GET /mcp/sse`, `GET /mcp/info`
**Stdio:** not an engine subcommand; for stdio MCP use the separate HeliosDB CodeKB MCP server (`cargo install heliosdb-codekb-mcp`)

---

## UVP

Most "MCP for SQL" stories ship a wrapper process that proxies `tools/call` into a `psql` subprocess. HeliosDB Nano is the wrapper. The same binary that serves PostgreSQL wire traffic, REST, and WebSockets exposes a JSON-RPC 2.0 dispatcher with **16 tools** (10 DB-backed + 6 in-process RAG) reachable over HTTP, WebSocket or SSE (stdio through the HeliosDB CodeKB MCP server) — and MCP clients such as Claude Code, Cursor, Continue and Aider can drive every tool. Tools that take a while emit `notifications/progress` events while they run, so agents render real progress instead of a hung spinner. **One process, one auth surface, real streaming.**

---

## Prerequisites

- HeliosDB Nano source tree
- Rust 1.85+
- An MCP-aware client (Claude Code, Cursor, Continue, Aider) for the §11 integration walkthrough
- `curl` and `jq` for the manual JSON-RPC walkthroughs
- About 30 minutes

---

## 1. Build with `--features mcp-endpoint`

```bash
cargo build --release --features mcp-endpoint,code-graph,graph-rag
```

Each feature flag is additive:

| Flag | What you get |
|---|---|
| `mcp-endpoint` (alone) | 10 DB-backed tools (`heliosdb_*`) + 6 in-process RAG tools |
| `+ code-graph` | adds 7 LSP tools (`helios_lsp_definition`, `_references`, `_call_hierarchy`, `_hover`, `_document_symbols`, `_rename_preview`, `_rename_apply`) and 3 diff tools (`helios_lsp_references_diff`, `_body_diff`, `helios_ast_diff`) |
| `+ graph-rag` | adds `helios_graphrag_search` |

`mcp-endpoint` alone gives you the wire-level dispatcher; the LSP and graph-rag tools auto-register through `inventory::submit!` whenever their feature is on.

---

## 2. Stdio Server (Claude Code / Cursor / Aider Path)

:::caution[No engine subcommand]
HeliosDB Nano has no `mcp-server` subcommand: the stdio server is a library
component, used by the separate HeliosDB CodeKB MCP server
(`cargo install heliosdb-codekb-mcp`). Use that server for stdio MCP clients, or
run `heliosdb-nano start` from a build with `mcp-endpoint` and use the HTTP,
WebSocket or SSE routes. The `mcp-server` commands in this section and in the
`.mcp.json` example below show the stdio message flow only.
:::

The simplest transport is stdin/stdout JSON-RPC, which is what Claude Code, Cursor, and Aider expect by default:

```bash
heliosdb-nano mcp-server --data-dir ./data
```

The server reads one JSON-RPC message per line, dispatches via `mcp::handle_rpc_with_db`, and writes one response per line to stdout. EOF closes the loop.

### Manual smoke test

```bash
$ heliosdb-nano mcp-server --data-dir ./data <<'EOF' | jq -c '.'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"ping"}
EOF
```

```json
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","serverInfo":{"name":"heliosdb-nano","version":"<release>"},"capabilities":{...}}}
{"jsonrpc":"2.0","id":2,"result":{"tools":[...16 entries...]}}
{"jsonrpc":"2.0","id":3,"result":{}}
```

---

## 3. The 16 Tools (Full Catalogue)

```bash
curl -s -X POST http://localhost:8080/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"verbose":true}}' | jq '.result.tools[].name'
```

### Core (`heliosdb_*`) — 10 tools, DB-backed

| Tool | Purpose |
|---|---|
| `heliosdb_query` | Run an arbitrary SQL query with optional parameters and `branch` override. |
| `heliosdb_schema` | Column list of a table from the catalog. |
| `heliosdb_list_tables` | Every user table (filters out `helios_*` / `mv_*`). |
| `heliosdb_create_table` | Create a table with column array. |
| `heliosdb_insert` | Insert one or more rows. |
| `heliosdb_branch_create` | Copy-on-write branch from another. |
| `heliosdb_branch_list` | Every branch. |
| `heliosdb_branch_merge` | Merge source branch into target. |
| `heliosdb_search` | Vector-similarity search via HNSW. |
| `heliosdb_time_travel` | Read-only `AS OF TIMESTAMP` query. |

### In-process RAG — 6 tools, no DB required

| Tool | Purpose |
|---|---|
| `heliosdb_bm25_index` | Build a process-wide BM25 index from `(doc_id, text)` documents. |
| `heliosdb_hybrid_search` | BM25 + caller-supplied vector hits, fused via RRF / MMR / weighted-linear. |
| `heliosdb_graph_add_edge` | Append a directed edge to the in-process graph store. |
| `heliosdb_graph_traverse` | BFS from a starting node with optional label filter and depth. |
| `heliosdb_graph_path` | Shortest path via BFS / Dijkstra / bi-directional BFS. |
| `heliosdb_embed_and_store` | Stash a `(doc_id, text)` tuple into a BM25 index. |

### Code-graph extensions — 10 tools (7 `helios_lsp_*` + 3 diff tools), via `--features code-graph`

| Tool | Purpose |
|---|---|
| `helios_lsp_definition` | "Where is `name` defined?" |
| `helios_lsp_references` | "Who uses `symbol_id`?" |
| `helios_lsp_call_hierarchy` | BFS over `CALLS` edges, configurable direction & depth. |
| `helios_lsp_hover` | Signature lookup. |
| `helios_lsp_document_symbols` | File outline ordered by line. |
| `helios_lsp_rename_preview` | Read-only preview of a rename's edit list. |
| `helios_lsp_rename_apply` | Identifier-boundary-aware rename with sha256 conflict check; supports `dry_run`. |
| `helios_lsp_references_diff` | Diff a symbol's reference set across two `AS OF` points. |
| `helios_lsp_body_diff` | Myers-LCS body diff across two `AS OF` points. |
| `helios_ast_diff` | File-level structural diff (added / removed / moved). |

### Graph-RAG extension — 1 tool, via `--features graph-rag`

| Tool | Purpose |
|---|---|
| `helios_graphrag_search` | Cross-modal seed → BFS expand → return query over `_hdb_graph_*`. Emits streaming progress. |

`tools/list?verbose=true` adds two extra fields per tool — `category` (`core` or `extension`) and `requiresDatabase` — so a client can gate which tools it surfaces to a model.

---

## 4. Single-Shot Discovery — `helios/info` and `GET /mcp/info`

Pull `serverInfo + capabilities + verbose tool catalogue + resource list` in one packet:

```bash
curl -s -X POST http://localhost:8080/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"helios/info"}' | jq
```

Or over HTTP (§5):

```bash
curl -s http://localhost:8080/mcp/info | jq
```

---

## 5. HTTP / WebSocket / SSE — Network Transports

Embed the MCP routes onto an Axum router:

```rust
use std::sync::Arc;
use heliosdb_nano::{
    mcp::{attach_mcp_routes, McpAuth, McpState},
    EmbeddedDatabase,
};

#[tokio::main]
async fn main() -> heliosdb_nano::Result<()> {
    let db = Arc::new(EmbeddedDatabase::open("./data")?);
    let state = McpState::new(db).with_auth(McpAuth::Disabled);
    let app = attach_mcp_routes(axum::Router::new(), state);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}
```

`attach_mcp_routes` mounts:

- `POST /mcp` — JSON-RPC request/response.
- `GET /mcp/ws` — WebSocket; one JSON-RPC message per text frame.
- `GET /mcp/sse` — Server-Sent Events transport per the MCP spec; emits an initial `endpoint` event with the paired POST URL plus a session id.
- `GET /mcp/info` — discovery payload (same body as `helios/info`).

All four transports dispatch through the same `mcp::handle_rpc_with_db` core, so tool/resource behaviour is identical.

### POST manual call

```bash
curl -s -X POST http://localhost:8080/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
        "name":"heliosdb_query",
        "arguments":{"sql":"SELECT 1+1 AS x"}
      }}' | jq
```

### Auth

```rust
// Loopback / dev — auth off.
let state = McpState::new(db).with_auth(McpAuth::Disabled);

// Production — JWT-gated, with read/write scopes.
let state = McpState::new(db).with_auth(McpAuth::jwt_hs256(secret_bytes));
```

When auth is on, `tools/call`/`resources/read` require the appropriate scope; reads return 401 without a Bearer header. WebSocket upgrades require Write scope (the per-frame loop has no per-message header). Use `mcp::bind_safety_check` to refuse non-loopback binds when auth is `Disabled`.

---

## 6. Streaming Progress Notifications

Tools that take a while emit `notifications/progress` events when the client opts in via `_meta.progressToken`. WebSocket and stdio forward each event inline; HTTP forwards through the SSE channel.

### Opt in over WebSocket

```javascript
ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 7,
  method: "tools/call",
  params: {
    name: "helios_graphrag_search",
    arguments: { seed_text: "parse", hops: 3, limit: 50 },
    _meta: { progressToken: "search-7" },
  },
}));
```

You'll receive one or more notification frames followed by the final response:

```json
{"jsonrpc":"2.0","method":"notifications/progress","params":{
  "progressToken":"search-7","progress":0.0,"total":50,
  "message":"graph_rag_search: seeding for 'parse', hops=3"}}
{"jsonrpc":"2.0","method":"notifications/progress","params":{
  "progressToken":"search-7","progress":18,"total":50,
  "message":"graph_rag_search: 18 hits"}}
{"jsonrpc":"2.0","id":7,"result":{"isError":false,"content":[{"type":"text","text":"..."}]}}
```

`helios_graphrag_search` is the first tool wired (emits "seeding" on entry, "<n> hits" on exit). The dispatcher (`mcp::call_tool_streaming`) runs the synchronous tool handler on `spawn_blocking` and forwards events through an unbounded channel. Any tool that calls `mcp::progress::emit(...)` from anywhere on the call stack will participate.

### HTTP + SSE pairing

Single-shot HTTP can't stream, so progress for `POST /mcp` rides the paired SSE channel:

1. Client opens `GET /mcp/sse?session=<id>`. The server replies with `event: endpoint` whose body is `/mcp?session=<id>`.
2. Client copies `<id>` into the `Mcp-Session-Id` header on the subsequent `POST /mcp` request and includes `_meta.progressToken` in the body.
3. The server runs the tool, forwards progress events to the SSE channel keyed by `<id>`, and returns the final `tools/call` response in the POST body.

---

## 7. Resources

```bash
curl -s -X POST http://localhost:8080/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"resources/list"}' | jq
```

Two static URIs are advertised; two more are URI-templated:

| URI | Body |
|---|---|
| `heliosdb://schema` | List of every user table. |
| `heliosdb://branches` | Every branch. |
| `heliosdb://schema/{table}` | Column metadata for a single table. |
| `heliosdb://stats/{table}` | Row count for a single table. |

`resources/read { "uri": "heliosdb://schema/users" }` returns JSON `{ "table": "users", "columns": [...] }`.

---

## 8. Worked Walkthrough — Ask Claude Code "find every caller of `parse`"

Configure Claude Code's `.mcp.json`:

```json
{
  "mcpServers": {
    "heliosdb": {
      "command": "heliosdb-nano",
      "args": ["mcp-server", "--data-dir", "./data"]
    }
  }
}
```

Index the corpus once (over psql, or via `heliosdb-nano code-graph hook` if you've wired the git hook):

```sql
CREATE TABLE src (path TEXT PRIMARY KEY, lang TEXT, content TEXT);
\copy src FROM 'sources.csv' CSV;
SELECT 'indexed' AS status FROM lsp_definition('answer');  -- triggers bootstrap
```

In Claude Code's chat: `Use heliosdb to find every caller of parse`. Claude calls `helios_lsp_definition({name: "parse"})`, gets a `symbol_id`, then `helios_lsp_references({symbol_id})` and renders the call sites with file/line citations. With `--features graph-rag` enabled, Claude can chain into `helios_graphrag_search({seed_text: "parse", hops: 2})` to widen the context to docs and issues that mention `parse`.

---

## 9. Cursor / Continue / Aider

Same `.mcp.json` shape works for all three. Each client autodetects the tools list on startup and surfaces them under whatever name they expose MCP tools as:

- **Cursor**: tools appear under `@heliosdb` slash commands.
- **Continue**: tools appear in the `tools` panel of any agent slot.
- **Aider**: tools are reachable via the `/mcp` command from the editor's chat.

All four clients honour `notifications/progress` and render the streaming message as a status line.

---

## 10. Branch Scoping per Tool Call

Every DB-backed tool accepts a `branch` argument:

```json
{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "heliosdb_query",
    "arguments": {
      "sql": "SELECT COUNT(*) FROM src",
      "branch": "refactor"
    }
  }
}
```

The dispatcher's `with_branch` helper switches to the requested branch before the call and restores the previous branch after — even on early-return paths. `branch == "main"` and `branch == current` skip the switch entirely.

---

## 11. Where Next

- [CODE_GRAPH_TUTORIAL](https://www.heliosdb.com/docs/nano/guides/tutorials/code_graph_tutorial/) — populate `_hdb_code_*` so the LSP tools have data.
- [GRAPH_RAG_TUTORIAL](https://www.heliosdb.com/docs/nano/guides/tutorials/graph_rag_tutorial/) — wire `helios_graphrag_search` against a populated `_hdb_graph_*`.
- [DOCLING_INGESTION_TUTORIAL](https://www.heliosdb.com/docs/nano/guides/tutorials/docling_ingestion_tutorial/) — pull external documents into the graph the MCP search hits against.
- [SEMANTIC_HASH_INDEX_QUICKREF](https://www.heliosdb.com/docs/nano/guides/quickstart/semantic_hash_index_quickref/) — keep re-indexing incremental as the corpus grows.
- [BAAS_REST_API_TUTORIAL](https://www.heliosdb.com/docs/nano/guides/tutorials/baas_rest_api_tutorial/) — REST endpoints (the `/rest/v1` table routes are a known issue in the current release; use the PostgreSQL wire protocol for table access).
