Skip to main content
Glama
sequico

server-noonien

Official

server-noonien

CI CodeQL Glama npm
version License: MPL 2.0 Node ≥
22

Shared, serverless, conflict-free memory for AI agents — with peer-to-peer sync.

server-noonien is a drop-in replacement for the official @modelcontextprotocol/server-memory knowledge graph — the same entities, observations and relations — that converges across machines with no central server, no database and no merge conflicts.

Its headline feature is peer-to-peer sync: the nooniend daemon replicates each node's shard directly to the others, so several machines share one memory with no shared folder — only IP reachability over a VPN or a LAN.

Table of contents

Related MCP server: Lorekeeper

Why server-noonien

The problem

Agent memory today is local, and the official memory server is a single JSONL file that every mutation reads and rewrites in full. That breaks the moment you have more than one machine:

  • It lives on one host. Switch machine and your agent has forgotten everything.

  • Sharing it over a synced folder loses writes. The server serializes mutations in-process only: two hosts each load the whole graph, mutate their own copy and write it back, so the last write silently discards the other's — and the sync tool forks the file into conflict copies instead of merging it.

  • The alternatives want a server, a database or a cloud. The mainstream "shared memory" products are something you have to host, or they ship your memory off your machines.

How server-noonien solves it

server-noonien removes the shared file and the central server at the root, and keeps the drop-in tool surface:

  • Every node keeps its own memory. Each machine writes only its own append-only shard (<node>.jsonl); a shard has a single writer by design, so no two nodes ever write the same file and noonien itself never forks one — a shared area's syncer still can, and a conflict copy is just another shard to fold.

  • Shards merge, they do not overwrite. The graph is a CRDT — an LWW-Element-Set ordered by (HLC, node, sequence) — whose merge is idempotent, commutative, associative and convergent. Any two machines that have seen the same operations hold the identical graph, in any order, so there is no "last write wins" data loss.

  • No shared area, no central server. The shards travel directly between machines with the nooniend daemon — only IP reachability, over a VPN or a LAN — so several machines share one memory with no shared folder and no service to host. Prefer a folder or a bucket you already have? file and s3 work too: the transport is a choice, not a lock-in.

  • Local-first and offline-friendly. Every node reads and writes its own shard on disk and keeps a full local replica, so it keeps working offline; the mesh reconverges when the network returns.

  • Drop-in for the official server. The same nine tools, inputs and outputs, so it slots under the memory server name with no agent changes — plus the update_*, merge_entities and read_conflicts extensions, the operations the official nine have no equivalent for.

Comparison

server-noonien vs the official memory server vs hosted memory services:

server-noonien

Official server-memory

Hosted memory services

Multi-machine

Yes — per-node shards, one graph

No — one local file

Yes, via the service

Sharing a synced folder

Safe — append-only, conflict-free

Unsafe — whole-file read-modify-write

N/A

Needs a server / database

No

No

Yes

Needs a cloud account

No

No

Yes

Works offline

Yes (file backend)

Yes

No

Storage

Folder or S3-compatible bucket, your choice

One JSONL file

Their cloud

Drop-in for the official tools

Yes — same nine, plus extension tools

—

No

Migration

noonien import memory.jsonl

—

Export/import dance

Model

Explicit knowledge graph

Explicit knowledge graph

Often vector/semantic

License

MPL-2.0

MIT

Proprietary

Hosted is a category, not a product: it covers services that host your memory for you. server-noonien is the local-first opposite — you own the storage and there is no account.

A shared area is required for the file/s3 backends — a folder or an object store every node can reach. nooniend removes that requirement: it replicates the shards directly between nodes.

The three commands

One npm package, server-noonien, ships three commands (Node.js ≥ 22). Install it once and all three land on your PATH:

npm install -g server-noonien

Command

Role

How it runs

server-noonien

the MCP memory server

short-lived, spawned by the MCP client over stdio

noonien

the maintenance CLI

one-shot, run by hand

nooniend

the peer-to-peer replication daemon

long-lived, one per machine

Only server-noonien matches the package name, so it is the only command npx can run by package name. Run the other two with --package, or from the global install above:

npx -y server-noonien                       # the MCP server (no argument serves over stdio)
npx -y -p server-noonien noonien help       # the maintenance CLI
npx -y -p server-noonien nooniend           # the replication daemon

From a clone — for development, or to run code not yet on npm:

git clone https://github.com/sequico/server-noonien.git
cd server-noonien && npm install && npm run build

node dist/index.js      # server-noonien — the MCP server
node dist/noonien.js   # noonien    — the maintenance CLI
node dist/gossip.js     # nooniend   — the replication daemon

server-noonien — the MCP server

The entry every MCP client uses. With no argument (or serve) it speaks MCP over stdio, so you don't run it yourself — the client spawns it and it lives for the session:

npx -y server-noonien                          # the stdio command the client runs
claude mcp add memory -- npx -y server-noonien # register it with Claude Code as the `memory` server

Point your client at it; it works on one machine out of the box, and sharing across machines is the next step. In OpenCode (V2):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "memory": {
        "type": "local",
        "command": ["npx", "-y", "server-noonien"],
        "environment": { "NOONIEN_DIR": "~/.noonien" }
      }
    }
  }
}

Other clients wrap the same command and environment in their own envelope; from a clone the command is ["node", "/path/to/server-noonien/dist/index.js"]. Because the nine drop-in tools are identical, you can replace the memory server entry with server-noonien and change nothing else.

noonien — the maintenance CLI

Command

Does

noonien serve

run the MCP memory server over stdio (the same as server-noonien)

noonien import <file>

import an official server-memory JSONL file (writes its own <node>-import shard, so it runs online)

noonien export

print the folded knowledge graph as JSON

noonien merge

fold every shard and report the merged state

noonien compact

maintain this node's shard now: prune shadowed operations and, only where it is safe, physically delete its tombstones — the same rule the server applies (NOONIEN_GC; with a daemon that owns the directory it keeps the whole view and leaves the shard to the daemon) — and report why when it keeps them

noonien query <text>

search entities and print the matching subgraph

noonien help

list the commands; noonien --version prints the version

nooniend — the daemon

A long-lived service, one per machine, that replicates the shard directory peer to peer. It takes no arguments and is configured entirely through environment variables — the shared NOONIEN_DIR and NOONIEN_NODE_ID (Shared variables) plus the daemon's NOONIEND_* (Daemon variables). See Peer to peer for the setup.

Share across machines

noonien merges shards; it does not move them by itself. How the shards travel between machines is your choice: peer to peer (a companion daemon — the recommended default) or a shared area (a folder or a bucket that already exists). The MCP server always reads and writes its own local shard; only the transport differs.

Peer to peer — nooniend (recommended)

The recommended way to share one memory across machines with no shared storage. nooniend is a long-lived daemon that replicates the local shard directory peer to peer. It only needs IP reachability, so it runs over any VPN (WireGuard, Tailscale, OpenVPN, Netbird, ZeroTier, …) or a plain LAN.

Give each machine a unique node id and run one nooniend per machine. Every daemon keeps a full replica of the others' shards beside its own, in the same local directory the MCP server uses — no shared folder required:

# on a tailnet
NOONIEN_DIR=~/.noonien \
NOONIEND_TAILSCALE=true \
nooniend

# or, with no self-listing underlay, seed one reachable peer
NOONIEN_DIR=~/.noonien \
NOONIEND_PEERS=ai.example:27878 \
nooniend

