MCP server de Knowledge Graph RDBMS
Proporciona un grafo de propiedades con etiquetas embebido usando SQLite o Postgres que permite a los agentes de IA leer, escribir y razonar sobre relaciones de entidades estructuradas.
También conocido como cunicopia-dev/knowledge-graph-rdbms, knowledge-graph-rdbms
- Stars
- 1
- Herramientas
- —
- Descargas, 30 días
- 191
- Última versión
- 0.1.9
- Último commit
- 9 ago 2026
- Licencia
- MIT
¿Qué es Knowledge Graph RDBMS MCP?
Knowledge Graph RDBMS es un MCP server. Proporciona un grafo de propiedades con etiquetas embebido usando SQLite o Postgres que permite a los agentes de IA leer, escribir y razonar sobre relaciones de entidades estructuradas.
Resumen generado a partir del README del proyecto
Acerca del MCP server de Knowledge Graph RDBMS
Knowledge Graph RDBMS proporciona un grafo de propiedades con etiquetas embebido, diseñado para que los agentes de IA gestionen relaciones de entidades estructuradas. Funciona como una solución local-first que almacena los datos en un único archivo SQLite o Postgres, lo que facilita el versionado, la copia y la inspección sin necesidad de un servicio de grafos externo como Neo4j. El sistema utiliza un modelo de propiedades con etiquetas compuesto por nodos, aristas tipadas, etiquetas y propiedades JSON para representar dominios complejos.
Los desarrolladores pueden interactuar con el grafo a través de una librería de Python, una interfaz de línea de comandos o un MCP server. Debido a que la arquitectura se basa en event sourcing, cada escritura se registra en un log de solo anexado. Esto permite capacidades únicas como revertir cambios específicos o reproducir todo el grafo hasta un punto determinado en el tiempo. Es ideal para construir la memoria de un agente o ontologías embebidas donde la auditabilidad y la capacidad de navegar por el historial de los datos son esenciales.
Qué puedes hacer con él
- 1Construcción de memoria persistente para agentesUn agente de IA puede usar la conexión MCP para leer un dominio y modelar lo que aprende como hechos estructurados.
- 2Auditoría y reversión de cambiosPuedes rastrear cada modificación realizada por un agente y deshacer eventos específicos utilizando sus IDs únicos.
- 3Creación de ontologías de aplicaciones embebidasPuedes incluir un grafo de conocimiento completo dentro de un único archivo de aplicación para facilitar el despliegue.
Escrito a partir del README del proyecto. Cada caso de uso se verifica con una cita del mismo.
Cómo instalar Knowledge Graph RDBMS MCP
Formatos verificados el 10 nov 2026Instalar Knowledge Graph RDBMS MCP en Claude Code
$ claude mcp add knowledge-graph-rdbms -- uvx knowledge-graph-rdbmsInstalar Knowledge Graph RDBMS MCP en Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"knowledge-graph-rdbms": {
"command": "uvx",
"args": [
"knowledge-graph-rdbms"
]
}
}
}Instalar Knowledge Graph RDBMS MCP en VS Code
.vscode/mcp.json
{
"servers": {
"knowledge-graph-rdbms": {
"type": "stdio",
"command": "uvx",
"args": [
"knowledge-graph-rdbms"
]
}
}
}Instalar Knowledge Graph RDBMS MCP en Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"knowledge-graph-rdbms": {
"command": "uvx",
"args": [
"knowledge-graph-rdbms"
]
}
}
}Instalar Knowledge Graph RDBMS MCP en Codex
~/.codex/config.toml
[mcp_servers.knowledge-graph-rdbms] command = "uvx" args = ["knowledge-graph-rdbms"]
Instalar Knowledge Graph RDBMS MCP en Gemini CLI
~/.gemini/settings.json
{
"mcpServers": {
"knowledge-graph-rdbms": {
"command": "uvx",
"args": [
"knowledge-graph-rdbms"
]
}
}
}Configuración
KGRDBMS_DEFAULT_ONTOLOGYName of the default ontology used when a tool call omits the `ontology` argument. Defaults to "default".
KGRDBMS_HOMEStorage root for ontology databases and the event log. Defaults to ~/.kgrdbms.
KGRDBMS_MCP_TOKEN
| Nombre | Descripción | Tipo |
|---|---|---|
| KGRDBMS_DEFAULT_ONTOLOGY | Name of the default ontology used when a tool call omits the `ontology` argument. Defaults to "default". | |
| KGRDBMS_HOME | Storage root for ontology databases and the event log. Defaults to ~/.kgrdbms. | |
| KGRDBMS_MCP_TOKEN | — |
README
Ver en GitHubknowledge-graph-rdbms
An embedded knowledge graph you own completely — in one SQLite file.
SQLite-native · zero-dependency core · MCP-ready · event-sourced · reversible.
Model anything as entities and relationships — agent memory, app ontologies, declarative datasets, "SQLite but my data is a graph" — without running Neo4j, RDF, Docker, or a separate graph service. No Cypher, no JVM, no server: five tables and one file you can copy, inspect, and version.
pip install "knowledge-graph-rdbms[mcp]"
# write a fact — auto-creates the graph in one SQLite file
kg node add person:ada --kind Person --name "Ada Lovelace"
# every write is logged; tail the log and roll any event back by id
kg events -n 5
kg revert <event_id>
# rebuild the graph as of any past moment — literal time travel
kg replay --upto 2026-01-01T00:00:00
# expose the whole graph to an AI agent over MCP
kg serve
Agent / CLI / Python
│
gated + logged writes
│
SQLite label property graph
│
replay · revert · time-travel
Python 3.10+ · MIT · zero-dependency core · library + CLI + MCP
The world isn't rows in a table — it's things, the kinds of things they are, and how they relate. A label property graph captures exactly that, and not much more: nodes (entities), typed edges (relationships), labels (sets), and JSON properties (everything else). There's no schema to design up front; meaning accretes as facts, and the shape stays as flexible as the domain it describes.
That flexibility is what makes it a natural substrate for AI agents. Hand an agent an MCP connection to this graph and it can do what agents are uniquely good at: read a domain, model what it learns, connect ideas, and reason over structure instead of prose. Every write is gated, attributed, and appended to an event log — so an agent can reshape the graph freely while you keep the receipts: audit it, replay it to any point in time, or roll a change back with one command. A memory that records why, not just what — in one embeddable file that travels wherever the agent runs: a laptop, a CI job, a serverless function, a Pi.
Small enough to hold in your head. Flexible enough to model anything.
Contents
- Where it fits
- Why not X?
- The idea in 30 seconds
- Design philosophy
- The data model
- Architecture: three front doors, one engine
- Many ontologies: one control plane
- Discovery: read the schema before you query
- Cross-ontology: federation and the backbone
- Event sourcing: the graph is a projection
- The safety gate: invariants vs. policy
- Install
- Quickstart
- Performance
- RDF interop: export, SPARQL, RDF-star
- Command reference
- Project layout
- Development
- License
Where it fits
It's built for graphs you want to own completely — small enough to inspect, fast enough to embed, transparent enough to trust:
- Embedded ontologies — a knowledge graph that ships inside a single app or service, in one file you can copy and version.
- Agent & assistant memory — facts an AI can read and write over MCP, with a full audit trail and one-command rollback.
- Declarative datasets — describe your base graph as YAML/JSON facts and replay them into a queryable graph, deterministically.
- "SQLite, but my data is a graph" — the moment relational rows start describing relationships, this is their natural home.
The design center is read-heavy, single-writer, up to low millions of nodes — the same sweet spot as SQLite itself: one file, in-process, no server to run. That covers a surprising amount of real work. For workloads past that center, Performance maps out exactly where the curve bends and a purpose-built graph engine starts to earn its extra moving parts.
Why not X?
Honest comparisons — reach for the right tool, and know exactly when that tool is this one.
Why not Neo4j?
Use Neo4j when you need deep traversal, complex Cypher pattern matching, concurrent writers, or clustered graph infrastructure. Use kgrdbms when you want a small embedded graph, agent memory, auditability, local-first ownership, and SQLite deployment simplicity.
Why not NetworkX?
NetworkX is great for in-memory algorithms. kgrdbms is for persistent graph state with durable storage, CLI/MCP access, event history, and rollback.
Why not RDFLib?
RDFLib is for RDF graphs and linked-data workflows. kgrdbms is a label property graph with RDF as an export/import boundary, not a triplestore.
Why not just SQLite tables?
Because some domains are naturally entities and relationships, not rectangular tables. kgrdbms keeps the storage boring while making the model graph-shaped.
The idea in 30 seconds
A label property graph needs only four primitives:
| Primitive | What it is |
|---|---|
| Node | a stable id, a kind, a display name |
| Edge | a typed, directed relationship between two nodes |
| Label | set memberships on a node (many per node) |
| Property | a JSON-valued key/value bag on a node or an edge |
Store those in SQLite, add a few indexes, and you have a knowledge graph. Everything else in this project — traversal, an append-only history, a safety gate, the CLI, the MCP server — is built on top of those four facts.
from kgrdbms import Graph
with Graph(path="demo.db") as g:
g.add_node("person:ada", kind="Person", name="Ada Lovelace",
labels={"Person"}, properties={"born": 1815})
g.add_node("field:cs", kind="Field", name="Computer Science")
g.add_edge("person:ada", "field:cs", "FOUNDED", properties={"year": 1843})
print(g.shortest_path("person:ada", "field:cs"))
flowchart LR
ada(["person:ada<br/>:Person<br/>born=1815"])
cs(["field:cs<br/>:Field"])
ada -->|"FOUNDED (year=1843)"| cs
Design philosophy
1. Boring storage is a feature. SQLite already solved durability, transactions, indexes, and recursive queries. We don't reinvent any of it. One file, copy it to back it up, open it with any SQLite tool to inspect it.
2. The schema fits on a screen. Five tables, no surprises. You can read the entire storage layer and know exactly where every fact lives. Legibility beats cleverness.
3. The state is a pure function of data. What you query is a projection.
The source of truth is an append-only log of facts plus an optional declared
seed. Your whole graph is reproducible: state = replay(seed + log). That one
property buys audit, undo, and time-travel for free.
4. Mutation is gated, and the gate has two layers. When you let an agent rewrite your graph over a wire, you need rules. Some rules are configurable (policy); some must be un-negotiable (compiled-in invariants). We separate mechanism from policy on purpose.
5. One engine, many doors. A library call, a kg command, and an MCP tool
all flow through the same gated, logged write path into the same file. There is
no "CLI version" of the truth and "MCP version" of the truth — there's one.
6. Pay for speed only when you ask. Every single write commits on its own
(safe by default). Bulk paths (batch(), add_nodes, add_edges) let you opt
into ~10× throughput when you mean to.
The data model
The graph is five tables. That's the whole storage layer.
erDiagram
nodes ||--o{ node_labels : "is labeled"
nodes ||--o{ node_properties : "has"
nodes ||--o{ edges : "from / to"
edges ||--o{ edge_properties : "has"
nodes {
text id PK
text kind
text name
text created_at
}
node_labels {
text node_id FK
text label
}
node_properties {
text node_id FK
text key
text value_json
}
edges {
text id PK
text from_node FK
text to_node FK
text type
}
edge_properties {
text edge_id FK
text key
text value_json
}
Design notes that matter:
- Edges are unique on
(from_node, type, to_node). Re-adding the same triple updates its properties instead of duplicating it. Idempotent by construction. - Properties are JSON. Values round-trip as whatever JSON type you store — ints, bools, lists, nested objects.
- Foreign keys cascade. Delete a node and its labels, properties, and incident edges go with it, enforced by SQLite.
slug()deduplicates natural-language ids. Two strings that slugify the same become the same node — the load-bearing trick for turning prose concepts into stable ids.- Ids are CURIEs.
person:ada-lovelace,company:apple— a compact URI:prefix:reference, where the prefix is a short stable type token and the reference is slugged (slug(name, prefix="person")mints them). It stays a plain string today and expands to a full IRI only the day you publish to the linked-data world, so interop is a cheap, additive option rather than a tax paid up front. The id is an address, not a record: identity goes in the id, changeable facts go in properties.
A sixth table, graph_events, holds the append-only history (see below). It
shares the same file and connection.
Architecture: three front doors, one engine
flowchart TD
subgraph doors["front doors"]
CLI["kg<br/>(command line)"]
MCP["kgrdbms-mcp<br/>(MCP server)"]
LIB["import kgrdbms<br/>(library)"]
end
SVC["service.py — gated + logged write path"]
GATE{"invariants.enforce<br/>then policy.mutation_check"}
GRAPH[("SQLite file<br/>nodes · edges · labels · properties")]
LOG[["graph_events<br/>(append-only log)"]]
CLI -->|writes| SVC
MCP -->|writes| SVC
LIB -->|writes| SVC
SVC --> GATE
GATE -->|"pass"| GRAPH
GATE -->|"record fact"| LOG
CLI -.->|reads| GRAPH
MCP -.->|reads| GRAPH
LIB -.->|reads / fast bulk writes| GRAPH
The CLI, the MCP server, and your own Python code all mutate through
service.py, so the safety gate and the event-log bookkeeping exist in exactly
one place and can't drift between front doors.
The gate resolves invariants.enforce and policy.mutation_check through
their modules at call time — so editing your policy (or monkeypatching it in a
test) takes effect across every door at once.
The library also has a direct, fast, unlogged path (
g.add_node(...),g.add_nodes(...)). It's the right tool for bulk loading, but those writes are not in the event log — see the warning under Event sourcing.
Many ontologies: one control plane
One graph is the engine. The control plane lets all three front doors
address many named ontologies through that one engine — each its own SQLite
file (and, when a workload earns it, its own engine entirely). You name an
ontology; the resolver routes. Nothing else changes — the gate, the log, and the
service.py write path are exactly the same; only which graph + log they act
on is chosen by name.
flowchart TD
subgraph doors["front doors, now ontology-aware"]
CLI["kg --ontology coffee …"]
MCP["kg_node_get(ontology='coffee')"]
LIB["resolve('coffee')"]
end
RES["resolver.py<br/>name → (backend, events, entry)"]
IDX[["index.db<br/>the registry — itself a kg"]]
REG{"backend registry"}
SQ[("sqlite · live")]
PG[("postgres · live")]
NEO[("neo4j · stub")]
CLI --> RES
MCP --> RES
LIB --> RES
RES -->|"look up the name"| IDX
RES -->|"open the engine"| REG
REG --> SQ
REG -.-> PG
REG -.-> NEO
- The registry is itself a kg. Ontologies are nodes in an index graph
(
<root>/index.db), so listing them is a query and registering one is an upsert — no new storage machinery. A database of databases. - The default ontology is the legacy file. Omit the name and you hit
<root>/graph.db, exactly as before. Multi-ontology is purely additive; nothing moves, and every existing command behaves identically. - Isolation is filesystem-shaped. Each ontology is its own file with its own event log. "Coffee doesn't know Ada" because they are different files — no tenant ids, no row filtering, no leak surface.
- The engine is pluggable. A backend is a factory registered under a name.
sqliteandpostgresare live;neo4jis a stub that routes and fails loudly until built. Most ontologies stay embedded SQLite — the zero-dependency default — while a specific heavy one can be escalated to a purpose-built engine when its workload (not its row count) turns deep. Philosophy #6, "pay for speed only when you ask," generalized from batching to whole engines. - History is owned by the control plane, not the engine. A non-sqlite
ontology keeps its append-only event log in a control-plane SQLite store
(
<root>/ontologies/<slug>/events.db); the engine is just the projection that replay and undo apply to. So a Postgres-backed ontology still gets the full audit / time-travel / one-command-revert story — graph data in Postgres, history in SQLite.
pip install "knowledge-graph-rdbms[postgres]" # the psycopg-backed engine
kg ontology create big --backend postgres \
--location "postgresql://user:pass@host:5432/db" --stance inferential
kg --ontology big node add company:acme --kind Company --name Acme # writes to Postgres
kg ontology create coffee --stance inferential # register (lands in index.db)
kg ontology list # the database of databases
kg --ontology coffee node add drink:latte --kind Drink --name Latte
kg --ontology coffee out drink:latte # scoped to that ontology
Two ways to target a graph, mirroring the MCP ontology argument: --ontology NAME routes through the resolver (named, registered, multi-engine), while
--db PATH stays the raw escape hatch onto one exact file, registry untouched.
Discovery: read the schema before you query
A graph you didn't build is opaque: which kinds exist? which edge types? what
property keys live on a Person? schema() answers all of it in one read —
the map to read before querying, rather than guessing with trial calls.
kg schema # kinds, edge types, labels, and property keys per kind — with counts
kg schema --samples # + a few example ids per kind and the enum-like values a key takes
What comes back is the observed vocabulary — a profile of what's actually in
the graph, not an enforced schema (the graph stays schemaless). The MCP tool
kg_schema carries an instruction to call it first, so an agent reads the map
before it moves. It's a plain read — pure GROUP BY aggregates, no gate — and
like every read it has a federated form (next) that unions the vocabulary across
many ontologies at once.
Cross-ontology: federation and the backbone
The control plane routes to one ontology per call. Two layers sit on top to work across many at once: reads federate, writes go through a backbone.
flowchart TD
Q["kg fed node person:ada"] --> FED["federation.py<br/>multithreaded fan-out"]
FED -.->|own thread + connection| O1[("people")]
FED -.->|own thread + connection| O2[("papers")]
FED -.->|own thread + connection| O3[("coffee")]
O1 --> M["merge, tagged by source<br/>(identity-aware)"]
O2 --> M
O3 --> M
L["kg link same-as …"] --> BB["backbone.py"]
BB --> IDX[["index.db — the backbone<br/>Ref + Prefix nodes, SAME_AS edges<br/>gated + logged"]]
Federation — cross-ontology reads, multithreaded
A federated read is a fan-out: open each member ontology in its own thread (its
own connection — SQLite releases the GIL during a query, so N ontologies read
concurrently) and merge the results tagged by source. There's no separate
"federated" API: every base read takes an optional ontologies=[...] scope.
from kgrdbms import Federation
fed = Federation(["people", "papers", "coffee"]) # or Federation.all()
fed.schema() # unioned vocabulary + a per-ontology breakdown
fed.nodes_by_kind("Person") # [Located(ontology, node), ...] — tagged by source
fed.node("person:ada") # every occurrence across worlds, identity-merged
kg fed schema # union vocabulary across ALL ontologies
kg fed node person:ada # find an id across the federation (identity-aware)
Federation never writes and never silently drops a member (a member that raises
propagates); parallel=False forces sequential for debugging.
The backbone — cross-ontology links
A leaf edge can't cross ontologies: its foreign key lives in one file. The backbone
is where cross-ontology structure lives, and it needs no new storage — it is the
index graph growing new kinds. A link becomes a lightweight Ref proxy node per
endpoint (id <ontology>::<node_id>, FK-satisfied inside the index) joined by an
edge; the prefix registry becomes Prefix nodes. Both go through the same gated +
logged service path, so a cross-domain assertion is audited, reversible, and
replayable exactly like leaf data.
kg link add coffee drink:latte ENJOYED_BY people person:ada # a typed cross-ontology edge
kg link same-as people person:ada wiki person:ada-lovelace # "same real-world entity" (symmetric)
kg link cluster people person:ada # the transitive SAME_AS cluster
kg prefix add person https://kg.local/person/ # CURIE prefix -> IRI (identity backbone)
Identity is opt-in per ontology. By default identity is local — person:ada
in two ontologies are different nodes until you link them via the backbone. An
ontology created with --shared-identity opts into global identity, and federation
then treats same-CURIE nodes across such ontologies as the same entity and merges
them.
Event sourcing: the graph is a projection
The graph you query is a cache. The append-only event log is the source of truth. Every gated mutation records a reversible fact.
flowchart LR
SEED["genesis seed<br/>(optional, declarative)"] -->|"seed once"| PROJ
LOG[["event log<br/>append-only, never deletes a row"]] -->|"replay in order"| PROJ[("graph projection<br/>what you query")]
PROJ -->|"every gated mutation appends a fact"| LOG
Because the log never loses a row, you get three things at once:
- Audit is archaeology. Every change is timestamped and attributed to an
actor.
kg eventstails the history. - Undo is an event, not a delete.
compensate()(CLI:kg revert <id>) emits the inverse event. The original row stays; you can see that it was reverted and by what. - Time travel.
replay(upto_ts=...)rebuilds the projection as of any past instant.
sequenceDiagram
participant U as you
participant L as event log
participant G as graph projection
U->>L: NODE_UPSERT person:ada
L->>G: apply → ada exists
U->>L: revert(that event)
L->>L: append NODE_DELETE (compensates)
L->>G: apply → ada gone
Note over L: both rows remain — history is intact
Two write paths, by design
You choose how each write relates to history:
- Logged path — the CLI, the MCP server, and
service.*. Every mutation is gated and appended to the log, so it's audited, reversible, and reproduced exactly byreplay(). This is the path you want when the timeline is the truth. - Direct path —
g.add_node,g.add_nodes,g.batch(). Writes go straight to the projection: the fastest way to bulk-load or stage data. They aren't in the log, soreplay()(which rebuilds from the log) won't include them.
Pick per workload: direct for raw loading speed, logged when you need the
history. And you can have both — kg import runs the logged path inside a
single batch(), so a bulk load is fast and fully recorded.
The replay seed is just a callable, so you can declare your base graph in YAML/JSON and re-seed deterministically before the logged deltas are applied:
from kgrdbms import Graph, EventLog, replay
def genesis(g):
# re-create your declared base facts (e.g. parsed from YAML)
g.add_node("root", kind="Root", name="root")
replay(graph, events, genesis=genesis) # rebuild from seed + log
replay(graph, events, genesis=genesis, upto_ts=ts) # ...as of an instant
Virtual edges: relationships you don't store
Event sourcing is the right model for curated facts — but the wrong one for a high-cardinality, machine-generated relationship layer that already lives, fresh and authoritative, in some operational store. Mirroring 100k correlation edges into the graph (and the log) every night is wasteful and instantly stale.
A virtual edge inverts that. The ontology stores only a binding — an edge TYPE plus the SQL that resolves its instances against an external source. At traversal time the resolver runs that query, parameterized by the node you're standing on, and synthesizes the edges live. Zero copy, always current, one source of truth — Ontology-Based Data Access in the graph's own terms.
flowchart LR
Q(["kg_edges('company:NVDA')"]) --> U{"union traversal"}
STORE[("event-sourced graph<br/>curated, logged edges")] -->|"stored edges"| U
U -->|"for each _VirtualEdge binding"| R["resolver<br/>runs bound SQL, ? = 'NVDA'"]
R -.->|"parameterized query"| EXT[("operational store<br/>system-of-record")]
EXT -.->|"rows → synthesized edges<br/>_virtual: true"| R
R --> U
U --> OUT[/"merged edge list<br/>stored + live"/]
# Bind CO_HELD_WITH to a query over the operational store (here, any DB-API source)
kg_virtual_edge_add(
edge_type="CO_HELD_WITH",
query="SELECT b AS to_id, shared FROM co_held WHERE a = ?", # '?' sqlite, '%s' postgres
dsn_env="OUROBOROS_DSN", # credentials by reference — never in the graph
source="id_slug", # company:NVDA -> bind "NVDA"
target_id_template="company:{value}",
prop_cols=["shared"], directions="both", ontology="market",
)
# now ordinary traversal unions stored + virtual edges; virtual ones carry _virtual:true
kg_edges("company:NVDA", direction="out", ontology="market")
Preguntas sobre Knowledge Graph RDBMS MCP
¿Cómo agrego Knowledge Graph RDBMS MCP a Claude Code?
Ejecuta claude mcp add knowledge-graph-rdbms -- uvx knowledge-graph-rdbms en tu terminal. Añade --scope user para que esté disponible en todos los proyectos.
¿Cómo agrego Knowledge Graph RDBMS MCP a Cursor?
Abre ~/.cursor/mcp.json y añade el servidor en mcpServers, usando la configuración de Cursor que se muestra en la sección de instalación de arriba.
¿Qué tipo de modelo de grafo utiliza?
Utiliza un modelo de grafo de propiedades con etiquetas que consiste en nodos, aristas tipadas, etiquetas y propiedades JSON.
¿Puedo revertir los cambios realizados por un agente de IA?
Sí, cada escritura se registra en un log de eventos de solo anexado (append-only), lo que te permite revertir eventos específicos o reproducir el grafo hasta un punto exacto en el tiempo.
¿Cómo se compara con Neo4j?
Neo4j es mejor para recorridos profundos y coincidencia de patrones complejos en entornos agrupados, mientras que esta herramienta está diseñada para el almacenamiento de grafos local-first, pequeño y embebido, ideal para la memoria de agentes.