An MCP server for building, validating, and traversing graph-structured agent skill systems — networks of composable Markdown files connected through wikilinks.
Skills are organized as a navigable graph where each node is a standalone Markdown file and edges are semantic wikilinks that tell an agent when and why to follow a connection.
Traditional agent skills are flat: one file, one capability, loaded in full. This works for simple tasks but breaks down when:
- A domain is too large for a single context window — the LLM has to load everything even when it only needs one procedure
- Concepts repeat across capabilities (error handling, auth patterns, rollback steps) and end up duplicated or inconsistent
- The agent needs to make routing decisions before committing to a full procedure
- Teams want modular, version-controlled knowledge they can diff, review, and update independently
A skill graph solves this by treating skills as a navigable network. Each node is a small Markdown file. Edges are wikilinks embedded in prose that tell the agent when and why to follow a connection — not just that one exists.
The mental model: a senior engineer onboarding you to a codebase. They don't hand you one giant doc. They give you a map, point you to the right modules, explain how pieces connect, and let you go deeper only where needed.
| Situation | Recommendation |
|---|---|
| Small, self-contained skill (< 200 lines) | Keep it flat. No graph needed. |
| Skill with 3+ distinct procedures | Decompose into leaf nodes under a MoC |
| Concepts reused across 2+ skills | Extract to a shared-concept node |
| Domain too large for one context window | Full graph with MoC routing layer |
| Existing monolithic skill file | Use decompose_skill to automate decomposition |
An agent navigates a skill graph by reading nodes progressively:
Agent receives task → reads SKILL.md (root MoC)
→ finds the right sub-domain → follows wikilink to sub-MoC
→ identifies the right procedure → follows wikilink to leaf-skill
→ executes the procedure → follows wikilinks to shared concepts as needed
At each step the agent only loads what it needs. A 500-line monolithic skill becomes a 5-node graph where the average interaction loads 2-3 nodes totaling ~100 lines.
A wikilink must always appear in prose that explains when to follow it:
<!-- BAD: bare wikilink, no context -->
- [[rollback]]
<!-- GOOD: wikilink in prose with decision context -->
If health checks fail after deployment, immediately follow [[rollback]] to revert
to the last known good image tag.This is enforced by Validation Rule 4 — validate_graph will flag bare wikilinks.
graph TB
subgraph MCP["MCP Server (FastMCP)"]
direction TB
Tools["17 Tools"]
Resources["3 Resources\ngraph://name/node/{node}\ngraph://name/topology\ngraph://name/mermaid"]
Prompts["3 Prompts\ndomain_discovery\nnode_authoring_guide\nvalidation_report"]
end
subgraph Core["Core Layer"]
direction TB
Graph["SkillGraph\n(networkx.DiGraph)"]
GraphMgr["GraphManager\n(multi-graph cache)"]
Validator["Validator\n(7 rules)"]
Parser["Parser\n(frontmatter + wikilinks)"]
Storage["GraphStorage\n(filesystem I/O)"]
end
subgraph Disk["Filesystem"]
direction TB
SKILL["graphs/{name}/SKILL.md\n(root MoC)"]
Nodes["graphs/{name}/nodes/*.md"]
Refs["graphs/{name}/references/*"]
end
subgraph LLM["LLM Sampling (ctx.sample)"]
Stage1["Stage 1: Node Inventory"]
Stage2["Stage 2: Batch Node Authoring"]
end
Tools --> GraphMgr
Tools --> Storage
Tools --> Validator
Resources --> Storage
GraphMgr --> Graph
Graph --> Parser
Parser --> Storage
Storage --> Disk
Tools -->|decompose_skill| LLM
LLM --> Storage
| Component | File | Role |
|---|---|---|
FastMCP server |
server.py |
Tool/resource/prompt registration, lifespan context |
SkillGraph |
graph.py |
In-memory networkx graph; BFS, cycle detection, Mermaid output |
GraphManager |
graph.py |
Multi-graph cache with async locks |
Validator |
validator.py |
7 graph integrity rules (orphans, depth, wikilink prose, etc.) |
Parser |
parser.py |
Frontmatter parsing, wikilink extraction, code-block filtering |
GraphStorage |
storage.py |
Filesystem CRUD; resolves node paths |
models.py |
models.py |
Pydantic v2 models for all inputs/outputs |
graph LR
MoC["map-of-content\nRouting + wikilinks\nMax 80 lines"]
Leaf["leaf-skill\nFull procedure\nSelf-contained"]
Shared["shared-concept\nReusable knowledge\nReferenced by ≥2 nodes"]
Ref["reference\nStatic data\nLoaded on demand"]
MoC -->|links to| MoC
MoC -->|links to| Leaf
MoC -->|links to| Shared
Leaf -->|references| Shared
Leaf -->|references| Ref
sequenceDiagram
participant Client
participant Tool as decompose_skill
participant LLM as ctx.sample()
participant FS as GraphStorage
Client->>Tool: content/file_path + graph_name
Tool->>Tool: Stage 0: validate inputs
Tool->>LLM: Stage 1: "produce node inventory"
LLM-->>Tool: JSON array [{name, type, links, ...}]
loop For each batch of N nodes
Tool->>LLM: Stage 2: "author node body for batch"
LLM-->>Tool: JSON array [{name, body}]
end
Tool->>Tool: Assemble NodeSpec objects
alt dry_run=False
Tool->>FS: scaffold + write nodes
Tool->>Tool: validate_graph
end
Tool-->>Client: DecomposeResult {nodes, validation_report, batch_errors}
pip install -e ".[dev]"Or with uv:
uv sync --extra devAdd to your Claude Code settings.json:
{
"mcpServers": {
"skill-graph-builder": {
"command": "fastmcp",
"args": ["run", "src/skill_graph/server.py"],
"env": {
"SKILL_GRAPHS_BASE_PATH": "./graphs"
}
}
}
}Or using python -m:
{
"mcpServers": {
"skill-graph-builder": {
"command": "python",
"args": ["-m", "skill_graph.server"],
"env": {
"SKILL_GRAPHS_BASE_PATH": "/path/to/your/graphs"
}
}
}
}1. scaffold_project(graph_name="my-domain")
2. discover_domain(graph_name="my-domain", domain="...", capabilities=[...], audience="...")
3. create_node(...) for each node in the inventory
4. validate_graph(graph_name="my-domain")
5. read_node / follow_link / search_graph to traverse
decompose_skill(
graph_name="my-domain",
file_path="/path/to/monolithic-skill.md",
dry_run=True, # preview first
)
Requires an MCP client that supports LLM sampling (ctx.sample()).
| Tool | Description |
|---|---|
list_graphs |
List all managed skill graph projects |
graph_stats |
Node counts, depth, link density |
read_node |
Read a node (frontmatter + body) |
follow_link |
Follow a validated wikilink to another node |
search_graph |
Search nodes by query (descriptions, body, tags) |
| Tool | Description |
|---|---|
create_node |
Create a new node with frontmatter validation |
update_node |
Partially update an existing node |
delete_node |
Delete a node (refuses if inbound links exist) |
| Tool | Description |
|---|---|
validate_frontmatter |
Validate a single node's frontmatter |
validate_graph |
Run all 7 graph integrity rules |
| Tool | Description |
|---|---|
scaffold_project |
Create directory structure for a new graph |
export_graph |
Export as JSON, Mermaid, or node inventory table |
| Tool | Description |
|---|---|
discover_domain |
Generate draft node inventory from domain + capabilities |
design_topology |
Validate and diagram a node inventory |
visualize_graph |
Generate Mermaid or ASCII diagram of an existing graph |
add_traversal_narrative |
Trace agent path through the graph for a scenario |
| Tool | Description |
|---|---|
decompose_skill |
Decompose a monolithic Markdown file into a full skill graph using LLM sampling |
decompose_skill parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
graph_name |
str |
required | Target graph name (kebab-case) |
content |
str | None |
None |
Raw Markdown content |
file_path |
str | None |
None |
Path to a .md file (alternative to content) |
dry_run |
bool |
False |
Preview decomposition without writing files |
batch_size |
int |
4 |
Nodes authored per LLM call |
The server enforces 7 graph integrity rules on every validate_graph call:
| # | Rule |
|---|---|
| 1 | No orphan nodes — every node reachable from root MoC |
| 2 | No dead-end MoCs — every map-of-content links to ≥1 node |
| 3 | Shared concepts are shared — shared-concept referenced by ≥2 nodes |
| 4 | Wikilinks carry context — every [[link]] has surrounding prose |
| 5 | Leaf skills are self-contained — ≥1 section with ≥3 lines of non-link prose |
| 6 | Depth ≤ 4 — no node is more than 4 hops from the root |
| 7 | No unescaped circular dependencies — cycles require conditional prose |
| Variable | Default | Description |
|---|---|---|
SKILL_GRAPHS_BASE_PATH |
./graphs |
Base directory for skill graph projects |
uv run python -m pytest tests/unit/ -v # Unit tests
uv run python -m pytest tests/integration/ -v # Integration tests
uv run python -m pytest --cov=skill_graph # With coverage