Tool / API Reference
Engraphy exposes its capabilities as MCP tools over POST /mcp (Streamable
HTTP, bearer-authenticated). The tool list is assembled per space — the twelve
core tools below, the four admin_* tools (unless a space sets
space_admin_tools: false), plus any aliases the space's pack declares.
- Argument types are enforced server-side against a fixed spec (unknown argument
names are refused; explicit
nullis refused — omit a key instead; out-of-range integers are clamped, not rejected). The publishedinputSchemais generated from that same spec, so what a client sees and what the server accepts cannot drift. - Every envelope carries
"v": 1(a version field). Errors come back asENGRAM_<CODE>strings (e.g.ENGRAM_SCOPE_UNKNOWN,ENGRAM_VALIDATION,ENGRAM_ROLE).
Examples below show the arguments object you send and the envelope you get back.
Read tools
search — hybrid semantic + lexical retrieval
Vector leg + lexical leg fused with RRF, read-time near-duplicate collapse, cut to
limit.
| Param | Type | Required | Notes |
|---|---|---|---|
scope |
string | ✓ | a scope id, or "all" for every readable scope. |
query |
string | ✓ | natural-language query. |
types |
string[] | restrict to these node types. | |
limit |
integer | clamped to 1–25 (default 25). | |
include_inactive |
boolean | include non-active nodes. |
|
detail |
"full"|"summary" |
summary omits bodies. |
// arguments
{"scope": "personal-devon", "query": "descale the espresso machine", "limit": 5}
// envelope
{
"v": 1,
"detail": "full",
"results": [
{"node": {"id": "…uuid…", "type": "note", "title": "Coffee machine",
"body": "Descale the office coffee machine monthly.", "attrs": {}},
"score": 0.032, "similarity": 0.71, "edge_count": 0}
],
"scopes_searched": ["personal-devon"],
"truncated": false
}
score is the fused RRF score; similarity (present when the node hit the vector
leg) is cosine 0–1; edge_count hints how much is attached (walk it with
traverse).
traverse — graph walk from a node
| Param | Type | Required | Notes |
|---|---|---|---|
start_id |
uuid | ✓ | the node to walk from. |
direction |
"out"|"in"|"both" |
✓ | edge direction. |
edge_types |
string[] | restrict to these edge types. | |
max_depth |
integer | clamped downstream (default 4). | |
limit |
integer | clamped downstream (default 50). | |
detail |
"summary"|"full" |
default summary (no bodies). |
// arguments — every fact attached to a person, one hop
{"start_id": "…person-uuid…", "direction": "both", "max_depth": 1}
// envelope
{
"v": 1, "detail": "summary",
"nodes": [{"id": "…", "type": "fact", "title": "…", "depth": 1}],
"edges": [{"src": "…", "dst": "…", "type": "involves"}],
"truncated": false
}
Common patterns: depth-1 both from an entity lists everything about it;
edge_types: ["same_topic"] from a hit lists its merge-linked cluster siblings.
get — full nodes by id
| Param | Type | Required | Notes |
|---|---|---|---|
ids |
uuid[] | ✓ | up to 25 ids. |
Unreadable or unknown ids come back in missing (existence is information; never an
error). Each node carries capped edge summaries (≤10 out, ≤10 in).
// arguments
{"ids": ["…uuid-a…", "…uuid-b…"]}
// envelope
{
"v": 1,
"nodes": [
{"id": "…uuid-a…", "type": "note", "title": "…", "body": "…", "attrs": {},
"status": "active", "created_at": "2026-07-31T…Z",
"edges": {"out": [{"dst": "…", "type": "references"}], "in": []}}
],
"missing": ["…uuid-b…"]
}
briefing — pack-declared session-start sections
| Param | Type | Required | Notes |
|---|---|---|---|
scope |
string | ✓ | scope id, or "all". |
hint |
string | seeds the pack's semantic: true sections. |
Returns {"v": 1, "sections": [...], "footer": {"inbox_pending": N}}, section
order = pack order. Without a hint, semantic sections are empty by design.
Write tools
write — dedup-banded write
The core write. Embeds the content, bands it against existing memory, and returns
the outcome plus a resonance report (related nodes above resonance.floor,
excluding the node just written).
| Param | Type | Required | Notes |
|---|---|---|---|
scope |
string | ✓ | must be writable by the caller. |
type |
string | ✓ | a pack-declared node type. |
title |
string | ✓ | 3–200 chars. |
body |
string | ✓ | 1–8000 chars. |
attrs |
object | validated against the type's schema. | |
links |
link-item[] | edges to attach to the new node (see Link items). | |
session_id |
string | provenance grouping. |
The outcome is one of:
| outcome | meaning |
|---|---|
inserted |
new node created; envelope has node. |
merged |
absorbed into an existing canonical (a near-verbatim restatement with high token overlap); envelope has canonical (the node it merged into — id, type, scope, title, body, attrs, status, author, created_at), similarity, addendum_added, links_attached/links_skipped, and an instruction to call supersede if you were correcting rather than restating. |
merged_linked |
same topic but distinct wording/content — inserted as its own node and same_topic-linked; envelope has node (the new member) and similarity. |
needs_confirmation |
borderline (pending band) — nothing written yet; envelope has pending_id, similarity, and an instruction to call resolve_duplicate. |
Which of
merged/merged_linked/needs_confirmationyou get depends on the embedding similarity and novelty of the text: an identical restatement absorbs (merged); a reworded note on the same topic is kept and linked (merged_linked); a related-but-less-similar write in the[t_low, t_high)band parks (needs_confirmation). The one thing you never get for a near-duplicate is a blind secondinsertednode.
// arguments
{"scope": "personal-devon", "type": "note",
"title": "Coffee machine", "body": "Descale the office coffee machine monthly.",
"attrs": {}}
// envelope (first write)
{"v": 1, "outcome": "inserted",
"node": {"id": "…", "type": "note", "title": "Coffee machine", "status": "active"},
"resonance": []}
// envelope (an identical re-write later — note the `canonical` key, not `node`)
{"v": 1, "outcome": "merged",
"canonical": {"id": "…same id…", "type": "note", "title": "Coffee machine",
"status": "active", "…": "…"},
"similarity": 1.0, "addendum_added": false,
"instruction": "…if you were correcting the fact, call supersede…",
"resonance": [...]}
If a write comes back
mergedbut you were updating/contradicting the stored fact, callsupersede. Auto-merge cannot tell a correction from a restatement.
supersede — replace a node, preserving history
Atomically inserts a new node, flips the old node's status to superseded, and
attaches a supersedes edge. Takes old_id (uuid, required) plus all of write's
fields (scope, type, title, body, attrs, links, session_id). Must be
the same node type as the replacement.
{"old_id": "…old-uuid…", "scope": "personal-devon", "type": "preference",
"title": "Prefers tea", "body": "Devon now prefers tea over coffee.",
"attrs": {"strength": "soft"}}
update — edit in place
| Param | Type | Required |
|---|---|---|
id |
uuid | ✓ |
title |
string | |
body |
string | |
attrs |
object |
Re-embeds only if the searchable text actually changed. Use update for
copy-edits; use supersede when the fact changed.
link — attach edges between existing nodes
| Param | Type | Required |
|---|---|---|
edges |
link-item[] | ✓ |
Link items are {type, src_id, dst_id}. For link.edges, both endpoints
are required (they connect two existing nodes). For write.links / supersede.links,
supply exactly one endpoint — the other is the node being written. Edges are
rule-checked against the pack's edge_rules.
{"edges": [{"type": "references", "src_id": "…note…", "dst_id": "…project…"}]}
resolve_duplicate — settle a pending verdict
| Param | Type | Required | Notes |
|---|---|---|---|
pending_id |
uuid | ✓ | from a needs_confirmation write. |
resolution |
"distinct"|"merge" |
✓ | |
merge_into |
uuid | required when resolution == "merge" |
the canonical to fold into. |
distinct promotes the parked write to its own node (attaching a "declared
distinct" relates_to); merge folds it into merge_into. Returns the write
envelope of the final outcome.
Scope tools
scope_list
No arguments. Returns the scopes this token can read — your starting point for knowing where you may write.
scope_create
| Param | Type | Required | Notes |
|---|---|---|---|
id |
string | ✓ | new scope id. |
display_name |
string | ✓ | |
confirm |
boolean | ✓-ish | must be true to actually create (a guard against typo-scoped writes). |
Creates a new private scope owned by the caller.
Inbox tool
inbox_review — triage captured items
| Param | Type | Required | Notes |
|---|---|---|---|
action |
"list"|"promote"|"discard" |
✓ | |
limit, offset |
integer | for list. |
|
id |
uuid | required for promote/discard |
the inbox item. |
type, scope, title, body |
string | required for promote |
the reviewer authors the promoted node. |
attrs, links, session_id |
as for write. |
list shows pending captures (fed by POST /inbox); promote writes one through
the standard dedup pipeline (so a promoted item can itself merge); discard drops
one.
Admin tools (space_admin only)
Present unless the space sets space_admin_tools: false (then they are absent
entirely — the operator does everything via the engram-admin CLI). Each requires
the caller's principal to have the space_admin role.
| Tool | Params | Effect |
|---|---|---|
admin_member_add |
id✓, display_name✓, role (member|space_admin) |
add a principal to this space. |
admin_token_create |
principal✓, client_name✓, role✓ (readwrite|readonly) |
mint a display-once bearer token. |
admin_scope_visibility |
scope_id✓, visibility✓ (private|team-read|team-write) |
change a scope's visibility. |
admin_grant |
scope_id✓, principal✓, level✓ (read|write) |
grant a principal access to a scope. |
Space bootstrapping (
space create) and the initial token are CLI-only — there is deliberately no network path for them. See the Deployment guide.
The /inbox HTTP endpoint
Separate from the MCP tools, POST /inbox (same bearer auth) captures a raw item
for later triage:
// POST /inbox Authorization: Bearer <token>
{"kind": "note", "payload": {"text": "look into the new pgvector HNSW tuning"},
"scope": "personal-devon"}
It returns the created inbox item; review it later with inbox_review.
Next: the end-to-end tutorial puts these together on a real pack.