Skip to main content
Glama
halaprix

bd-graph MCP Server

by halaprix

bd-graph

A temporal knowledge graph over your beads issue tracker — built by coding agents, queried over MCP, no LLM API keys required.

Your bd store accumulates decisions, invariants, supersessions, and provenance — but keyword search can't answer shape questions: which rules currently govern this component, and since when? Was that memory superseded? Which lasting decisions came out of that epic? bd-graph derives a Neo4j graph from your bd corpus (issues, memories, ADRs) that answers exactly those, and exposes it to coding agents through a read-only MCP server.

The temporal layer is the point: edges carry valid_from / invalid_at, and SUPERSEDES / INVALIDATES edges record when guidance flipped — so agents retrieve the current rule instead of a stale one.

The graph is a derived, disposable index. bd stays the source of truth. Rebuild it any time with one command; never write project state to it.

How it works

bd store ──export──▶ corpus/ ──┬─▶ load_p1.py  (deterministic: structure + regex)
docs/adr ──copy───▶            └─▶ load_p2.py  (semantic: agent-extracted JSON)
                                        │
                     Neo4j  ◀───────────┘
                       ▲
        mcp-neo4j-cypher (read-only MCP) ◀── your coding agents

Phase 1 — deterministic (no LLM). Issue dependency trees (CHILD_OF, DEPENDS_ON, DISCOVERED_FROM), ADR→issue tracking links, ADR supersession status lines, and regex cross-mentions between issues/ADRs/memories/PRs. This alone is ~90% of the edges.

Phase 2 — semantic (agents, not APIs). Your coding agents (Claude Code subagents, Codex, whatever you run) extract Component and Rule entities and GOVERNS / ESTABLISHES / SUPERSEDES / INVALIDATES edges with dates, using the prompt template in prompts/extraction.md. Because the extractor is your agent harness, this costs subscription tokens — no OPENAI_API_KEY, no embeddings provider, nothing.

Precision pass. A second wave of agents adversarially verifies every extracted edge against its source (prompts/verification.md); apply_verdicts.py deletes refuted edges and tags unverifiable ones confidence: "unsupported". In practice verifiers kill 5–15% of edges — mostly fabricated dates and misattributed components.

Related MCP server: graphforge-neo4j MCP server

Data model

Node

Key

From

Issue

id

every bd issue incl. closed (title, status, dates, close_reason, summary)

Memory

key

bd memories --json

ADR

id

NNNN-slug.md files (title, status, date)

PR

number

regex over all text

Component, Rule

name

Phase-2 extraction

Meta

freshness stamp: generated_at, corpus counts

Phase-1 edges: CHILD_OF, DEPENDS_ON, DISCOVERED_FROM, TRACKED_IN, SUPERSEDES (ADR status lines), MENTIONS, REFERENCES_PR. Phase-2 edges: ABOUT, ESTABLISHES, GOVERNS, CONSTRAINS, SUPERSEDES, INVALIDATES, PART_OF — each with fact, valid_from, invalid_at, confidence, source.

Quickstart

Requirements: Python ≥3.11, Docker, a beads store (tested against bd 1.1.0); pipx install mcp-neo4j-cypher for the MCP server (no pipx? python3 -m pip install --user pipx first).

git clone <this repo> && cd bd-graph
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt

docker run -d --name bd-graph-neo4j --restart unless-stopped \
  -p 127.0.0.1:7474:7474 -p 127.0.0.1:7687:7687 \
  -v bd-graph-data:/data -e NEO4J_AUTH=neo4j/<pick-a-password> neo4j:5-community

cp config.example.toml config.toml   # edit: id_prefix, password, repo_root, vocab
                                     # repo_root IS REQUIRED for the first run —
                                     # it's what lets regenerate.sh export your corpus
mkdir -p corpus extracted
./regenerate.sh                       # exports corpus, builds Phase 1
.venv/bin/python test_graph.py        # structural + recall assertions

Then wire the MCP server into your agent tool (Claude Code shown):

claude mcp add bd-graph --scope local -- mcp-neo4j-cypher \
  --db-url bolt://localhost:7687 --username neo4j --password <password> \
  --database neo4j --transport stdio --read-only

