geml mcp — document editing over MCPLet Claude change one block of a document instead of rewriting the whole file; a bad edit is caught before it reaches disk, and a wrong one can be undone a single block at a time.
When --root also holds a code graph, this same server adds the three
read-only call-graph tools — one client entry, one process, instead of two.
This replaced the separate geml codemap mcp server, which has been removed;
if you registered it, switch to geml mcp --root <dir>.
npm install -g @geml/geml
claude mcp add geml -- geml mcp --root /abs/path/to/your/docs
Or in claude_desktop_config.json:
{
"mcpServers": {
"geml": {
"command": "geml",
"args": ["mcp", "--root", "/abs/path/to/your/docs"]
}
}
}
--root is required and is the only directory the server will read or
write. A client cannot override or widen it: every path a tool receives is
resolved through it, with symlinks followed before the check, so neither
../../etc/passwd nor a symlink planted inside the workspace escapes.
Point --root at the repository and the server picks up
<root>/.geml-code-graph automatically, adding resolve_name, open_symbol
and get_backlinks to the same tool list:
geml codemap build --root /abs/path/to/repo # once, to create the graph
claude mcp add geml -- geml mcp --root /abs/path/to/repo
Use --graph <dir> for a graph kept somewhere else inside the root. With no
graph the three tools are not listed at all, so a client never sees a tool
it cannot use.
The graph tools are read-only, but they now share a process that writes — so a
client-supplied graph_dir is confined to --root like every other path,
rather than being free to name any directory as it was on the old standalone
server. $GEML_GRAPH_DIR is ignored here for the same reason: the operator who
chose --root decides what the server can reach, not the environment it
inherits.
| Tool | What it does |
|---|---|
geml_list_ids |
Every addressable block: #id, kind, heading text |
geml_read_block |
One block by id — not the whole file |
geml_check |
Diagnostics with stable codes (Appendix A) |
geml_history_log |
Recorded revisions, newest first |
geml_write_block |
Replace one block (whole / head / body) |
geml_add_block |
Insert blocks or prose (append / before / after) |
geml_delete_block |
Remove blocks by id |
geml_rename_id |
Rename an id and every reference to it |
geml_revert_block |
Undo one block — its last change, or a named revision |
With a code graph under --root, five more (read-only):
| Tool | What it does |
|---|---|
search_symbols |
Find symbols by partial name — start here when you don’t know the exact one |
resolve_name |
Exact name → the document and block id to open |
open_symbol |
Open one symbol’s block: its callees, confidence notes, called-by pointer |
get_backlinks |
Who calls this symbol, with file:line sites |
trace_calls |
Several hops of the chain as a tree — callees downstream, callers for the impact path |
search_symbols and trace_calls are the two that keep an agent off the
one-call-per-hop treadmill: the first because resolve_name needs a name you
may not have yet, the second because a three-level chain would otherwise be
three round trips carrying three full symbol blocks. Every line trace_calls
prints is a complete doc.geml#id, so it can be fed straight back to
open_symbol without working out which document it belongs to.
Building and refreshing a graph stay on the CLI (geml codemap build /
refresh): both run indexers or recorded shell steps, and refresh is behind a
trust gate for that reason — not something a model should be able to trigger.
geml codemap serve renders HTML for a person, which a model cannot consume.
A write is validated before it reaches disk. Every mutation is first produced without touching the file; the result is parsed; the file is overwritten only if the result is clean. A broken edit is refused with the diagnostics that refused it:
{ "ok": false,
"diagnostics": [
{ "severity": "error", "code": "unresolved-reference",
"message": "unresolved reference `#ghost`", "line": 12 }
],
"hint": "… The write was refused; the file on disk is unchanged." }
The hint is there for the model: without being told the file is unchanged, a
model reads “error” and carries on as though its edit landed.
Every write is preceded by a history commit, so geml_revert_block always
has a revision to undo to. Pass --no-history to turn that off; the default is
on, because without it the strongest tool in the set has nothing to revert to.
geml_revert_block undoes one block. After a bad edit you recover that
block while every other byte of the document — including good edits made since —
stays exactly as it was. General file-editing tools can restore a whole file
from a snapshot; none of them can put back a single block.
Called without rev, it undoes that block’s last change, however many other
blocks were edited in between. This matters more than it sounds: history records
a snapshot of the whole document per write, so a -N offset is a document
cursor, and which N holds a given block’s previous content depends on how many
unrelated writes followed — a number the caller has no way to know. Pass rev
only when you want one specific revision:
geml_revert_block(file, id) # undo my last edit to this block
geml_revert_block(file, id, rev="-2") # go to a specific document snapshot
geml_revert_block(file, id, rev="c9d5f1cc") # go to a specific revision id
Repeating the call does not walk further back — it alternates between the last
two versions of the block. Revert once, look at the result, and use an explicit
rev from geml_history_log if you need to go deeper.
Deleting a referenced block is allowed. References left dangling are
reported in diagnostics, but they do not block the deletion — removing
something on purpose is a legitimate act, and the caller decides whether to
repair or restore.
A document that already has errors is locked until they are repaired. The pre-write check refuses any result containing an error, including one the document already had, so editing an unrelated block in a broken document is refused too. The refusal says so explicitly — that the errors predate the edit and which ones they are — so the fix is to repair those first. Repairing them is never blocked.
> list the blocks in spec.geml
> read #budget
> rewrite #budget's body to mention Q3
> that was wrong — revert #budget
| Symptom | Cause |
|---|---|
--root <dir> is required |
The server refuses to start rather than serve the whole filesystem. |
path escapes the server root |
The path resolved outside --root. Use a path relative to that root. |
graph_dir escapes the server root |
Same rule for the code-graph tools: the directory must sit inside --root. |
| The code-graph tools are missing | No graph was found. Run geml codemap build --root <dir>, or pass --graph <dir>. |
| A write keeps being refused with the same error | The error predates your edit. Run geml_check and repair it first. |
no .gemlhistory sidecar yet |
Nothing has been written through the server yet; the first write creates it. |