MCP Server
Connect an MCP client to KuraDB over streamable HTTP or stdio, and understand the two read-only tools it exposes.
Two transports, one implementation
KuraDB implements the Model Context Protocol with the official github.com/modelcontextprotocol/go-sdk. It serves the same server definition through two transports:
| Transport | Endpoint | Process | Requires a running daemon |
|---|---|---|---|
| Streamable HTTP | POST /mcp on the daemon listener |
Daemon | Yes |
| Stdio | kura mcp |
Separate process spawned by the client | No |
Both build the same tool set from internal/mcp and answer from search.Search in their own process. Neither transport calls the REST API, and the stdio bridge does not proxy the daemon.
Connect over stdio
Point any MCP client at the kura binary:
{
"mcpServers": {
"kuradb": {
"command": "kura",
"args": ["mcp"]
}
}
}
kura mcp opens the registered databases read-only, restores its own vector cache from SQLite, and speaks JSON-RPC over stdin and stdout. Diagnostics go to stderr, so they never corrupt the protocol stream. Closing stdin ends the session with exit code 0.
Because the process loads the vector cache at spawn time, its view is a snapshot: files indexed by the daemon afterwards appear only after the MCP session restarts.
Connect over HTTP
The daemon mounts the MCP handler on the same listener as the REST API:
BASE="$(cat ~/.config/kuradb/endpoint)"
curl -X POST "$BASE/mcp" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
The response carries an Mcp-Session-Id header that subsequent requests must repeat. Because an unpinned daemon binds a random port on every restart, pin one before writing the URL into a client configuration:
kura port set 8080
Server instructions
During initialize the server returns instructions describing what it is and, equally important, what it is not:
- KuraDB is a read-only retrieval source over static files the user placed in a watched folder.
- Callers should use it whenever an answer depends on the content of those files rather than on general knowledge.
- It stores no conversation history, no session memory, and no user profile.
The last point is deliberate. A tool advertised as a memory store gets selected to answer questions it cannot answer; the boundary keeps routing honest.
list_rag
Lists the databases that can be searched. It takes no arguments.
{
"loaded": ["notes"],
"registered": [
{ "db": "notes", "createAt": "2026-07-14T07:00:00Z" }
]
}
loaded names are queryable in this process; registered entries come from db.json. A database added after startup appears only in registered.
search_rag
Searches one database and returns file chunks grouped by source.
| Parameter | Required | Default | Behavior |
|---|---|---|---|
db |
Yes | — | Target database name, as reported by list_rag |
q |
Yes | — | Query text |
mode |
No | Both | keyword or semantic; omit to run both |
limit |
No | 10 |
Maximum chunks per mode, 1–100 |
The schema declares the mode enum and the limit default, so the SDK validates arguments and applies defaults before the handler runs. An invalid enum value or a missing required field is rejected as a tool error without touching SQLite.
Structured output mirrors the REST response, minus the branches that did not run:
{
"keyword": [
{
"source": "/Users/example/Kura_notes/rag.md",
"matches": [{ "chunk": 1, "content": "RAG combines retrieval with generation." }]
}
],
"semantic": []
}
Every call returns both a JSON text block and structuredContent, so clients that understand output schemas and clients that only read text both work.
Errors
| Condition | Result |
|---|---|
Unknown mode, missing db or q |
Rejected by schema validation, returned as a tool error |
| Database not loaded | Tool error: invalid argument: "name" not exist |
| Keyword or semantic branch failure | Tool error carrying the underlying message |
Argument mistakes are reported through search.ErrInvalidArgument. The REST transport maps that sentinel to HTTP 400; the MCP transport lets the SDK mark the result with isError.
Tool naming
The tool names and parameters match the KuraDB client tools already shipped by Agenvoy, so a consumer can switch from its own HTTP wrapper to this MCP server without rewriting prompts. Renaming a tool or changing a parameter requires updating both sides.