The peers find each other — you never list the machines. Configure one discovery source: NOONIEND_TAILSCALE (from tailscale status), NOONIEND_DNS_SRV (the _nooniend._tcp SRV records), or NOONIEND_PEERS (a static seed; one is enough, membership then self-propagates). A discovery source returns candidates, not members: a candidate joins only once it answers /info, so a machine on the network that does not run nooniend is never adopted. NOONIEND_ADVERTISE is optional and defaults to the listen host.

How it behaves:

  • Never authors operations. Each node's MCP server writes only its own <node>.jsonl; the daemon keeps replicas of the peers' shards and only ever pushes the shard it authors, so the single-writer rule holds. It also restores the local shard from a peer's replica when the local copy is behind.

  • Push on change + periodic anti-entropy. On a local change (NOONIEND_PUSH) and every NOONIEND_INTERVAL seconds it compares the per-shard high-water seq and moves the delta in whichever direction is behind. Operations are idempotent, so delivery may be best-effort — no consensus, no ordering, only eventual completeness.

  • Scales past a full mesh. By default it contacts every peer; NOONIEND_FANOUT caps that to a random subset per round — which excludes tombstone collection, because a reachable peer the round did not sample suspends it, so the daemon refuses that combination — and at or above NOONIEND_DIGEST_MIN_SHARDS it reconciles through a fixed-size digest instead of the full shard list (with NOONIEND_CHANNELS moving membership onto its own channel). A small mesh keeps behaving exactly like a full mesh, and a peer without the digest capability falls back to the plain list, so a mixed mesh never breaks.

  • Relays. A node serves every replica it holds, so an offline node keeps converging through any peer that has its shard.

  • Discovery and membership. Bootstrap adapters (Tailscale, DNS SRV, a static seed) return candidates; the peer set then gossips itself. A static node@host:port seed is explicit intent and enters membership directly; a bare address (a Tailscale or DNS-SRV candidate) joins only once it answers /info, and its id comes from that answer — never from the discovery source — so a device that does not run nooniend is never a member and never blocks collection. A node that comes up is adopted on its next probe, and the sources are re-read every NOONIEND_DISCOVER_INTERVAL seconds, so a joiner is found without a restart. Liveness comes from the exchange results — suspect after NOONIEND_SUSPECT_AFTER failures, dead after NOONIEND_DEAD_AFTER, retried after NOONIEND_DEAD_RETRY, and forgotten after NOONIEND_MEMBERSHIP_TTL. A peer that reaches this node is marked alive — its certificate names it under mTLS, otherwise the id it announces on the request — so a peer that recovers is re-engaged at once instead of waiting out NOONIEND_DEAD_RETRY. A peer this node does not know yet is adopted from the address it announces on the request (untrusted, like a gossiped entry, and confirmed by the next sync), so a statically-seeded joiner is discovered by the seeds it reached and its entry then spreads to the rest. A node's own entry is authoritative; an address that serves a different node is dropped.

  • Wire and security. A small HTTP/JSON service bound to NOONIEND_LISTEN (the full wire contract is in PROTOCOL.md); a shard accepts only its own node's operations. mTLS with per-peer certificates: the certificate's common name is the node id, so a peer may only push the shard it authors, while reads stay open to any authenticated peer — which is what lets a node relay. NOONIEND_REVOKED refuses specific nodes on every route; NOONIEND_DEPARTED additionally discards a departed node's replica and lets collection proceed past it.

  • Observability. Prometheus metrics — liveness, peer health, the stable watermark and, for collection, how long each peer has been away (noonien_gossip_absent_seconds) and how many deleted elements are frozen (noonien_gossip_frozen_elements) — plus a per-shard stable watermark (the minimum high-water mark reached on every contactable peer, a conservative causal-stability bound), and status and graph routes for probes and operators — see The daemon HTTP API.

Under systemd, a user unit:

[Unit]
Description=nooniend — peer-to-peer shard replication
After=network-online.target

[Service]
EnvironmentFile=%h/.config/noonien/nooniend.env
ExecStart=%h/.local/bin/nooniend
Restart=on-failure

[Install]
WantedBy=default.target

The daemon HTTP API

nooniend serves a small HTTP/JSON API on NOONIEND_LISTEN (default 0.0.0.0:27878). The full wire contract — routes and payloads, the operation format, the protocol revision and capabilities, the size limits and the mTLS model — is specified in PROTOCOL.md.

curl -s http://127.0.0.1:27878/health
curl -s http://127.0.0.1:27878/status
curl -s http://127.0.0.1:27878/peers
curl -s http://127.0.0.1:27878/graph | jq '{entities: (.entities|length), relations: (.relations|length)}'

Shared area — file and s3

The file and s3 backends converge through a shared area that already exists and is replicated outside noonien — a folder or a bucket every node can reach. Point every node at the same area and each node's shard reaches the others:

NOONIEN_BACKEND=file NOONIEN_DIR=/srv/noonien-shared NOONIEN_NODE_ID=ai server-noonien
# or
NOONIEN_BACKEND=s3 NOONIEN_S3_BUCKET=my-bucket NOONIEN_NODE_ID=ai server-noonien
  • file — shards are *.jsonl files in NOONIEN_DIR; point it at a folder replicated by Syncthing, Dropbox, a git working tree or a shared mount.

  • s3 — one object per shard, written with conditional writes retried on contention; uses @aws-sdk/client-s3 (an optional dependency, loaded only when selected). Conditional writes need a store that honours If-Match/If-None-Match on PutObject (AWS S3, Cloudflare R2, MinIO do); a store that ignores them is not supported, and a shard object with a multipart ETag is refused rather than guarded by a token that can never match. An object store has no append, so every mutation rewrites the whole shard object (read-modify-write): correct, but O(shard size) per write.

The memory backend keeps shards in process only — for tests and throwaway sessions. See Configuration for the variables.

What a shared area does not give you: a safe deletion. file/s3 share a graph; they cannot say whether a peer holds a write it has not delivered yet. So without a daemon the maintenance prunes only, and a physical deletion happens only where the operator declares the directory written by this node alone (NOONIEN_GC) — see Deletion and collection. Convergence is delegated to the syncer as well: noonien neither verifies nor retransmits, so latency and conflict copies are the syncer's business, and a conflict copy is just another shard to fold. And the daemon replicates a directory: nooniend refuses to run with NOONIEN_BACKEND=s3 (or memory), where it would replicate a local folder the server never uses.

Configuration

Every setting is read from the environment. NOONIEN_DIR and NOONIEN_NODE_ID are shared; the MCP server, the CLI and the daemon otherwise have their own variables — NOONIEN_* for the server, NOONIEND_* for the daemon.

Shared variables

Used by every command.

Variable

Default

Meaning

NOONIEN_NODE_ID

hostname

This node's shard name — the identity the MCP server writes under and the daemon replicates. Must be unique per machine and written by a single server at a time.

NOONIEN_DIR

~/.noonien

Directory holding this machine's shards: the file backend's store and the daemon's replica store. A leading ~ is expanded.

These two are local to each machine, not a shared area: the MCP server and the daemon share the same directory on the same host, and every machine keeps its own copy of every shard. Peer-to-peer sync with nooniend needs no shared folder or bucket; a shared area is only the alternative transport for file/s3.

A node id is a single-writer shard: exactly one server process should append to it at a time. The normal setup is one memory server per machine; if you run several servers against the same store concurrently, give each its own NOONIEN_NODE_ID. The tool does not enforce this: two writers that share a node id and write in the same millisecond produce operations with the same id, which a read fails loudly on — but writers on different clocks usually land on different ids, so the shard can quietly interleave and the fold picks one. A unique node id is a requirement, not something the tool can check.

Server variables

Used by the MCP server (and the noonien CLI, which reads and writes the same shards).

Variable

Default

Meaning

