Support external vault roots across tooling

This commit is contained in:
2026-09-20 17:05:21 +02:00
parent 1cb6369282
commit 293b582e97
16 changed files with 573 additions and 204 deletions
+48 -5
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
"""MCP server for the knowledge-base repository.
"""MCP server for a selected knowledge-base vault, using framework-owned tooling.
This server is the single interface an agent needs to work the vault over stdio
with JSON-RPC 2.0. It exposes graph and note *querying* (graph_summary,
with JSON-RPC 2.0. It provides framework operating instructions (read_instructions),
and exposes graph and note *querying* (graph_summary,
graph_search_nodes, graph_neighbors, read_note, refresh_graph), *writing*
(write_note for text, capture_raw for verbatim bytes from a local file or an
http(s) download) and *publishing* (edit_note, git_commit, git_push), so an
@@ -18,6 +19,7 @@ It auto-detects the transport layer:
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
@@ -30,6 +32,7 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List
from vault_root import FRAMEWORK_ROOT, parse_vault_args
SERVER_NAME = "knowledge-base-mcp"
SERVER_VERSION = "0.1.0"
@@ -227,6 +230,12 @@ def _relative_to_root(state: AppState, target: Path) -> str:
def _run_git(state: AppState, args: List[str]) -> subprocess.CompletedProcess:
repo = subprocess.run(
["git", "rev-parse", "--show-toplevel"], cwd=state.root,
check=False, capture_output=True, text=True,
)
if repo.returncode != 0 or Path(repo.stdout.strip()).resolve() != state.root.resolve():
raise ValueError("vault root must be the Git repository root for Git operations")
return subprocess.run(
["git", *args],
cwd=state.root,
@@ -249,6 +258,19 @@ def _git_result(payload: Dict[str, Any], exit_code: int) -> Dict[str, Any]:
def _tool_definitions() -> List[Dict[str, Any]]:
return [
{
"name": "read_instructions",
"description": (
"Read framework AGENTS.md and AGENT_QUERY_GUIDE.md before working "
"with the vault. These operating documents live outside an "
"external vault and do not require a generated graph."
),
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": False,
},
},
{
"name": "graph_summary",
"description": (
@@ -474,6 +496,15 @@ def _text_result(text: str, structured: Dict[str, Any] | None = None) -> Dict[st
return result
def _tool_read_instructions() -> Dict[str, Any]:
documents = [
{"path": name, "content": (FRAMEWORK_ROOT / name).read_text(encoding="utf-8")}
for name in ("AGENTS.md", "AGENT_QUERY_GUIDE.md")
]
payload = {"framework_root": str(FRAMEWORK_ROOT), "documents": documents}
return _text_result(json.dumps(payload, indent=2), structured=payload)
def _tool_graph_summary(state: AppState, args: Dict[str, Any]) -> Dict[str, Any]:
graph = _load_graph(state)
nodes = graph.get("nodes", [])
@@ -624,7 +655,10 @@ def _tool_read_note(state: AppState, args: Dict[str, Any]) -> Dict[str, Any]:
def _tool_refresh_graph(state: AppState, args: Dict[str, Any]) -> Dict[str, Any]:
command = [sys.executable, "scripts/generate-graph.py"]
command = [
sys.executable, str(FRAMEWORK_ROOT / "scripts/generate-graph.py"),
"--vault-root", str(state.root), "--write",
]
if bool(args.get("validate", False)):
command.append("--validate")
@@ -884,6 +918,8 @@ def _tool_git_push(state: AppState, args: Dict[str, Any]) -> Dict[str, Any]:
def _call_tool(state: AppState, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
if name == "read_instructions":
return _tool_read_instructions()
if name == "graph_summary":
return _tool_graph_summary(state, arguments)
if name == "graph_search_nodes":
@@ -908,7 +944,7 @@ def _call_tool(state: AppState, name: str, arguments: Dict[str, Any]) -> Dict[st
def _list_resources(state: AppState) -> Dict[str, Any]:
return {
result = {
"resources": [
{
"uri": "repo://graph",
@@ -924,6 +960,12 @@ def _list_resources(state: AppState) -> Dict[str, Any]:
},
]
}
if not (state.root / "README.md").is_file():
result["resources"] = [
resource for resource in result["resources"]
if resource["uri"] != "repo://readme"
]
return result
def _read_resource(state: AppState, uri: str) -> Dict[str, Any]:
@@ -994,7 +1036,8 @@ def _handle_request(state: AppState, message: Dict[str, Any]) -> Dict[str, Any]
def main() -> None:
root = Path(__file__).resolve().parents[1]
args = parse_vault_args(argparse.ArgumentParser(description=__doc__))
root = args.vault_root
state = AppState(root=root, graph_path=root / "index" / "graph.json")
reader = _StdinReader(sys.stdin.buffer)