GraphRAG · Model Context Protocol · zero ETL

An AI agent queries your warehouse as a graph.

No graph database. No pipeline. The agent speaks Bolt to ClickGraph / DeltaGraph, which translates Cypher to ClickHouse or Spark SQL and runs it where the data already lives.

user   FOLLOWS  ·  watch the 2-hop traversal light up
The loop

Two tools are all it takes

Point any MCP-capable assistant at the Bolt endpoint. It gets get_neo4j_schema and read_neo4j_cypher — enough to discover the graph and answer questions grounded in live warehouse rows.

01

Discover

Agent calls get_neo4j_schema — learns the labels, properties, and relationships.

02

Translate

Plain-English question → Cypher, written from the discovered schema.

03

Traverse

Agent calls read_neo4j_cypher; Cypher is compiled to warehouse SQL.

04

Ground

Real rows return; the answer is grounded in the warehouse, not a guess.

Six scenarios · verified live

What traversal does that similarity can't

The first is ordinary retrieval. Everything after it depends on following edges across the warehouse — which a vector store cannot do. Every result below is the actual rows returned from the DeltaGraph Databricks social graph.

01 · BASELINE

Who are the most influential users?

Rank users by how many people follow them.

1 hop · aggregate vector store: roughly
read_neo4j_cypher warehouse
MATCH (u:User)<-[:FOLLOWS]-(f:User)
RETURN u.name AS influencer, count(f) AS followers
ORDER BY followers DESC, influencer LIMIT 5
5 rows
influencerfollowers
Rachel5
Tina5
Xander5
Alice3
Jack3
02 · RECOMMENDATION

Who should Alice follow?

Friends of friends she doesn't already follow — ranked by mutual connections.

2 hops · anti-join vector store: no
read_neo4j_cypher warehouse
MATCH (me:User {name:'Alice'})-[:FOLLOWS]->(f:User)-[:FOLLOWS]->(fof:User)
WHERE fof <> me AND NOT (me)-[:FOLLOWS]->(fof)
RETURN fof.name AS suggested, count(DISTINCT f) AS mutual_connections
ORDER BY mutual_connections DESC, suggested LIMIT 5
3 rows
suggestedmutual_connections
Ben1
Henry1
Victor1
fof <> me is node-identity comparison — it resolves to the schema's user_id column, not a literal .id, so it runs unchanged on ClickHouse and Databricks (fix #1076).
03 · CONTENT GROUNDING

What is Alice's network talking about?

Pull the posts authored by the people Alice follows.

2 hops · scoped vector store: no
read_neo4j_cypher warehouse
MATCH (me:User {name:'Alice'})-[:FOLLOWS]->(f:User)-[:AUTHORED]->(p:Post)
RETURN f.name AS author, p.content AS post
ORDER BY p.created_at DESC LIMIT 5
5 rows
authorpost
XanderGraph neural networks
TinaSubqueries and comprehensions
TinaUNION queries in Cypher
DavidClickHouse integration patterns
DavidBuilding social networks with graphs
This is GraphRAG, literally. The answer is grounded in warehouse rows reached by traversal, scoped to Alice's neighborhood — not a fuzzy top-k over the whole corpus.
04 · CONNECTION PATH

How is Alice connected to Rachel? To Ben?

Find the shortest chain of relationships between two specific people.

variable-length vector store: no
read_neo4j_cypher warehouse
// read the whole chain, by name
MATCH path = shortestPath((a:User {name:'Alice'})-[:FOLLOWS*1..5]->(b:User {name:'Rachel'}))
RETURN [n IN nodes(path) | n.name] AS connection_path, length(path) AS hops
1 row
AliceDavidHenrySamRachel

hops = 4 — the names come back in traversal order, materialized as a parallel array carried alongside the path.

