MCP Server¶
Connect agent harnesses — Claude Code, Claude Desktop, Cursor, or any MCP client —
directly to your graph. kg serve exposes it over the
Model Context Protocol with four read-only,
schema-aware tools. No LLM is involved in serving: extraction needs a model,
querying doesn't.
The four tools¶
| Tool | What it does |
|---|---|
describe_ontology |
The schema the loaded ontology declares and what the graph actually holds (per-label counts). Agents call this first. |
search_entities |
Case-insensitive substring search on the name property — resolves a vague mention into a concrete node. |
get_neighbors |
One-hop neighborhood of a named entity, both directions, with the relationship type and out/in direction. |
query_graph |
Hand-written read-only openCypher (MATCH/WHERE/RETURN/ORDER BY/LIMIT); write clauses are rejected. |
Tool descriptions are generated at runtime from the loaded ontology — swap the YAML and the agent sees a different schema, with zero code changes. That's the repo's core claim carried through to the agent layer.
Quick connect (stdio)¶
uv sync --extra mcp # optional dependency; default install stays lean
kg serve --check # DB reachable? graph? data? (exit 0/1, no mcp needed)
kg serve --print-config # ready-to-paste harness config JSON
--print-config prints something like:
{
"mcpServers": {
"kg-generic": {
"command": "/path/to/.venv/bin/python",
"args": ["-m", "kg.cli", "serve", "--env", "/path/to/.env"]
}
}
}
The command is the venv's own interpreter and the .env path is absolute, so the
harness can't accidentally resolve either against the wrong environment. Paste the
mcpServers block into your harness config and restart the harness — it should
list the four tools.
(Or add the mcpServers block from --print-config to .mcp.json / your
project settings.)
Paste the block into claude_desktop_config.json (Settings → Developer →
Edit Config) and restart the app.
Any MCP client that speaks stdio: use the command + args from
--print-config as the spawn command.
Two graphs, two servers¶
Each .env profile serves its own ontology and graph. Give each a name and
register both:
kg serve --print-config --env .env # kg-generic -> kg_graph
kg serve --print-config --env .env.constitution # kg-constitution_india -> kg_graph_const
Both blocks can live in the same harness config; each server only ever sees its own graph, and each gets tool descriptions built from its own ontology.
Operational notes¶
- Ontology YAML edits need a harness restart. Descriptions are built once at startup. Ingested data, however, appears without a restart — counts refresh on a ~60s TTL.
- If the server won't start, run
kg info/kg serve --check. Over stdio the harness swallows tracebacks; these two print the real error (bad YAML, unreachable DB, missing graph) to your terminal. - Serving an empty graph is fine.
kg servestarts,describe_ontologyreports the declared schema with zero counts and a hint pointing atkg ingest. Run the ingest whenever ready; the server picks the data up without a restart. Use--require-dataif you'd rather it refuse to start.
The read-only boundary¶
The server connects as the kg_reader role — created by
docker/init-age.sql with USAGE/SELECT only. This is the actual guard
against hostile Cypher from an agent: the write-clause check in query_graph is
a friendly error message, not a security boundary. Configure via .env:
Unset, the server falls back to the write credentials (it works, but any
query_graph caller could write).
Non-default graph names
The init script grants on the default kg_graph schema. A graph created
under a different AGE_GRAPH_NAME gets its own schema and needs a one-time:
HTTP deployment (optional)¶
Some setups prefer an HTTP endpoint over per-client stdio processes:
The mcp service is profile-gated — plain docker compose up -d still starts
only Postgres. It serves the bundled ontology named by MCP_ONTOLOGY
(default generic.yaml) at http://127.0.0.1:8080/mcp.
No authentication
This endpoint exposes the entire graph to anyone who can reach the port,
with no auth. It is bound to 127.0.0.1 on purpose. Before exposing it
beyond your machine, put at least a bearer-token check (ASGI middleware) in
front — deliberately not implemented here. Auth is tracked as future work.
Note
The HTTP server needs no Ollama / hosted LLM — just Python and Postgres.
Extraction is the only model-dependent step, and it runs on the host via
kg ingest, not inside this container.
Limits¶
This is schema-aware read access, not GraphRAG retrieval: there's no retrieval/QA, no text-to-Cypher, no ranking over text chunks. The agent sees the ontology and queries the graph itself.