Power your agent
คำสั่งของผู้เผยแพร่เอง ไม่ใช่เอนด์พอยต์ของ PlugMCP
Host a copy with our keys and logs.
npx -y [email protected]Claude Code ไม่มีลิงก์ติดตั้งสำหรับคำสั่งแบบโลคัล ให้คัดลอกคำสั่งด้านล่างแล้วรันในเทอร์มินัล
NOONIEN_DIR=<value> NOONIEN_NODE_ID=<value> claude mcp add io.github.sequico-server-noonien -- npx -y server-noonien@1.3.0รันในเทอร์มินัล
{ "mcpServers": { "io.github.sequico-server-noonien": { "command": "npx", "args": [ "-y", "[email protected]" ], "env": { "NOONIEN_DIR": "<value>", "NOONIEN_NODE_ID": "<value>" } } }}claude_desktop_config.json
URL แบบรีโมตหรือคำสั่งบนเครื่องของคุณ? การติดตั้งสองแบบต่างกันอย่างไร
รันบน PlugMCP
A new server you create. It is not this listing's official endpoint. Use the publisher install when you want their URL and their credentials.
เปิด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.
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:
server-noonien removes the shared file and the central server at the root, and keeps the drop-in
tool surface:
<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.(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.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.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.server-noonien vs the official memory server vs hosted memory services:
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.
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
The s3 backend's @aws-sdk/client-s3 is an optional dependency, loaded only when
NOONIEN_BACKEND=s3 selects it. npm install -g server-noonien --omit=optional skips it when you
never use s3.
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 serverThe 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 CLInooniend — the daemonA 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.
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.
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:49778 \
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:
<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.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.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.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 caller that is not a peer yet is never adopted from its headers: nothing it
sends becomes membership, and its id is never taken on trust. It is offered as a discovery candidate
instead — the host the request came from, the port it announces — and the daemon confirms it on
/info, exactly like a Tailscale or DNS candidate. That is how a statically-seeded joiner is
discovered by the seeds it reached, within a round, and why an address announced for a different
host is never even probed. A node's own entry is authoritative; an address that serves a different
node is dropped.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.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
nooniend serves a small HTTP/JSON API on NOONIEND_LISTEN (default 0.0.0.0:49778). 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:49778/health
curl -s http://127.0.0.1:49778/status
curl -s http://127.0.0.1:49778/peers
curl -s http://127.0.0.1:49778/graph | jq '{entities: (.entities|length), relations: (.relations|length)}'
/graph folds everything the node holds, so it is a diagnostic for a normal directory: past 50 000
operations it answers 413 and asks for the shards instead, which serve the same data a page at a
time.
file and s3The 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, waiting
the same budget the file backend gives a lock, so a competing writer on one node id costs a wait
rather than a failed write; 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 conditional write carrying an ETag the store
cannot use — none, or a multipart one — is refused rather than guarded by a token that can never
match. Reading is not conditional: a shard object with a multipart ETag reads and stats normally.
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.
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.
Used by every command.
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, and it does not
have to be silent about it: an append carries the shard state the writer derived its sequence
numbers from, so a second writer is refused and re-derives instead of spending a sequence already
spent — both operations land, on their own sequences, and every peer receives both. Two writers that
land on the same millisecond still produce operations with the same id, which a read fails
loudly on. A unique node id is still a requirement, not something the tool can check.
Used by the MCP server (and the noonien CLI, which reads and writes the same shards).
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. An append is synced
before it is acknowledged, so a write the server has confirmed is on disk; a lock nobody refreshes
is stolen, by its age in either direction, because a folder a syncer replicates can carry one
stamped by a machine whose clock runs ahead.
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.
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.
Listen port. The daemon listens on 49778, a port we select ourselves from the dynamic range
(49152-65535). Nothing assigns it and nothing has to: the expected deployment is a private
network — Tailscale or another VPN, with the port reachable only over it — where every peer is
configured with the port instead of looking it up, so no reserved number is needed.
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. Give every node the same
port, and set NOONIEND_LISTEN and NOONIEND_ADVERTISE to match while they are mixed.
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:
Ordering is deterministic: entities by name, relations by (from, to, relationType), observations
in operation order (HLC, node, sequence).
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.
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 unreachable and
unverifiable — 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. A peer this node has never exchanged
with, on the other hand, gates nothing — and not out of leniency: a causally aware competitor,
the only thing a retained tombstone has to beat, means having folded the element, hence holding the
tombstone. An operation of this node leaves here only through a push, and a push is recorded with
the receiver's post-push high-water mark, so a tombstone that is collectable is held by nobody
at all; the other authors' work such a peer may hold is reported by the peer that relayed it, and a
mark this node lacks blocks on its own. An inbound push is recorded the same way, at once: the
pusher authored what it pushed, so it holds it — which keeps the elements it delivered frozen even
if it never answers again, and puts a pusher from outside the membership under the gate. A peer
that reaches this node with a push also stays reachable, so the round suspends until an exchange has
recorded the rest of its frontier. The peers tolerated this way are
published as noonien_gossip_unreached_peers.
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 has nothing to materialise and gates nothing while it stays silent; its
own frontier gates it from the first exchange. The retired record is durable, so the daemon syncs it
before renaming it into place and refuses to start on a file it cannot read — an empty one included,
which only a crash that lost the write can leave. A peer record it cannot parse is one whose
protection is unknown, so it is not read as "this peer holds nothing": that node keeps a blanket,
and noonien_gossip_blankets counts it until an operator resolves the file.
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.
The daemon serves every metric at /metrics (the MCP server is stdio and has no endpoint). The
collection and retention state:
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).
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.
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.
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.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 before it is dialed, and a dial
target only ever comes from a peer's own /info, a static seed, the membership list a peer gossips
or a candidate built from the host the request came from — so an announced address can never
point the dialer at another host. The store also refuses a new shard once it holds as many as a
peer may advertise, so neither a pushed node id nor an advertised one can fill the directory. 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.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).SECURITY.md.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 audit # npm audit --audit-level=high
npm run gate # all of the above, the audit first
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.
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:
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.
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.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.
If this component is saving your company hundreds of hours of infrastructure development, please consider supporting the project:
Shared, serverless, conflict-free knowledge-graph memory for AI agents
{ "mcpServers": { "io.github.sequico-server-noonien": { "command": "npx", "args": [ "-y", "[email protected]" ], "env": { "NOONIEN_DIR": "<value>", "NOONIEN_NODE_ID": "<value>" } } }}เครื่องมือ
ยังไม่มีรายการเครื่องมือ เซิร์ฟเวอร์แบบรีโมตจะได้รายการหลังการตรวจสอบสถานะครั้งแรก
FAQ
คัดลอกคอนฟิก stdio สำหรับ Cursor, Claude Desktop, Claude Code หรือ VS Code ได้ในหน้านี้ คอนฟิกนี้ใช้การติดตั้งของผู้เผยแพร่ ไม่ใช่พร็อกซีของ PlugMCP
ผู้เผยแพร่ระบุตัวแปรสภาพแวดล้อมเหล่านี้: NOONIEN_DIR, NOONIEN_NODE_ID
Server Noonien ทำงานแบบโลคัลผ่าน stdio
ตรวจสอบล่าสุดเมื่อ 2026-11-14 Shared, serverless, conflict-free knowledge-graph memory for AI agents