NOONIEN_BACKEND

file

file, memory or s3.

NOONIEN_S3_BUCKET

—

Bucket (required for the s3 backend).

NOONIEN_S3_PREFIX

""

Key prefix all shard objects live under.

NOONIEN_S3_REGION

us-east-1

Region passed to the S3 client.

NOONIEN_S3_ENDPOINT

—

Custom endpoint for MinIO / R2 / Contabo and similar.

NOONIEN_S3_FORCE_PATH_STYLE

on when an endpoint is set

Path-style requests.

NOONIEN_COMPACT_AFTER

1000

Compact the local shard online after this many appended operations (0 disables).

NOONIEN_GC

off

Delete tombstones without a daemon: only where this node is the directory's only writer — a promise nothing in the directory can prove. Where a daemon owns the directory (it announces itself with its knowledge file from startup) it has no effect: the daemon collects.

NOONIEN_GC (server, default off) and the daemon's NOONIEND_GC (default on) are different switches; with the memory backend nothing is shared, so the server collects regardless of NOONIEN_GC.

S3 credentials come from the standard AWS chain (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, profiles, instance roles, …).

The local shard is compacted online: the server rewrites its own shard after NOONIEN_COMPACT_AFTER appended operations (default 1000, 0 disables), inside the same mutation queue as the writes. The rewrite is a compare-and-swap against the content it read, and a concurrent write — an append, or the nooniend daemon recovering the shard — makes it re-read and retry, so nothing is ever lost; noonien compact runs the same compaction by hand. noonien import authors its own <node>-import shard, so it too can run while the node's server is live and re-importing is idempotent. That shard only ever adds, so it needs neither the peer knowledge nor the NOONIEN_GC promise: noonien import prunes it on the way out, and noonien compact prunes it too — dropping the operations a later one shadowed, keeping every tombstone, and rewriting only when something was dropped, so a repeated maintenance costs nothing and no peer is asked to re-pull the shard. On the file backend, append and compact take a per-shard lock, so they serialize instead of racing even across processes — and the daemon takes the same lock.

Within a node the timestamp is monotonic: if the wall clock steps back (NTP, a restored snapshot, a manual date), a new operation keeps the previous timestamp rather than an older one, so an earlier operation can never win over a later one of the same node.

Daemon variables

Used by the daemon only. It needs at least one discovery source — NOONIEND_TAILSCALE, NOONIEND_DNS_SRV or NOONIEND_PEERS — to reach the mesh; the rest is optional. Run it behind a VPN or firewall and enable mTLS — see Security.

Variable

Default

Meaning

NOONIEND_LISTEN

0.0.0.0:27878

host:port the gossip service binds.

NOONIEND_ADVERTISE

the listen host (hostname on a wildcard)

Optional. host:port this node advertises to peers; set it only when the default does not resolve from the others.

NOONIEND_PEERS

—

Discovery: static seeds — node@host:port or host:port, comma separated. One is enough; membership then self-propagates.

NOONIEND_DNS_SRV

—

Discovery: domain whose _nooniend._tcp SRV records are seeds.

NOONIEND_TAILSCALE

off

Discovery: derive candidate addresses from tailscale status (a peer joins once it answers /info).

NOONIEND_DISCOVER_INTERVAL

300

Seconds between discovery refreshes; 0 reads the sources once at startup. A refresh only adds candidates.

NOONIEND_INTERVAL

30

Anti-entropy period, seconds.

NOONIEND_PUSH

on

Reconcile as soon as the local shard changes.

NOONIEND_FANOUT

0

Peers contacted per round; 0 is every reachable peer. A cap excludes NOONIEND_GC: a reachable peer the round did not sample suspends collection, so the daemon refuses the two together.

NOONIEND_DIGEST_MIN_SHARDS

32

Shard count at or above which the digest replaces the full list (0 = whenever supported).

NOONIEND_CHANNELS

off

Gossip membership on its own channel instead of piggybacking it on every exchange.

NOONIEND_RELAY

—

Comma-separated node ids always sampled (relays / super-peers).

NOONIEND_GC

on