Path readout by name (#1079) shipped from building this demo — the third real bug the MCP loop surfaced. [n IN nodes(path) | n.name] now returns the ordered chain, verified live on ClickHouse and Databricks.
05 · ENGAGEMENT

Which posts are resonating, and who wrote them?

Rank posts by distinct likers, joined back to their authors.

3-way join vector store: no
read_neo4j_cypher warehouse
MATCH (author:User)-[:AUTHORED]->(p:Post)<-[:LIKED]-(liker:User)
RETURN author.name AS author, p.content AS post, count(DISTINCT liker) AS likes
ORDER BY likes DESC, post LIMIT 5
5 rows
authorpostlikes
UmaGraph machine learning5
GraceGraph algorithms overview4
BenGraph query languages comparison4
LeoIndexing strategies for graphs4
IrisReal-time graph analytics4
06 · HYBRID — VECTOR + GRAPH

Semantic entry, then graph expansion

Use similarity to find the entry point; use the graph to traverse, explain, and re-rank. The question ≈ "deep learning spanning ML and NLP", encoded as a query vector.

vector + traversal vector store: step 1 only
1 VECTOR — what is the question about?
read_neo4j_cypher warehouse
MATCH (t:Topic)
RETURN t.name AS topic, round(1 - cosineDistance(t.embedding, $q), 4) AS similarity
ORDER BY similarity DESC LIMIT 3
3 rows
topicsimilarity
Natural Language Processing0.6844
Machine Learning0.6844
Databases−0.0001

The two AI topics surface; unrelated topics sit at ~0. A vector store stops here.

2 GRAPH — how does that topic connect, by name?
MATCH path = (start:Topic {name:'Machine Learning'})-[:RELATED_TO*1..2]->(t2:Topic)
RETURN [n IN nodes(path) | n.name] AS topic_chain, length(path) AS hops
3 rows · path readout #1079
Machine LearningNatural Language ProcessingComputer Vision

[n IN nodes(path) | n.name] reads the neighborhood back in traversal order — similarity search cannot represent this.

3 HYBRID — re-rank the grounded neighborhood
MATCH (t:Topic)-[:DISCUSSES]->(d:Document)
WHERE t.name IN ['Machine Learning','Natural Language Processing','Computer Vision']
RETURN d.title AS document, round(1 - cosineDistance(d.embedding, $q), 4) AS similarity
ORDER BY similarity DESC LIMIT 5
top 3 of 5
documentvia_topicsimilarity
Introduction to Neural NetworksMachine Learning0.6844
Transformer Architecture ExplainedNatural Language Processing0.6844
Object Detection with YOLOComputer Vision−0.0016

The graph scopes the candidates; the vector re-ranks them. The two foundational ML/NLP papers rise; the off-topic CV paper sinks. Relevant and explainable.

4 ALL AT ONCE — the whole loop in one statement
MATCH (ml:Topic {name:'Machine Learning'})-[:RELATED_TO*1..2]->(t2:Topic)-[:DISCUSSES]->(d:Document)
RETURN DISTINCT d.title AS document, t2.name AS via_topic,
       round(1 - cosineDistance(d.embedding, $q), 4) AS similarity
ORDER BY similarity DESC
3 rows · unblocked by #1083
documentvia_topicsimilarity
Transformer Architecture ExplainedNatural Language Processing0.6844
Deep Learning for NLPNatural Language Processing0.0943
Object Detection with YOLOComputer Vision−0.0016

Variable-length graph expansion feeds a vector re-rank in one Cypher statement — no client round-trip between traversal and ranking. The NLP-adjacent paper rises; the CV paper sinks.

ClickHouse-native vectors. cosineDistance / L2Distance / gds.similarity.* and CALL db.index.vector.queryNodes(…) run on the ClickGraph/ClickHouse endpoint. Variable-length expansion feeds the chained DISCUSSES hop directly — no WITH barrier — which composes once the anonymous-alias collision (#1083, fixing #1081) is in place. One naming caveat: the path start node is ml, not t — a variable-length-path endpoint named exactly t collides with ClickGraph's reserved internal VLP-CTE alias and is rejected with an actionable error (#1085); any other name resolves correctly.
The point

Same warehouse tables. Zero copies.

ScenarioTraversalCould a vector store do it?
01 · Influencers1 hop, aggregateroughly
02 · Recommendation2 hops + anti-joinno — answer is in graph shape
03 · Content grounding2 hops, scopedno — needs edge-scoping
04 · Connection pathvariable-lengthno — not a vector
05 · Engagement3-way joinno — relationship aggregation
06 · Hybrid vector + graphvector seed → traverse → re-rank, one statementstep 1 only — traversal & re-rank compose in one query
Warehouse-portable

Swap the port. Same Cypher runs.

The graph is a query-time view over the warehouse, not a copy of it. Point the MCP server at ClickHouse or Databricks — every query on this page runs unchanged.

# ClickHouse (ClickGraph)
--db-url bolt://localhost:7687

# Databricks (DeltaGraph)
--db-url bolt://localhost:7688