redraft
Allows opening and navigating the project graph directly in Obsidian as plain Markdown with wikilinks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@redraft assemble a report on recent design decisions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
redraft
AI-assisted project development and tracking: a typed, versioned design record for engineering projects, driven by your agent over MCP.
The decision record, as the operator UI renders it: every decision grouped under the question it addresses — accepted and rejected alternatives side by side, with rationale and tradeoffs. The road not taken stays part of the record.
Why
Most project documentation is written as a design book: a linear narrative, drafted once, read rarely, stale within a sprint. That is not how engineering projects actually produce knowledge. Decisions, rationale, requirements, and open questions show up as sparse pieces at wildly different levels of a project's hierarchy, at different times, and they only become useful once linked back to what they addressed, what they ruled out, and what they later superseded. redraft treats that as the real shape of the data instead of fighting it: typed nodes and typed edges, stored as plain Markdown so git stays the source of truth. An MCP server gives your agent the tools to organize the graph as the project evolves; reports collapse it back into review-grade documents on demand -- including the roads not taken.
Why not an ADR folder, a wiki, or plain Obsidian? All three store prose; none store structure an agent can act on. ADRs are linear, append-only files -- the rejected alternative lives in a paragraph nobody can query, and nothing links a decision to the requirement it satisfies or the question it answered. A wiki adds links but lives outside git, away from the code and the agent's hands. Obsidian gets the storage format right -- which is why a redraft graph is an Obsidian-compatible vault -- but a vault alone has no typed schema, no integrity checks, and no tools an agent can call. redraft is that missing layer: a closed type system, structural invariants the server actually enforces (cycles, collisions, dangling links), hybrid retrieval built for an agent, and reports that put accepted and rejected alternatives in the same table.
Related MCP server: kb
Quickstart
1. Clone and install the engine.
git clone https://github.com/KaelinGraf/redraft.git redraft
cd redraft
uv tool install --from . redraftThis puts one command, redraft, on your PATH (run uv tool update-shell once if it
warns the bin directory isn't there yet). Working on the engine itself instead of just using
it? Skip the tool install and run uv sync -- uv run redraft then resolves to your local
checkout.
2. Birth a graph.
redraft init ~/my-projectWrites graph/nodes/ (empty), a CLAUDE.md (the organizing protocol, verbatim), a
.mcp.json pointing at this graph (location-independent -- it keeps working if the graph
repo is later cloned to a different path or device), and, by default, its own independent
git history.
redraft init --help lists every option (--no-git, --project-name). Already have a
graph repo from another device? A plain git clone is all it takes -- see "Engine and graph
are separate repos" below.
3. Start working.
cd ~/my-project
claude.mcp.json tells Claude Code how to launch redraft serve for this graph -- the tools
(create_node, search_nodes, assemble_report, and the rest) appear automatically once
the session starts.
redraft speaks standard MCP over stdio, so any MCP client can drive it the same way --
point yours at redraft serve with REDRAFT_DIR set. Claude Code is the documented path
because the shipped skills and the SessionStart hook target it.
redraft serve needs a uv-managed Python 3.12 (pinned in .python-version): the retrieval
stack requires a sqlite3 build with FTS5 and extension-loading support, which a
python-build-standalone interpreter has and many system/distro Pythons don't -- it fails
fast with a clear error at startup if yours is missing either, rather than failing
confusingly inside a later tool call.
Registering redraft in an existing project
The graph doesn't need to live inside a code project, and most of the time it shouldn't --
birth it as a sibling directory, then register it at Claude Code's project scope instead of
hand-writing a .mcp.json:
claude mcp add --scope project -e REDRAFT_DIR=<abs graph dir> -e PYTHONPATH= --transport stdio redraft -- redraft servePYTHONPATH= matters in any shell where something else (a ROS workspace is a common culprit)
has already polluted it -- an inherited entry can shadow redraft's own dependencies or
autoload an incompatible plugin.
The operator UI
redraft ui serves a local web app over the same graph: an outline-first three-pane
view for reading and full authoring (create with live duplicate-detection hints, link,
batch-link, reparent, merge, upload, snapshot), plus a live document render, the
decision tables above, a dependency-aware milestone timeline, and a force-directed map.
Outline — spine tree, node detail, attention strip | Doc — the graph, rendered as a design document |
Timeline — milestones with real dependencies | Map — the whole project, one connected graph |
Ask your assistant for a technical report and it renders the graph into a review-grade
LaTeX document — decision tables, open questions, and all — saved under reports/ and
displayed typeset in the Reports tab:
Session-start overview
Every graph redraft init births ships with a Claude Code SessionStart hook
(.claude/settings.json) alongside CLAUDE.md/.mcp.json. It runs redraft overview --
the same cheap, embedding-free query the overview MCP tool and the graph://project/overview
resource expose -- and injects the resulting markdown map (spine roots, their major branches,
and per-branch tallies for open questions and unjustified decisions) straight into context at
the start of every new session, so the first real call is targeted instead of a blind guess.
It's on by default because that's the point of the feature; to turn it off, delete
.claude/settings.json (or just its one SessionStart entry, if you've since added other
hooks to that file) from the graph repo.
Engine and graph are separate repos
This repo (the engine) is the MCP server, the storage/index code, and the CLI. It holds
no project data. A project's graph -- its node files, its CLAUDE.md (the organizing
protocol, written verbatim by redraft init), and its .mcp.json -- lives in its own repo,
with its own git history and its own remote, versioned entirely separately from the engine.
Why split them: the engine is replaceable tooling that gets upgraded, reinstalled, or forked; the graph is the irreplaceable design record. Coupling their lifecycles would mean every engine change risks the data, and every device would need the full engine source checked out just to read a graph.
Upgrading
Reinstalling a newer engine doesn't reach out and touch a graph you already have -- an
existing graph's CLAUDE.md (the organizing protocol) and .claude/settings.json (the
SessionStart hook) were written once, back when you ran redraft init, and stay exactly as
they were even after the engine that ships newer versions of both is installed. Run
redraft sync inside the graph to catch it up: it refreshes those two files (plus
.gitignore's required lines) to whatever the newly installed engine ships, commits the
refresh if the graph is a git repo, and never touches graph/nodes/, docs/, or reports/
-- your project data is untouched either way. CLAUDE.md is engine-managed for exactly this
reason: don't hand-edit it, since the next sync overwrites it -- project-specific content
belongs in the graph's nodes, not the protocol file.
uv tool install --reinstall --no-cache --from . redraft
cd ~/my-project
redraft syncRestart your Claude Code session afterward so it picks up the refreshed protocol and hook.
What's inside
Node types:
concept,decision,rationale,requirement,constraint,idea,question,artifact,observation,milestone-- a closed set, each with its own narrowstatuslifecycle where one applies (onlydecision,question, andmilestonecarry astatusat all).Edge semantics:
part_ofis the one scalar edge -- the hierarchy spine -- plus eight typed list edges:justifies,supersedes,addresses,depends_on,contradicts,references,derived_from,relates_to.docs/protocol/organizing-protocol.mdis the full vocabulary and the rules an agent follows to use it well.Hybrid retrieval: FTS5 lexical search plus local sentence-embedding vector search (fastembed, no network calls), merged by reciprocal rank fusion.
Integrity checks: dangling edges, title collisions, orphaned nodes, decisions with no recorded rationale -- checked against the derived SQLite index, which is fully rebuildable from the text files and never a second source of truth.
Reports with decision tables:
assemble_reportwalks apart_ofsubtree and groups every decision (any status) under the question or requirement it addresses, with rationale, tradeoffs, and supersession chains already resolved -- accepted and rejected alternatives rendered side by side, so the road not taken is part of the record instead of lost to it.Versioning is local git commits: every batch of graph writes ends in a
snapshot— a pathspec-scoped commit in the graph repo's own git history. Local commits are the version history (diffable, revertable, blameable node-by-node); adding a remote and pushing is optional and entirely yours to decide.Report generation, built in: the organizing protocol every graph ships teaches the driving assistant to collapse the graph into technical writeups —
assemble_reportfor the full spine with decision tables,briefing(query)for topical context — saved as LaTeX underreports/and rendered in the operator UI. Theredraft-reportskill packages the flow so "generate a technical report" is a one-liner.Saved reference material:
docs/is a local, gitignored cache of referenced research — re-fetchable on any device from thesource_urlrecorded on itsartifactnode via theredraft-fetch-docsskill — not versioned, so it doesn't travel with the graph and keeps the repo lean.graph/attachments/, which holds files uploaded onto a specific node through the operator UI, remains the committed option for files that must persist with the graph.
Obsidian compatibility
The graph is plain Markdown with YAML frontmatter and [[wikilink]]-style edges under the
hood -- open a graph repo directly in Obsidian and it renders and navigates natively.
redraft is only required to organize the graph, never to read it.
Claude Code skill
skills/redraft-init/SKILL.md walks Claude Code through birthing a graph for a project and
registering it, so you can just ask instead of following the Quickstart by hand. Symlink or
copy it into ~/.claude/skills/:
ln -s "$(pwd)/skills/redraft-init" ~/.claude/skills/redraft-initskills/redraft-fetch-docs/SKILL.md re-fetches a graph's local docs/ cache (see above) from
each artifact node's recorded source_url; symlink it the same way (ln -s "$(pwd)/skills/redraft-fetch-docs" ~/.claude/skills/redraft-fetch-docs).
skills/redraft-report/SKILL.md packages the report flow: assemble the graph, write a
grounded LaTeX technical report into reports/, snapshot it — so "generate a technical
report" is a one-liner in any session with a redraft graph connected
(ln -s "$(pwd)/skills/redraft-report" ~/.claude/skills/redraft-report).
Running tests
env -u PYTHONPATH uv run pytest -qLicense
Apache-2.0. See LICENSE.
Available Tools
25 toolsassemble_reportA
Collapse root_id's part_of subtree into a report bundle: nested sections with attached cross-links, decision_tables grouped by driver question/requirement (any status, cross-branch — see report-bundle-v2.md), and per-section gaps (open questions, decisions lacking rationale). The agent writes the narrative; this never summarizes.
Inputs: root_id (required), depth, include_edge_types. Returns: root_id, sections, generated_at, contradictions, +2 more
briefingA
One-call topic guidance: hybrid search hits, their 1-hop neighborhood, open questions, and unjustified decisions scoped to the topic. Prefer this over grepping graph files for "what do we know about X" -- it returns the matched nodes together with their neighborhood and open gaps, which grep cannot assemble.
Inputs: query (required), k. Returns: report-bundle-v2.md section 6. scope := hits ids unioned with every src/dst app…
case_collisionsB
Groups of >=2 node ids identical under Unicode NFC+casefold.
Inputs: no named parameters. Returns: result
contradictionsA
{a, b} node pairs linked by a contradicts edge (a dangling contradicts edge is
surfaced by dangling_edges() instead, not here).
Inputs: no named parameters. Returns: result
create_edgeA
Create a directed edge. STRICT (pin R2): dst must already exist -- raises not_found otherwise, no dangling-edge creation via this tool. A part_of edge when src already has a different part_of parent raises collision; call delete_edge first to re-parent.
Inputs: dst (required), src (required), type (required). Returns: dst, src, type, warnings, +1 more
create_edgesA
Create many edges in ONE atomic operation (GraphStore.create_edges): every edge is validated together -- including part_of single-parent/cycle rules considering the OTHER edges in this same batch -- before anything is written. On any hard error the whole call fails, with the store's own message, and NOTHING from this batch is applied (no partial report). Prefer this over looping create_edge when linking several edges from one dump (organizing-protocol.md §3.4).
Inputs: edges (required). Returns: result
create_nodeA
Create a new node. Does not attach it anywhere -- call create_edge next.
Inputs: type (required), title (required), body, status, +1 more. Returns: id, body, type, title, +4 more
dangling_edgesB
Edges whose src and/or dst no longer resolves to a node.
Inputs: no named parameters. Returns: result
decisions_without_rationaleA
Decisions (any status) with no inbound justifies edge.
Inputs: no named parameters. Returns: result
delete_edgeD
Remove an edge.
Inputs: dst (required), src (required), type (required). Returns: ok, existed, orphaned_inbound_edges
delete_nodeB
Remove a node's file (Git retains history). Inbound edges from other nodes are left dangling by design -- surfaced here, not silently dropped.
Inputs: id (required). Returns: ok, existed, orphaned_inbound_edges
find_similarA
Vector-only near-duplicate search -- the dedup primitive. text_or_id may be an existing node's id (reuses its cached vector, excludes it from its own results) or free text (embedded fresh). Prefer this over grepping graph files to check "is this already recorded" -- it matches paraphrase across different wording, which grep cannot.
Inputs: text_or_id (required), k. Returns: result
get_nodeB
Fetch a node. neighbor_depth > 0 also returns nodes reachable within that many hops (any edge type, any direction) and the node's own direct edges.
Inputs: id (required), neighbor_depth. Returns: node, edges, neighbors
get_subgraphB
Everything connected to root_id within depth hops, any direction.
Inputs: root_id (required), depth, edge_types. Returns: edges, nodes
merge_nodesA
Dedup: repoint drop's inbound edges to keep, migrate drop's outbound edges onto keep (deduped; a part_of conflict is a warning, never a failure), delete drop's file. Body is NOT auto-merged -- inspect dropped_body_preview and update_node(keep_id, ..., mode='append') first if unique content from drop should survive (pin R3).
Inputs: drop_id (required), keep_id (required). Returns: Mirrors GraphStore.merge_nodes' MergeOutcome exactly (design amendment A2, I4)…
neighborsC
Single-hop edges touching id, optionally filtered by type/direction.
Inputs: id (required), direction, edge_types. Returns: result
open_questionsC
Question nodes with status='open'.
Inputs: no named parameters. Returns: result
orphansA
Nodes with zero edges in any direction/type (a legitimate root-level concept with no part_of parent is NOT flagged for that alone).
Inputs: no named parameters. Returns: result
overviewA
Cheap, shallow map of the project's shape: every spine root, each root's direct part_of children as branches with per-subtree tallies (descendants, open questions, decisions by status, unjustified decisions), whole-graph totals, and the top open questions. Prefer this over grepping graph files for the project's shape -- it returns the spine structure and tallies in one cheap call, which grep cannot compute. Call this (or read graph://project/overview) at the start of a session, before anything else, to load the project's shape and make your first real call targeted instead of blind.
Inputs: no named parameters. Returns: roots, totals, floating_by_type, top_open_questions
reindexA
Rebuild the derived index from graph/nodes/*.md. Run after a manual git pull/merge, or to recover from a deleted/corrupted index/ dir.
Inputs: no named parameters. Returns: Mirrors GraphStore.reindex()'s ReindexStats exactly (design-storage 5.2).
rename_nodeA
Rename a node, rewriting every inbound frontmatter wikilink to the new id. Inline [[wikilinks]] in Markdown body prose are NOT rewritten (the index only tracks frontmatter edges) -- flagged in body_references_not_updated, not silently left unmentioned.
Inputs: id (required), new_title (required). Returns: new_id, old_id, relinked, body_references_not_updated
search_nodesA
Hybrid FTS+vector search, fused by Reciprocal Rank Fusion. types/status filter each branch's candidates before fusion (a filtered-out result never consumes a fusion rank slot it wasn't really competing within). Prefer this over grepping graph files for ANY lookup -- it matches paraphrase, filters by type/status, and returns typed nodes, which grep cannot.
Inputs: query (required), k, types, status. Returns: result
snapshotC
git add (pathspec-scoped) + commit; push only if push=True (defaults off to avoid a surprise network operation).
Inputs: message (required), push. Returns: Mirrors GraphStore.snapshot()'s CommitResult exactly (design-storage 7.2).
staleB
Nodes untouched since before_iso. types/statuses omitted (None) use the default non-terminal question/decision/milestone set; an explicit [] matches nothing (not reinterpreted as 'omitted').
Inputs: before_iso (required), types, statuses. Returns: result
update_nodeA
Update a node's body/status/properties. Cannot change title or edges
(use rename_node / create_edge / delete_edge for those) -- pin R5.
properties MERGES onto the existing dict (it cannot delete a key); to clear a
properties key, list it in remove_properties instead -- removal is applied after the
merge, so it wins if the same key appears in both, and removing an absent key is a
no-op.
Inputs: id (required), body, mode, status, +2 more. Returns: id, body, type, title, +4 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 25 tools
Most tools target distinct resources/actions, and descriptions explicitly differentiate the potentially overlapping ones (create_edge vs atomic create_edges, search_nodes vs find_similar vs briefing). The search/discovery cluster (search_nodes, find_similar, briefing, overview) and get_node(neighbor_depth) vs get_subgraph/neighbors carry mild overlap, but guidance mitigates it.
Core mutators follow a clean verb_noun pattern (create_node, update_node, delete_node, rename_node, merge_nodes, create_edge, delete_edge). Read-only analytics/report tools use bare-noun forms (orphans, stale, contradictions, overview, briefing), which is a consistent secondary convention rather than random mixing.
25 tools sits at the heavy end of the acceptable band. Each is distinct, but the seven separate audit queries (decisions_without_rationale, open_questions, orphans, contradictions, stale, dangling_edges, case_collisions) suggest the surface could be consolidated somewhat.
Full node lifecycle (create/get/update/rename/merge/delete), edge create-batch/delete, search/dedup, maintenance (reindex/snapshot), and rich reporting/audit coverage are all present. Only minor gaps (no in-place edge update, though delete+recreate covers it) leave it just shy of complete.
Maintenance
Related MCP Connectors
MCP server for generating rough-draft project plans from natural-language prompts.
Repository knowledge graph MCP server for codebase understanding and debugging.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceLiving typed knowledge graph for software projects, exposed over MCP so coding assistants can capture and query modules, capabilities, flows, events, rules and decisions across sessions. SQLite-backed, schema-validated, ships as a Claude Code plugin.MIT
- AlicenseNot gradedqualityDmaintenanceA local MCP server that provides a shared context and learning foundation across multiple AI tools (Claude, Copilot, Codex) for multiple projects, enabling persistent knowledge, decisions, and gap reflection through note storage.89 PyPIMIT
- AlicenseAqualityBmaintenanceCode intelligence MCP server for Claude Code providing multi-project code graph, semantic search, session history, knowledge base, and web search.154MIT
- AlicenseNot gradedqualityDmaintenanceA local knowledge graph server that captures and provides access to a project's decision history, enabling AI agents to navigate past decisions, dead-ends, and constraints via MCP tools.2AGPL 3.0