Recipe — the epistemic-graph KG as a sql-mcp connection¶
Cross-repo concept:
CONCEPT:KG-2.205(KG-SQL surface) overCONCEPT:KG-2.189(engine pg-wire listener) +CONCEPT:KG-2.202(SCRAM auth).
The epistemic-graph engine exposes its Knowledge
Graph over the Postgres wire protocol. A server built with the pgwire
feature and started with EPISTEMIC_GRAPH_PGWIRE_ADDR set accepts native
SQLAlchemy/psycopg connections and runs read-only SQL over the nodes/edges
tables (the same DataFusion path Method::Sql uses). So the KG needs no
special connector in sql-mcp — it is just another named connection.
This is the complement to the native KG-SQL MCP tool: agent-utilities' graph-os
exposes graph_query(scope="sql") (+ REST /graph/query) for SQL-on-the-KG over
MCP; this recipe reaches the same engine surface from a generic SQL client.
1. Deploy the engine with pg-wire + SCRAM¶
Build/run the engine with the listener bound (loopback by convention) and an auth secret set so SCRAM is the default:
# engine side (epistemic-graph)
cargo build --release --features "server,query,pgwire,redb"
GRAPH_SERVICE_AUTH_SECRET=<engine-secret> \
EPISTEMIC_GRAPH_PGWIRE_ADDR=127.0.0.1:5433 \
EPISTEMIC_GRAPH_PGWIRE_AUTH=scram \
target/release/epistemic-graph-server --persist-dir /var/lib/epistemic-graph
When GRAPH_SERVICE_AUTH_SECRET is set, scram is the default auth mode; the pg
user maps to an engine agent_id, and queries run under that identity against
the engine ACL / row-level security (IsolationLayer::check_access).
2. Derive the SCRAM password¶
Under SCRAM the per-user password is derived from the engine secret — it is never stored:
sql-mcp ships a helper that reproduces that derivation exactly:
export GRAPH_SERVICE_AUTH_SECRET=<engine-secret>
python -m sql_mcp.kg_pgwire derive-password --user sql-mcp
# -> <hex password>
# or the whole DSN in one shot:
python -m sql_mcp.kg_pgwire dsn --user sql-mcp --host 127.0.0.1 --port 5433 --graph __commons__
# -> postgresql+psycopg://sql-mcp:<hex>@127.0.0.1:5433/__commons__
Store the derived password as a vault ref (OpenBao apps/sql-mcp, key
KG_PGWIRE_PASSWORD); it is injected into the service env at deploy time. The
engine secret itself stays in OpenBao and is never committed.
bao kv put apps/sql-mcp \
KG_PGWIRE_USER=sql-mcp \
KG_PGWIRE_PASSWORD="$(python -m sql_mcp.kg_pgwire derive-password --user sql-mcp)"
3. Register the kg connection¶
Point one of sql-mcp's named connections at the listener
(SQL_CONNECTIONS, CONCEPT:SQL-1.2). The deployed stack
(services/sql-mcp/compose.dev.yml) already wires this from the vault-injected
vars:
SQL_CONNECTIONS='{"kg":"postgresql+psycopg://sql-mcp:<KG_PGWIRE_PASSWORD>@127.0.0.1:5433/__commons__"}'
The database segment (__commons__) selects the engine graph; use any
registered graph name, e.g. team:alpha.
4. Validate¶
# connection health
sql_admin action=ping connection=kg
# read the graph as SQL
sql_query action=execute connection=kg \
params_json='{"sql":"SELECT id, label FROM nodes LIMIT 5"}'
sql_admin ping confirms the SCRAM handshake + latency; the sql_query returns
the bounded {columns, rows, row_count, truncated} envelope (CONCEPT:SQL-1.4)
over real KG nodes.
Notes¶
- Read-only by default. sql-mcp's
sql_queryonly runs SELECT/CTE; the engine pg-wire surface also classifies writes and routes them through the governed write path. LeaveSQL_ALLOW_WRITESoff for thekgconnection unless you intend governed DML overnodes. - Driver. Requires the
postgresextra (sql-mcp[postgres]→psycopg). - No new connector code. This is config + the derivation helper only — the KG is reached through the standard SQLAlchemy Postgres dialect.