Collect the authored shard: prune the shadowed operations (keeping the operation a peer off the mesh could still have folded as the gate's evidence) and drop a tombstone once no peer off the mesh that folded its element could contest it — the peer's knowledge frontier is a subset of what this node holds and covers no operation of the element (physical deletion).

NOONIEND_FORGET_AFTER

15552000 (180 days)

Seconds of silence after which a peer is retired: its protection becomes durable (a merged threshold, or a blanket when it holds operations this node lacks) and its live record is dropped, so the metadata collection depends on stays bounded. 0 never retires.

NOONIEND_SUSPECT_AFTER

3

Failed exchanges before a peer is marked suspect.

NOONIEND_DEAD_AFTER

6

Failed exchanges before a peer is marked dead.

NOONIEND_DEAD_RETRY

300

Seconds a dead peer is left alone before it is probed again.

NOONIEND_MEMBERSHIP_TTL

604800

Seconds a dead peer is kept before it is forgotten.

NOONIEND_HOOK_URL

—

http(s) URL the daemon POSTs { node, watermark } to when the stable watermark advances (a peer wrote something); best effort, a hook that is down is a warning, never a failed round.

NOONIEND_REVOKED

—

Comma-separated node ids the daemon never syncs with and refuses to accept.

NOONIEND_DEPARTED

—

Comma-separated node ids explicitly gone: like NOONIEND_REVOKED but their replica is also discarded and they stop blocking collection.

NOONIEND_TLS_CERT / NOONIEND_TLS_KEY

—

Serve TLS with this certificate and key.

NOONIEND_TLS_CA

—

CA that signs the peers' certificates, used to verify them in both directions.

NOONIEND_TLS_REQUIRE_CLIENT

on

Require a client certificate (mTLS).

Listen port. The daemon listens on 27878, the IANA user port the service is being registered for; the default was 7878 before 1.0.3. A mesh is only as reachable as its ports: a discovery seed carries no port and takes the dialer's own, so a node left on a different port is never found. Upgrade the nodes together, or set NOONIEND_LISTEN and NOONIEND_ADVERTISE to match while they are mixed.

How it works

Every mutation is recorded as an operation in an append-only log, split into per-node shards:

  • create_entities / add_observations / create_relations add elements;

  • delete_entities / delete_observations / delete_relations write tombstones (a deleted entity also tombstones its observations and incident relations).

A read folds all shards: each element (entity, observation occurrence, relation) is an LWW register decided by (HLC, node, sequence). A delete of an observation is content-level: it removes every occurrence older than it. Observations are shown while their entity is present, relations while both endpoints are present. The fold depends only on the set of operations, never on the order shards or lines were read. Operations are ordered by a Hybrid Logical Clock: the order survives clock skew, and a node that has observed a claim can write a correction ordered after it. An observed stamp further than a bounded skew in the future is clamped, so a wrong clock cannot drag the node's timestamps into the future. Reads are incremental: each shard is decoded once and cached against a cheap fingerprint, and the folded graph is memoized, so a read with nothing changed costs only a fingerprint poll per shard.

Consequences, all covered by property tests:

  • Idempotent — applying the same operations twice changes nothing.

  • Commutative — order of operations across shards does not matter.

  • Associative — folding shards in any grouping yields the same graph.

  • Convergent — two nodes with the same operations produce the identical graph.

Ordering is deterministic: entities by name, relations by (from, to, relationType), observations in operation order (HLC, node, sequence).

Compaction

The log grows with every mutation. Compaction rewrites this node's shard keeping, per element, only the operations that no later operation shadows — the shadow index is built from the merged log, so an operation superseded by another node's later operation is dropped too, and the winner of every element always survives, tombstones included. It runs online: the server compacts its own shard after NOONIEN_COMPACT_AFTER appends, and noonien compact does the same by hand. When each also collects tombstones — the physical deletion — is under Deletion and collection. The rewrite is a compare-and-swap (a temporary file renamed over the shard on the file backend, a conditional write on s3), so a crash midway can never truncate it and a concurrent write — an append, or a gossip recovery — makes the whole compaction re-read and retry instead of losing it.

Every compaction stamps a shard.compact metadata operation (never a graph element): its generation is bumped each time, and its seq is the shard's durable high-water mark, so the sequence never regresses and a compaction is never mistaken for a lost shard. Peers read the generation from /shards and replace a stale replica with the compacted shard, so every copy converges to the compacted content. Only the local shard is rewritten; every other shard is untouched, so compaction is conflict-free and other nodes are unaffected.

Deletion and collection

A delete is an operation, not a removal: delete_entities, delete_observations and delete_relations append tombstones, and the fold hides the element while a tombstone is the latest operation for it. The tombstone is exactly what makes the deletion beat a concurrent write, so dropping it while that write still exists would revive the element. A deletion is therefore only physical once nobody can still hold one.

nooniend collects the shard it authors when the mesh is safe, per element. It first prunes the operations a later one shadows that no retained off-mesh peer could still need, then it may drop a surviving tombstone when no peer off the mesh could contest that element. Two conditions gate it, per peer: the peer's knowledge frontier must be a subset of what this node already holds — a peer holding any operation this node lacks could hold a relayed operation on the element and is treated as a blocker — and the frontier must cover no operation of the element, so only a peer that folded it can contest it; a peer that never folded it can still mint a coincident element (an independent name, an observation on an entity it knows), which the fold decides as a concurrent genesis, not a revival. An element that appeared while a peer was already away is thus collectable at once, without waiting for that peer to return; an element the peer did know stays frozen until the peer comes back or is explicitly departed. A surviving operation on another shard also keeps the tombstone, so the fold is always preserved. When a peer could contest an element, the operation it knew is kept as the evidence the next round reads again — and by the node that authored it, not only by the deleter: a node never prunes a shadowed operation on an element a peer off the mesh could still contest, so the proof survives even when the tombstone lives on a different shard (a delete by one node of an element another created).

The claim, in one line: collection needs no barrier over the history — a peer that never folded an element cannot causally contest it, however long it stays away, and neither a global round of acknowledgements nor a timer on the data is needed. What it does need is a stable round over the live set: every retained peer that is still reachable must have been exchanged with in that round (otherwise the round suspends, because such a peer may hold writes this node has not pulled), and no retained peer may hold anything this node lacks. That is why NOONIEND_FANOUT and NOONIEND_GC exclude each other. Liveness is not autonomous: a peer that is both unreachable and unverifiable — never reached, or holding operations this node cannot read — blocks collection until the operator departs it (NOONIEND_REVOKED / NOONIEND_DEPARTED); the retention window bounds metadata and forfeits no data, but it does not remove that decision.

Each peer's knowledge frontier — the greatest sequence it has been seen to hold of every author's shard — is durable, persisted beside the shards in .nooniend-peers.json, so a machine gone for a year keeps blocking only the elements it actually knew. A peer the round could still reach but did not exchange with suspends collection entirely, because it may hold writes this node has not pulled. Forgetting a peer (the membership TTL) does not unblock it — it may be alive off the mesh with valid unsent writes — but NOONIEND_REVOKED and NOONIEND_DEPARTED do, the operator's decision that its writes are expendable; a departed peer's replica is discarded outright. A forfeited node's authorship is dropped from the gate as well, so it stops holding the collection back through the peers that still hold its replica; a revoked node's operations already here keep their elements frozen until it is departed, which discards them. Turn the daemon's collection off with NOONIEND_GC=false.

The per-peer record is bounded. After NOONIEND_FORGET_AFTER seconds of silence (default 180 days) a peer is retired, and retirement is semantically invisible: it moves where the protection lives, never which elements are guarded. An accounted peer — one whose frontier is a subset of what this node already holds — is merged into a durable retired frontier, one elementwise maximum per author. The maximum is exactly the union of the per-element decisions the peers would have made, so it keeps gating every element those peers could have folded, including deletions that happen after the retirement, over one map bounded by the shards this node holds rather than one map per peer. (A per-element list computed at expiry would not be sound: a tombstone created later is not on it.) An unaccounted peer — one that claims operations this node cannot read — leaves a durable blanket instead: the same "it could hold anything" that gated it while it was live, stated once rather than carried as an unverifiable map. Nothing is forfeited by retiring, which is why the window can be on by default. NOONIEND_REVOKED and NOONIEND_DEPARTED remain the only forfeiture — and since a maximum cannot be un-merged, a node must be departed before its window expires for its contribution to be lifted. A frontier that claims an author this node cannot serve is never recorded: a peer cannot freeze the mesh with a claim it cannot back. A claimed node that was never reached keeps blocking conservatively, as before.

Without the daemon a physical deletion needs an explicit promise. A daemon that owns the directory (.nooniend-peers.json present) holds the peer knowledge and collects, and it is the only writer that maintains the shard: the server and noonien compact keep their whole view there — a shadowed operation is exactly the evidence the daemon's gate reads, so they must not prune it — and leave the shard to the daemon. With no daemon there is no knowledge to gate on, and the directory cannot tell whether a peer is merely absent: a machine that is offline, or configured but not yet synced, leaves nothing to see, and its unsynced write would be lost by a collection — which is the immediate deletion the official server performs. So the server's online maintenance and noonien compact both prune only, until the operator declares the directory written by this node alone (NOONIEN_GC=true), the promise that makes it sound; a declaration contradicted by another node's shard is refused and reported.

Metrics

The daemon serves every metric at /metrics (the MCP server is stdio and has no endpoint). The collection and retention state:

Metric

Meaning

noonien_gossip_frozen_elements

deleted elements kept because a peer off the mesh may still contest them

noonien_gossip_absent_nodes

retained peers not exchanged with in the last round

noonien_gossip_absent_seconds{node}

how long each peer has been away

noonien_gossip_retained_peers

peers whose knowledge still gates collection

noonien_gossip_retired_total

peers retired into the retired frontier

noonien_gossip_retired_unaccounted_total

peers retired with a blanket

noonien_gossip_retired_authors

authors whose thresholds survive in the retired frontier

noonien_gossip_blankets

durable blankets in force

The same endpoint carries the transport and liveness families — noonien_gossip_rounds_total, noonien_gossip_round_seconds, noonien_gossip_round_bytes, noonien_gossip_links, noonien_gossip_shards, noonien_gossip_stable_shards, noonien_gossip_membership_size, noonien_gossip_peers_ok_total, noonien_gossip_peers_failed_total, noonien_gossip_ops_pulled_total, noonien_gossip_ops_pushed_total, noonien_gossip_digest_exchanges_total, noonien_gossip_membership_channel_failures_total, noonien_gossip_bytes_sent_total, noonien_gossip_bytes_received_total, noonien_gossip_fanout, noonien_gossip_up, noonien_gossip_hook_total, noonien_gossip_hook_failed_total — and, when the daemon runs the local log's own maintenance, its noonien_mcp_* counters (noonien_mcp_folds_total, noonien_mcp_fold_seconds, noonien_mcp_shards_merged, noonien_mcp_shard_stat_total, noonien_mcp_ops_decoded_total, noonien_mcp_compacted_total, noonien_mcp_compaction_failed_total).

Tools

The MCP surface is the official nine, drop-in: names, inputs, outputs and messages match @modelcontextprotocol/server-memory. Extensions the official server does not have are added on top — an entity, a relation and an observation can be edited, two entities can be merged, and the elements more than one machine wrote are visible — and the nine are never changed. read_graph and search_nodes take an optional page and type filter; without them they answer exactly as before.

Tool

Does

create_entities

create multiple new entities in the knowledge graph

create_relations

create multiple new relations between entities (active voice)

add_observations

add new observations to existing entities

delete_entities

delete entities and their associated relations

delete_observations

delete specific observations from entities

delete_relations

delete multiple relations

read_graph

read the entire knowledge graph

search_nodes

search nodes by a query, matched against entity names, types and observation content

open_nodes

open specific nodes by their names

update_entities

rename existing entities and/or change their entity type, moving their observations and relations

update_observations

replace observation contents on existing entities

update_relations

change the type of existing relations and/or repoint their endpoints

merge_entities

merge an entity into another: the union of their observations, its relations repointed, the merged entity deleted

read_conflicts

list the elements more than one node has written, with their authors

Drop-in compatibility

The same nine tools (Tools), so it slots under the memory server name with no agent changes. The extension tools sit beside them: a client written for the official server never calls them and never notices they are there.

The initialize result also carries instructions naming the extension tools and when to prefer them over composing delete_* + create_* (and each extension's description says what it replaces). A model already knows the nine official tools, and a tool it does not know it will not call.

The full graph is also exposed at the memory://knowledge-graph resource. The resource is read-only; clients can subscribe to it and receive notifications/resources/updated whenever a mutation changes the graph — via resources/subscribe on the 2025-era protocol, or a subscriptions/listen stream on the 2026-07-28 revision. The server speaks both: it answers the initialize handshake for 2025-era clients and server/discover for 2026-07-28 clients.

Entities are keyed by name. Observations behave like the official server: add_observations has no effect when the content is already present, but preserves duplicate contents within a single request, create_entities preserves duplicate contents as given, and deleting an observation removes every occurrence of that content. Relations are sets: adding the same relation twice has no effect. delete_entities also removes the entity's observations and its incident relations. The tombstones cover what the deleting node could see: an observation added concurrently on another node survives as an element, so if the entity is later created again that observation reappears with it.

update_entities renames an entity and/or changes its type, and update_relations changes a relation's type or endpoints. An edit is a tombstone plus an add, like every other mutation: a rename tombstones the old name together with its observations (so re-creating the old name cannot revive them) and re-creates the entity under the new name with the same observations and incident relations, repointed. A rename onto an existing name is refused rather than merging the two entities, and the endpoints of an updated relation must exist, as they must for a creation. A rename that another node contests — or two renames in one call that touch the same relation — settles per element by last-writer-wins, exactly like the rest of the graph.

update_observations replaces a content with another (content-level, like the delete it is built on: every occurrence of the old goes, the new is added once). merge_entities combines two entities — the union of their observations, their relations repointed — which is the operation a rename onto an existing name refuses to guess at; it is the way to un-do a duplicate a concurrent rename left behind. read_conflicts lists exactly those elements more than one node has written, with the authors, so a last-writer-wins resolution is visible instead of silent. read_graph and search_nodes accept an optional limit/offset (and search_nodes an entityType filter) so a large graph is read a page at a time — a page carries the relations touching its entities — and without those arguments the answer is byte-for-byte the one they gave before.

Security

  • One writer per shard. Keep NOONIEN_NODE_ID unique and run one server per node id. A node id is sanitised to a bounded, path-safe shard-file stem, and every node id that arrives from a peer (a /shards/{node}/ops path, a gossiped summary, an announced header) is validated before it can name a file, enter the peer set or label a metric.

  • Replication. nooniend is a network service. Run it behind a VPN or firewall, and enable mTLS (on by default when certificates are configured) so only authenticated peers replicate. The client always verifies the peer certificate — a configured CA, or the system trust store when none is set; an unverified connection is never used. Without TLS the daemon trusts the underlay; a peer's advertised address is still validated as a plain host:port, and it is adopted only when it matches the host the request actually came from (so an announced header cannot point the dialer at another host). A refusal (NOONIEND_REVOKED / NOONIEND_DEPARTED) needs the client certificate that mTLS supplies — without it a peer has no identity to refuse. Setting NOONIEND_TLS_REQUIRE_CLIENT=false turns TLS back into an unauthenticated channel (any client can push any shard); the daemon logs a warning at startup in that mode.

  • Trust model. mTLS authenticates members, not individual operations. A node serves every shard it holds, so relaying is trusted: a member can carry another node's shard to a third node, and the daemon recovers its own lost shard from a peer's replica. Run nooniend only on machines you trust as peers. The fold's last-writer-wins order trusts the operation's HLC stamp, so a member whose clock is far ahead wins every conflict on its elements; a wrong clock is a correctness hazard, not a privilege boundary. Collection also trusts what a member reports: its frontier from /shards and the peer set it gossips — the certificate proves who it is, not that its reports are true — and it assumes the mesh is the only path by which a node learns: a node that leaves must not keep receiving operations elsewhere (do not point a daemon and a shared file/s3 area at the same directory, and a node bridging two meshes belongs to both or should be departed).

  • Reporting a vulnerability. Do not open a public issue — see SECURITY.md.

Development

git clone https://github.com/sequico/server-noonien.git
cd server-noonien
npm install

npm run check       # Biome: lint + format + import order
npm run typecheck   # tsc --noEmit
npm run test        # Vitest, including the CRDT property tests
npm run build       # tsc -> dist/
npm run gate        # all of the above

npm run release:check   # version artifacts agree, every bin is packed, the changelog matches
npm run mcpb            # build server-noonien-<version>.mcpb (Claude Desktop / Smithery Local)
npm run publish:smithery -- --dry-run   # the Smithery ServerCard this release would publish

The distribution is npm; the extra commands are for the channels that need a file rather than a package — the MCPB bundle for Claude Desktop and Smithery's Local track — and for the release preflight. Publishing the Smithery release needs SMITHERY_API_KEY and SMITHERY_SERVER in the environment and the bundle from npm run mcpb.

See CONTRIBUTING.md for the workflow and the ground rules.

Why the name noonien?

The name is an homage to Star Trek: The Next Generation. The project was first meant to be called "Datalore" — the title of the episode (Season 1, Episode 13, first aired 18 January 1988) that introduces Data and his twin brother Lore, the two Soong-type androids built by Dr. Noonien Soong. Data is the good one; Lore is the flawed, emotional, malicious prototype. Both are played by the same actor, Brent Spiner.

The episode title is a portmanteau of the two androids, and the two names fit a knowledge graph almost too well:

  • Data — a datum: a single fact.

  • Lore — the body of knowledge and tradition shared by a community.

So datalore would have meant the facts plus the shared lore — exactly what a shared agent memory is. But "Datalore" is also the name of an unrelated commercial product in the same software space, so rather than carry that brand conflict the project was renamed noonien, after the androids' creator, Dr. Noonien Soong. Naming it after their maker keeps the homage to both Data and Lore.

The twin motif maps onto this project's core problem — many copies of the same graph, on different machines, that must stay consistent. Data's good twin and Lore's evil twin are the two failure modes; in server-noonien the copies cannot drift apart, because the merge is conflict-free by construction. The graph has no evil twin.

A small production gem: the episode was first pitched as a romance for Data with a female android. It was Brent Spiner himself who suggested the evil-twin plot instead — so the actor who played both twins also shaped the episode's twist.

References:

This project is an independent fan homage. "Star Trek", "Data", "Lore" and "Noonien Soong" are the property of their respective rights holders; this project is not affiliated with or endorsed by them.

Documentation

  • PLAN.md — architecture and design decisions.

  • SCALING.md — the forward-looking plan for 1,000 / 10,000 nodes.

  • PROTOCOL.md — the nooniend wire protocol: transport, messages, limits and security.

  • docs/paper.md — the technical report: the model, the tombstone-collection contribution and the evaluation, with the bibliography in docs/references.bib (frozen at release 1.0.0; the report is © Sequi Company under CC BY 4.0 — see NOTICE).

  • CONTRIBUTING.md — how to contribute.

  • AGENTS.md — the project rules for contributors and agents.

  • SECURITY.md — security policy and how to report a vulnerability.

  • CODE_OF_CONDUCT.md — the community code of conduct.

License & Disclaimer

The source code is licensed under the Mozilla Public License 2.0 (MPL-2.0); the report docs/paper.md is © Sequi Company, licensed under CC BY 4.0 — see NOTICE.

⚠️ NO WARRANTY & NO SUPPORT: This software is provided "as is". The author offers absolutely no commercial support, no maintenance guarantees, and no uptime assistance. If you open an issue, it will be addressed purely on a voluntary basis.

❤️ Support the Project (Passive Monetization)

If this component is saving your company hundreds of hours of infrastructure development, please consider supporting the project:

  • Sponsor on GitHub: Help keep this project alive by adding your company logo to this README. 👉 Sponsor Now

  • Back a Feature: Want a specific bug fixed or a new protocol added? We use Polar.sh. Put a bounty on any GitHub Issue to incentivize its development. 👉 View Bounties

Available Tools

14 tools
add_observationsAdd ObservationsB

Add new observations to existing entities in the knowledge graph

ParametersJSON Schema
NameRequiredDescriptionDefault
observationsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare this is a non-read-only, non-destructive, non-idempotent write, so the safety profile is covered. The description adds the useful constraint that target entities must already exist, but says nothing about duplicate accumulation (relevant given idempotentHint=false) or error behavior for unknown entities.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single efficient sentence with the resource and scope front-loaded. Nothing is wasted, but at ten words it is close to under-specified rather than optimally concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. However, for a mutation tool with an unannotated parameter structure and a non-idempotent flag, the description should at least cover append semantics and the failure mode when the named entity does not exist.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Reported schema description coverage is 0%, and the description contains no parameter guidance at all — nothing about the observations array, its entityName/contents fields, or the fact that observations are appended rather than replaced. The nested schema properties carry some of this, but the description does not compensate for the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb + resource: 'Add new observations to existing entities in the knowledge graph.' The qualifier 'existing' implicitly separates it from create_entities, though the sibling is never named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the tool is for appending observations to entities that already exist. There is no explicit when/when-not guidance or routing to siblings like create_entities (for new entities) or delete_observations (for removal).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_entitiesCreate EntitiesC

Create multiple new entities in the knowledge graph

ParametersJSON Schema
NameRequiredDescriptionDefault
entitiesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitiesYes

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false, destructiveHint=false, idempotentHint=false, and openWorldHint=false, so the safety profile is partly covered. The description adds nothing about what happens when an entity name already exists, whether the batch is atomic, or whether duplicates are merged — the key behavioral questions for a non-idempotent write tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single compact sentence, front-loaded with the verb and resource. Nothing is wasted, but there is also very little content to be structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and annotations carry the safety hints. Still, for a non-idempotent batch-creation tool the description omits duplicate-handling, batch failure semantics, and prerequisite context, leaving meaningful gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% at the top level, but the nested item schema documents name, entityType, and observations, and the word 'multiple' signals the batch/array shape. The description adds no format, uniqueness, or required-field detail beyond that, so it neither compensates nor misleads.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a clear verb ('Create'), resource ('entities'), and a scope qualifier ('multiple new ... in the knowledge graph'). It separates itself from create_relations and add_observations by naming entities, though it never explicitly contrasts with the sibling that appends observations to existing entities.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance and no alternatives named, despite siblings add_observations, create_relations, and delete_entities being obvious candidates for confusion. The agent must infer that this tool is for brand-new entities rather than extending existing ones.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_relationsCreate RelationsB

Create multiple new relations between entities in the knowledge graph. Relations should be in active voice

ParametersJSON Schema
NameRequiredDescriptionDefault
relationsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
relationsYes

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false and destructiveHint=false, so the mutation/safety profile is covered. The description adds one genuinely useful domain convention (relations in active voice) that the annotations do not carry, but it is silent on non-idempotency — whether re-submitting the same relation creates duplicates or errors — which is the key behavioral risk here.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action, with no filler. The active-voice rule is stated tersely, though it arrives as an orphan second sentence rather than being attached to the relationType field it governs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and annotations cover the safety profile. What remains missing for a batch, non-idempotent graph mutation — duplicate handling, partial-failure semantics, and whether `from`/`to` entities must already exist — is not addressed anywhere.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Top-level schema description coverage is 0% and the description adds no meaning for the single `relations` parameter: it never explains that the argument is an array of {from, to, relationType} objects, nor whether entity names must match existing entities. Only the nested item-level field descriptions in the schema carry any semantics, and the description does not leverage or extend them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Create multiple new relations") plus the domain scope ("between entities in the knowledge graph"). It is clearly distinguishable in kind from siblings like delete_relations and create_entities, though it never names them explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied by the verb: call this when adding new relations. There is no statement of when-not to use it, no mention of the create_entities prerequisite, and no routing guidance versus siblings. The "active voice" sentence is a formatting convention rather than invocation guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_entitiesDelete EntitiesA
DestructiveIdempotent

Delete multiple entities and their associated relations from the knowledge graph

ParametersJSON Schema
NameRequiredDescriptionDefault
entityNamesYesAn array of entity names to delete

Output Schema

ParametersJSON Schema
NameRequiredDescription
messageYes
successYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=true and readOnlyHint=false, so the safety profile is covered structurally. The description adds genuinely new behavioral context: deletion is both batched and cascading, taking associated relations with it. It does not describe partial-failure behavior, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that front-loads the action and packs the two salient facts (batch scope, relation cascade) with no filler. Nothing to trim and nothing buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and annotations carry the destructive/idempotent profile. The cascade is called out, leaving only edge behavior (nonexistent names, partial results) unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With one parameter at 100% schema description coverage, the schema already fully documents entityNames. The description only echoes the batch nature ("multiple entities") and adds no format, naming, or matching semantics beyond what the schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb (Delete) and resource (entities), plus batch scope ("multiple") and the cascade effect ("their associated relations"), which meaningfully separates it from delete_relations and delete_observations. It stops short of explicitly naming a sibling to route against, so it does not reach a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The scope term "multiple" implies this is the bulk deletion path, but the description never states when to prefer it over delete_relations or how to remove individual entities. Usage is inferred rather than instructed, and no prerequisites or warnings are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_observationsDelete ObservationsC
DestructiveIdempotent

Delete specific observations from entities in the knowledge graph

ParametersJSON Schema
NameRequiredDescriptionDefault
deletionsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
messageYes
successYes

TDQS

C2.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=true, and readOnlyHint=false, so the safety profile is fully covered by structured data. The description adds nothing beyond restating the operation: it does not say the deletion is permanent, whether missing observations are silently skipped, or what happens on partial failure across multiple entities.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence with no filler or repetition. It is efficiently sized for the operation, though the brevity reflects under-specification rather than disciplined concision.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is low-complexity (one parameter) and has an output schema plus destructive/idempotent annotations, so the missing return-value and safety detail is largely covered elsewhere. The remaining real gap is routing guidance among the many sibling mutation tools, which the description leaves unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% at the parameter level, and the description does not compensate by explaining the 'deletions' array shape, batching semantics, or match requirements. Only the nested entityName/observations fields carry schema descriptions, so the description contributes almost no additional parameter meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (delete) and resource (observations belonging to entities), which distinguishes it from delete_entities and delete_relations by the level of granularity. However it does not explicitly name those siblings or contrast scope, so an agent must infer the distinction from the resource noun alone.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No when-to-use guidance, no prerequisites, and no mention of the sibling tools (add_observations to reverse, delete_entities for whole entities). The agent is left to infer timing entirely from the verb and resource.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_relationsDelete RelationsC
DestructiveIdempotent

Delete multiple relations from the knowledge graph

ParametersJSON Schema
NameRequiredDescriptionDefault
relationsYesAn array of relations to delete

Output Schema

ParametersJSON Schema
NameRequiredDescription
messageYes
successYes

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=true, and readOnlyHint=false, so the safety profile is covered. The description adds nothing beyond that: no note on partial failure behavior, whether missing relations are silently ignored, or any auth requirement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with zero filler; the verb and resource lead. It is efficient, though its brevity borders on under-specification rather than true economy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and the single parameter is fully documented in the schema. However, for a destructive batch operation the description leaves behavioral and routing questions entirely to annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the nested relation object's from/to/relationType fields are each documented in the schema, so the description need not compensate. It adds no syntax or format detail beyond the schema, which is the expected baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (Delete), resource (relations), batch scope (multiple), and domain (knowledge graph). This distinguishes it from delete_entities and delete_observations, though the description never explicitly contrasts with those siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides no when-to-use guidance, no prerequisites, and no mention of alternatives such as delete_entities or delete_observations for removing other graph elements. The agent must infer usage purely from the name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

merge_entitiesMerge EntitiesA
Destructive

Prefer this to deleting one entity and re-adding its observations and relations to another: merge an entity into another, unioning their observations and repointing its relations

ParametersJSON Schema
NameRequiredDescriptionDefault
mergesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true and non-idempotent, and the description goes beyond that by disclosing exactly what is destroyed (the 'from' entity is removed) and what is preserved/reassigned (observations unioned, relations repointed). It does not discuss failure modes or whether a name collision is an error, but the behavioral core is well covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that front-loads the selection guidance before the mechanics. Dense but every clause earns its place; only mild friction from the colon-and-list construction.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive merge tool with an output schema and annotations covering the safety profile, the description supplies the missing semantic layer (what happens to observations and relations). Nothing critical is absent, though batch behavior of the `merges` array is unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Top-level schema description coverage is 0%, so the description must carry meaning, and it does explain the merge direction and outcome (merge one into another, union observations). However, it never maps this onto the `merges` array structure or clarifies that multiple merge pairs can be submitted, leaving parameter-level guidance thin.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (merge) and resource (entity), plus the exact mechanics: unioning observations and repointing relations. An agent can distinguish this from delete_entities, create_entities, and update_entities without inspecting any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says to prefer this over the delete-and-re-add workflow, which is precisely the alternative offered by the delete_* / create_* / add_observations siblings. It describes the pattern rather than naming the competing tools, so it is strong but not maximally explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

open_nodesOpen NodesB
Read-onlyIdempotent

Open specific nodes in the knowledge graph by their names

ParametersJSON Schema
NameRequiredDescriptionDefault
namesYesAn array of entity names to retrieve

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitiesYes
relationsYes

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and openWorldHint=false, so the safety profile is fully covered. The description adds only the name-based scope, saying nothing about behavior with missing/nonexistent names; with annotations carrying the burden, a baseline 3 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler; nothing is repeated or wasted. It is not padded, though it is arguably too terse to be maximally informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return format need not be explained, and the single-param schema is well documented. However, for a tool with close siblings search_nodes and read_graph, the description gives the agent no basis for choosing among them, leaving a real contextual gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Single parameter with 100% schema description coverage ('An array of entity names to retrieve'), so the schema already carries the semantics. The description's 'by their names' merely echoes this without adding format, batching, or matching-behavior detail, matching the baseline 3 for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a verb ('Open') and resource ('specific nodes in the knowledge graph') scoped by name, which distinguishes it from the broad read_graph and the query-based search_nodes. The verb 'Open' is slightly unconventional for a retrieval operation, but the schema's 'retrieve' wording and the name-scoped phrasing make the intent discernible.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit when-to-use guidance or named alternatives, even though the sibling set (search_nodes, read_graph) makes the selection question real. The agent must infer that you call this only when you already know the entity names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_conflictsRead ConflictsA
Read-onlyIdempotent

Call this when a write may have been superseded by another machine, or before trusting an edit: it lists the elements more than one node has written, with their authors, so a last-writer-wins resolution is visible instead of silent

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
conflictsYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, and non-destructive, so safety is covered. The description adds genuinely new behavioral context: the result surfaces authors alongside conflicting elements and makes last-writer-wins resolution explicit rather than silent, which tells the agent what the call actually reveals.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that front-loads the call trigger, then uses a colon to deliver the payload and rationale. It is slightly dense, but each clause (when to call, what it lists, why it matters) earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no parameters, full annotation coverage, and an output schema present, the description only needs to cover calling context and result meaning — both of which it does. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate. Baseline for a no-parameter tool is 4, and the description correctly introduces no non-existent inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('lists') and a precisely scoped resource ('the elements more than one node has written, with their authors'). That scope inherently separates it from siblings like read_graph or search_nodes, which return the graph or matches rather than multi-writer conflicts, so an agent can pick between them without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives two concrete trigger conditions: after a write may have been superseded by another machine, and before trusting an edit. That is clear when-to-use guidance, but it names no alternative tool and states no exclusion conditions, so it falls short of a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_graphRead GraphA
Read-onlyIdempotent

Read the knowledge graph, or a page of it: the optional limit and offset take a slice of the entities in name order

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoThe most entities to return; omit for all of them
offsetNoHow many entities to skip before the page; omit for none

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitiesYes
relationsYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), so the bar is lower. The description adds a genuinely useful behavioral fact beyond the annotations: entities are returned in name order, which is essential to know for offset pagination to be stable and meaningful.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single dense sentence that front-loads the core action and appends the pagination detail after a colon. No filler, and the most important information comes first.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present the description need not explain return values, and the annotations carry the safety profile, so the only real gap was ordering — which it fills. A note on graph size or pagination expectations would have made it fully complete, but nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description earns above that by supplying the ordering semantics (name order) that the schema descriptions omit — without that, an agent cannot reason about what an offset actually skips over. It still doesn't state whether offsets are 0-based beyond what the schema says.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource ("Read the knowledge graph") and explains the pagination slice, so the purpose is unambiguous. It does not, however, distinguish itself from siblings like search_nodes or open_nodes, which an agent could plausibly confuse with a full-graph read.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: the mention of limit/offset signals this is the bulk-read/pagination path, and unlike search_nodes there is no filtering. But there is no explicit when-to-use-this-vs-alternatives guidance or exclusion naming a sibling, so the agent must infer routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_nodesSearch NodesB
Read-onlyIdempotent

Search for nodes in the knowledge graph based on a query

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoThe most entities to return; omit for all of them
queryYesThe search query to match against entity names, types, and observation content
offsetNoHow many matches to skip before the page; omit for none
entityTypeNoOnly entities of this exact type; omit to match every type

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitiesYes
relationsYes

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and openWorldHint=false, so the safety profile is fully covered by structured data. The description adds no further behavioral context such as match semantics or result ordering, so it neither helps nor harms beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler or redundancy. It is efficient, though its brevity is partly under-specification rather than true conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need no explanation, and annotations plus full schema coverage handle safety and parameters. What is missing is sibling differentiation among 13 related tools, which is the one piece an agent genuinely needs here.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema itself documents query, limit, offset, and entityType precisely, including matching scope for query and exactness for entityType. The description contributes no additional parameter meaning, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ("Search") and resource ("nodes in the knowledge graph") and notes the query-based mechanism, so the core operation is unambiguous. However, it does nothing to distinguish it from close siblings like open_nodes or read_graph, which an agent must choose between.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to use this tool versus alternatives such as open_nodes, read_graph, or read_conflicts, and no prerequisites or exclusions are given. The agent must infer routing from the name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_entitiesUpdate EntitiesA
Destructive

Prefer this to deleting an entity and re-creating it under another name: rename existing entities and/or change their entity type, keeping their observations and relations

ParametersJSON Schema
NameRequiredDescriptionDefault
entitiesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the safety profile (destructiveHint=true, idempotentHint=false). The description adds real behavioral context beyond them: the operation preserves existing observations and relations, which is exactly what an agent needs to know to prefer it over delete+recreate. It does not disclose what happens on a name collision or a missing entity, hence not a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence of about 30 words with the decision rule front-loaded and the capability statement second. Nothing is redundant, though the sentence is slightly overloaded with a colon-joined clause.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, so return values need no explanation, and annotations cover the safety profile. However, for a destructive, non-idempotent mutation with a nested array payload and siblings like merge_entities and read_conflicts, the description is silent on collision handling, not-found entities, and partial-batch failure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Top-level schema coverage is 0% for the single `entities` parameter, so the description must carry meaning. It does: rename and/or entity-type change are partial updates, and the preservation clause clarifies that unspecified fields are untouched. It does not describe the array/batch shape or per-item failure behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb set (rename, change entity type) on a specific resource (existing entities) and explicitly frames itself as the non-destructive alternative to delete-then-recreate. An agent can distinguish it from delete_entities, create_entities, and update_observations without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Prefer this to deleting an entity and re-creating it under another name" is an explicit when-to-use rule with a named alternative pattern. It stops short of saying when NOT to use it, e.g. that merge_entities is the right sibling for consolidating duplicates or that read_conflicts should be consulted first.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_observationsUpdate ObservationsA
Destructive

Prefer this to delete_observations followed by add_observations: replace observation contents on existing entities in place

ParametersJSON Schema
NameRequiredDescriptionDefault
updatesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=false, and openWorldHint=false, so the safety profile is covered. The description usefully adds that replacement happens 'in place' on 'existing entities' (i.e., no new entities are created), but it is silent on what happens when oldContent does not match, or whether a multi-item update is atomic or partial on failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence, front-loaded with the routing recommendation ('Prefer this...') before the explanation after the colon. Every clause earns its place; no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and annotations cover the mutation's safety profile, so the description need not explain return values. It is nearly complete for an agent's decision-making, with only the match-failure/partial-update behavior and the shape of the 'updates' payload left unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is reported at 0% (the top-level 'updates' array has no description), so the description carries the burden of explaining parameter semantics — and it does not. It never explains that 'updates' is a list of {entityName, oldContent, newContent} triples or that oldContent acts as a match key, which is the single most important thing an agent needs to call this correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('replace observation contents on existing entities in place') and immediately distinguishes itself from the sibling pattern it replaces (delete_observations + add_observations). An agent knows exactly what this does versus the other 13 tools without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly prescribes when to use this tool over alternatives: 'Prefer this to delete_observations followed by add_observations.' The alternative and the condition selecting it are both named, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_relationsUpdate RelationsA
Destructive

Prefer this to deleting a relation and creating another: change the type of existing relations and/or repoint their endpoints

ParametersJSON Schema
NameRequiredDescriptionDefault
relationsYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=false, and readOnlyHint=false, so the safety profile is covered without the description. The description adds the useful detail that relations can be partially modified ('and/or'), but does not disclose what happens to unmatched relations, permissions required, or partial-failure behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single compact sentence with the recommendation front-loaded and no filler. It could be tightened slightly, but every clause carries meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need not be explained, and annotations cover the mutation safety profile. The description is sufficient to invoke the tool correctly, with only minor gaps around partial-update behavior and error handling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Top-level schema coverage is 0% for the 'relations' array, but the nested fields (newFrom, newTo, newRelationType) already carry complete descriptions including 'omit to keep the current one'. The description restates that type and endpoints can change, adding concept-level mapping but no syntax or format detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource (change the type of existing relations, repoint endpoints) and distinguishes the operation from the delete-then-create alternative. It is clear what the tool does, though it does not name the sibling tools (delete_relations/create_relations) directly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states a preference condition: use this instead of deleting a relation and creating another. This tells the agent when to choose update_relations over the delete+create path, though no other alternatives or exclusion conditions are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv1.1.0
    • Addedmerge_entities
    • Addedread_conflicts
    • Changedread_graph2 fields changed
      • addedInput schema / properties / limit
        Added value: +{
        +  "description": "The most entities to return; omit for all of them",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / offset
        Added value: +{
        +  "description": "How many entities to skip before the page; omit for none",
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
    • Changedsearch_nodes3 fields changed
      • addedInput schema / properties / entityType
        Added value: +{
        +  "description": "Only entities of this exact type; omit to match every type",
        +  "type": "string"
        +}
      • addedInput schema / properties / limit
        Added value: +{
        +  "description": "The most entities to return; omit for all of them",
        +  "maximum": 9007199254740991,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / offset
        Added value: +{
        +  "description": "How many matches to skip before the page; omit for none",
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
    • Addedupdate_entities
    • Addedupdate_observations
    • Addedupdate_relations
  2. 9 tool updatesv1.0.2
    • First observedadd_observations
    • First observedcreate_entities
    • First observedcreate_relations
    • First observeddelete_entities
    • First observeddelete_observations
    • First observeddelete_relations
    • First observedopen_nodes
    • First observedread_graph
    • First observedsearch_nodes

TDQS

A3.7/5.0

Scored across 14 tools

Disambiguation4/5

Each tool pairs a distinct resource (entity, observation, relation) with an action, and the update/merge tools clearly differentiate in-place edits from merges. The main soft spot is the read trio (read_graph, open_nodes, search_nodes), which are all retrieval operations and could occasionally be confused, though descriptions distinguish whole-graph paging, exact-name lookup, and query search.

Naming Consistency5/5

Every tool follows a clean verb_noun pattern (create_entities, delete_observations, update_relations, merge_entities, read_graph, search_nodes). Conventions are uniform across all 14 tools with no mixing of styles.

Tool Count5/5

14 tools is well within the sweet spot and each earns its place by covering a distinct resource-action pair across entities, observations, and relations plus graph-level reads. Nothing feels redundant or padded.

Completeness5/5

Full lifecycle coverage exists for all three resource types (create, update, delete on entities/observations/relations), supplemented by merge, rename-via-update, search, paged read, and conflict inspection. No obvious dead ends for a knowledge-graph workflow.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Semantic memory for AI agents — local-first MCP server with hybrid search, knowledge graph, contradiction detection, and plan-then-commit consolidation.
    243 npm
    4
    AGPL 3.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Self-hosted, local-first knowledge graph and memory server for AI agents. Enables agents to persist, recall, and organize knowledge through MCP with automatic distillation, deduplication, and cross-linking.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides a self-hosted shared memory service that lets AI agents capture and recall durable facts, decisions, and context across multiple tools and MCP-capable clients.
    3
    -