For Phase 2, dispatch extraction agents with prompts/extraction.md, verify with prompts/verification.md, then:

.venv/bin/python apply_verdicts.py && ./regenerate.sh

Try it without a bd store

demo/ contains a synthetic corpus (5 issues, 3 memories, 2 ADRs, a sample extraction) — setup commands in demo/config.demo.toml. The demo exercises every edge type including a supersession chain.

Example queries

// pre-flight: what binds this component, and since when
MATCH (r:Rule)-[g:GOVERNS|CONSTRAINS]->(c:Component {name:'Api'})
OPTIONAL MATCH (src)-[e:ESTABLISHES]->(r)
RETURN r.name, g.fact, e.valid_from, coalesce(src.id, src.key);

// staleness chain: what superseded what, in date order
MATCH (a)-[s:SUPERSEDES|INVALIDATES]->(b)
RETURN coalesce(a.id,a.key,a.name), type(s), s.valid_from,
       coalesce(b.id,b.key,b.name) ORDER BY s.valid_from;

// epic rollup: lasting decisions that came out of an epic
MATCH (adr:ADR)-[:TRACKED_IN]->(:Issue)-[:CHILD_OF*1..2]->(:Issue {id:'demo-ep1'})
MATCH (adr)-[:ESTABLISHES]->(r:Rule) RETURN adr.id, r.name;

// issue provenance: where it came from, what it left behind
MATCH (i:Issue {id:'demo-bug7'})
OPTIONAL MATCH (i)-[:DISCOVERED_FROM]->(p)
OPTIONAL MATCH (i)-[:REFERENCES_PR]->(pr)
RETURN p.id, collect(pr.number);

Keeping it honest

  • Freshness is queryable in-band: MATCH (m:Meta) RETURN m.generated_at, m.issues. Tell your agents to check it at first use per session.

  • ./doctor.sh names the failing layer (container / bolt / data / staleness) with the exact fix command; exit 0/2/1 = healthy/stale/broken. --full chains both test suites.

  • test_graph.py asserts corpus↔graph parity, dependency-edge parity, no dupes/self-loops, ISO dates, all corpus supersession lines present — plus any project-specific [[probes]] you define in config.

  • test_mcp.py drives the MCP server over raw stdio JSON-RPC and proves the write path is closed.

  • Suggested agent-instruction snippet (CLAUDE.md / AGENTS.md):

    Use the bd-graph MCP for shape questions over the tracker (what governs a component, supersession chains, epic rollups, provenance). It is a derived index — on any mismatch trust bd. Check Meta.generated_at at first use; on any problem run doctor.sh and say explicitly that you're falling back to the bd CLI. Never fail silently.

Design notes & limitations

  • Recall is deliberately partial (~8 edges/doc): the graph is a map, not a substitute for reading the document it points to.

  • ADR parsing assumes the common # ADR-NNNN: title + - **Status:** / - **Date:** / - **Tracked in:** header block; adjust ADR_META in load_p1.py for other formats. Duplicate ADR numbers merge into one node — treat that as a lint error in your repo.

  • Extraction quality depends on your [vocab] component list; keep it short and canonical.

  • Inspired by the Graphiti/Neo4j temporal-graph approach (getzep/graphiti); this project trades Graphiti's embedding-based hybrid search for zero API keys and agent-native extraction. If you have API keys and want semantic search, Graphiti is the heavier-duty option.

Security

Everything is local-only by design: Neo4j binds to 127.0.0.1, the MCP server is registered --read-only, and config.toml (credentials, project vocab) is gitignored. Your corpus (corpus/, extracted/) contains your project's internal knowledge — both are gitignored; never commit or publish them. Note that mcp-neo4j-cypher takes the password as a CLI argument (visible in ps on the local machine) — one more reason the password must be a local-only throwaway, never a reused credential.

License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Exposes a Neo4j graph database as MCP tools, enabling AI agents to run read and write Cypher queries.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Lets AI agents query a computerized system inventory as a knowledge graph using Cypher, enabling blast radius, data lineage, regulation checks, and change impact assessments while preventing hallucinated regulatory claims.
    -