Skip to main content
Glama

Neotoma

A record, not recall.

Neotoma is the system of record for AI agents. Without one, an agent works from whatever it happens to recall: part of the last session, a note its tool saved, or nothing. With one, it reads what was stored, from which source, and what is current now, so the agents you connect can defer to one answer.

npm install -g neotoma
neotoma init
neotoma setup --tool claude-code

neotoma setup --tool claude-code connects Claude Code: it writes MCP entries, CLI instruction files, and an allow rule that lets Claude Code run any neotoma command; Install lists everything it writes. Human-run setup omits --yes; automated setup may add it after the operator authorizes those changes. Omission does not prevent every setup write.

After a plain npm install, CLI data commands such as neotoma store return AUTH_REQUIRED, because the CLI uses the production profile, which needs a token. Run them as neotoma dev ... or with NEOTOMA_ENV=development.

Other hosts are listed under Connect your tools.

Known limitations: docs/known_limitations.md lists behaviour you may run into, each with a workaround and its tracking issue.

Agents cannot own work they cannot verify.

You cannot delegate real work to agents you cannot hold to account. Accountability has two halves: what was true, and who was authorized. Neotoma answers what was true. Your agents can produce a value; they cannot tell you its version, its source, or whether it is still current. Neotoma keeps every fact as an observation tied to its source, so you can see where a value came from and what is current now.

What ships today is the foundation of that record: Neotoma ingests sources into observations that the write path does not edit (the database does not enforce this) and computes entity snapshots, with each current field tracing back to the observation that set it. It is exposed over an MCP server, a REST API, a CLI, and a bundled web Inspector. See Vision and execution status for exactly where the implementation stands against the vision.

It is built for developers building and operating AI agents who need a shared record that connected agents can defer to, with provenance on every field. The same engine serves two further groups: individuals keeping one record across their assistants on a single-user install, and operators running a shared, multi-user instance with agent access control. Agents store structured records (contacts, tasks, transactions, decisions, events, and any other type) once and read them back across every tool and session. The same observations and rules give the same snapshot.

neotoma.io · Install · Documentation (also served in-app at /docs)

What Neotoma is

Neotoma is a system of record, not a chat memory or a vector cache. It records observations from your sources, resolves them into entities, and computes a current snapshot for each entity by reducing its observations in a deterministic order. No observation is overwritten by a later write. Corrections and reinterpretations add new observations, and the earlier observations stay readable for that entity.

Three properties hold across every interface:

  • Deterministic. Entity IDs, event IDs, and reducer output are derived from the inputs by hashing. Entity IDs agree across instances unless tenant-scoped entity IDs are on (NEOTOMA_SANDBOX_MODE or NEOTOMA_TENANT_SCOPED_ENTITY_IDS). Source and observation IDs also depend on the user and on the interpretation run, so they can differ between instances. The stdio MCP server writes as the shared local development user, but the HTTP server, and therefore the CLI, runs by default in local_sandbox mode with a per-install user, so the same data stored on two installs gets different source and observation IDs. Each interpretation run also gets a new observation ID. Do not rely on source or observation IDs matching across instances. The same observations and rules give the same snapshot regardless of order or timing. IDs are derived from inputs; observation timestamps are wall-clock, and the default merge rule orders by them.

  • Immutable and auditable. The write path adds sources and observations and does not edit them; the database does not enforce this with a constraint. Each current field names the observation that set it, and a stored observation names its source. A correction observation has no source, and a write from the CLI records no writer.

  • Self-hosted and portable. The engine is embedded SQLite plus content-addressed file storage under a directory you control, with optional AES-256-GCM at-rest encryption. Nothing is used for training, and you can export your data as current state; observation history is not part of the export. In single-user mode the data stays entirely local; multi-user, peer-sync, and hosted modes are opt-in (see Deployment modes).

Related MCP server: midas-memory-mcp

How it works

graph LR
  Sources["Sources (files, messages, API payloads)"] --> Obs[Observations]
  Obs --> Res[Entity resolution]
  Res --> Snap["Entity snapshots (reduced, versioned)"]
  Snap --> Graph["Graph + timeline"]
  Graph <--> MCP[MCP]
  Graph <--> REST[REST API]
  Graph <--> CLI[CLI]
  Graph <--> Inspector[Inspector]
  1. Source. Raw input is stored once, deduplicated by SHA-256 content hash, with a deterministic source ID.

  2. Interpretation. Structured fields are extracted from the source (directly for structured input, or via an LLM interpretation run whose model, temperature, and prompt are recorded).

  3. Observation. Each extracted fact becomes an observation with a hash-derived ID, linked to its source and interpretation.

  4. Entity resolution. A deterministic canonical name (driven by the type's schema) maps the observation to an entity, creating it if needed.

  5. Snapshot. All observations for an entity are reduced into a current snapshot using per-field merge policies, with a stable order (observed_at DESC, id ASC) and a field-to-observation provenance map.

  6. Timeline and relationships. Date fields emit deterministic timeline events; typed relationships connect entities into a graph.

What you can do with it

Neotoma v0.28.0 exposes 67 MCP tools and 112 REST paths in its OpenAPI spec. They cover largely the same capabilities, but the MCP server, the REST API, and the CLI are separate implementations, and some checks and attribution differ between them (see Known limitations). The capability surface includes:

Ingest and store. Store structured records or raw files in one call. File ingestion extracts text from PDF (with a first-page image fallback), CSV (with adaptive chunking for large files), Parquet, JSON, and plain text; images and audio are stored as raw sources. A committed, keyed structured store started on v0.26.0 that stops partway finishes its missing observations on a same-key retry; a store carrying an interpretation returns ERR_STORE_WRITE_INCOMPLETE, and keys left over from earlier versions behave differently over MCP and HTTP (see Known limitations). See Known limitations.

Resolve, retrieve, and search. Look up entities by identifier (name, email, and similar), resolve identity from multiple signals with confidence scoring, list observations, retrieve a field's provenance chain, traverse the relationship graph N hops, and pull a full graph neighborhood. When an embedding key is configured, semantic vector search runs over entity snapshots (stored locally in sqlite-vec); keyword filtering works without it.

Correct and evolve. Submit corrections as observations marked as corrections, never edits: a correction wins in the snapshot until a later correction for the same field replaces it, on array fields too, and the earlier observation stays readable. Schemas are inferred from your data, recommended from recurring unknown fields, and can be auto-enhanced or updated incrementally with versioning. New entity types work without any code change.

Relate and sequence. Create typed relationships (for example PART_OF, DEPENDS_ON, REFERS_TO, DUPLICATE_OF), query timeline events across types and date ranges, and read an entity's observation history in a deterministic order.

Manage the entity lifecycle. Merge duplicates, split an entity by predicate, soft-delete and restore, list potential duplicates, and run GDPR-oriented deletion. Merge and split are transactional and audited.

Control multi-agent access. Writes carry attribution when the writer signs its requests (AAuth) or reports a client name. Unless an agent signs its requests, the name is the writer's own claim, and some write paths, including the CLI, record no writer. Agent grants express least-privilege capabilities (which operations on which entity types). This is infrastructure behind the guarantees above, not a feature to evaluate on its own. The mechanics, including optional hardware-attested authentication and guest access tokens, are documented in the AAuth reference.

Federate, sync, and intake. Register peer instances and sync entities between them with configurable scope and conflict resolution (last-write-wins, source priority, or manual). Subscribe to entity or event changes over webhooks (HMAC-signed) or Server-Sent Events. Accept guest entity submissions, mirror GitHub issues into conversation threads, and mirror your data to deterministic, git-trackable canonical Markdown.

Export and own your data. Produce a bounded MEMORY.md summary, a JSON snapshot export of current entity state with a per-field provenance map (field to the observation ID that set it) and attribution metadata (the observations themselves, and so the observation history, are not exported), or a complete Markdown mirror of every entity, relationship, source, and timeline day.

Interfaces

The same record is reachable four ways. The MCP server, the REST API, and the CLI are separate implementations, so some checks and attribution differ between them (see Known limitations).

Interface

What it is

Transports

MCP server

Model Context Protocol tools for agents to store and retrieve state

stdio, WebSocket, streamable HTTP

REST API

Full HTTP interface for application integration

HTTP/HTTPS, OAuth or key-based auth

CLI

The neotoma command, around 170 commands for setup, scripting, and direct access

local process

Inspector

Bundled web app for browsing and managing the store

served by the API server

The Inspector

The Inspector is a single-page web app bundled into the build and served by the API server (at / for browsers, no separate deployment). It is an operator console for the data store, with screens for:

  • Entities (browse, detail, correct fields, view history and provenance, per-entity timeline)

  • Observations, sources, interpretations, and recent activity

  • An interactive knowledge-graph explorer

  • Relationships and the global timeline

  • Schemas and entity types (browse, register, inspect merge policies)

  • Agents, agent grants (create, suspend, revoke, restore), peers, and subscriptions

  • Issues, conversations and turns (the agent audit trail), access policies, and compliance

  • Search, analytics and usage, settings (including dark mode), a sandbox surface, and an in-app documentation browser at /docs

Install

npm install -g neotoma
neotoma init
neotoma setup --tool claude-code

In the current package source, neotoma setup --tool claude-code writes the MCP entries and more. The skill count below includes source changes not yet in published v0.27.0, which contains 12 installable skills; check the package version you actually use:

  • Two MCP entries in the project's .mcp.json: neotoma-dev (development profile, neotoma.db) and neotoma (production profile, neotoma.prod.db). They use separate databases, so an agent on one entry does not see what an agent on the other stored.

  • CLI instruction files for Claude Code, Cursor, and Codex in the project: .claude/rules/neotoma_cli.mdc, .cursor/rules/neotoma_cli.mdc, and .codex/neotoma_cli.md.

  • When claude is on your PATH, it runs claude plugin marketplace add and claude plugin install to install the Neotoma Claude Code plugin (five hooks). Without claude on PATH, setup writes everything else and then exits with status 1.

  • When ~/.claude exists, it links 13 Neotoma skills into ~/.claude/skills.

It also adds two allow rules, Bash(neotoma:*) and Bash(npm install -g neotoma:*), to .claude/settings.local.json (project scope, the default). The first is a wildcard: Claude Code then runs any neotoma command without asking, including destructive ones such as neotoma reset, neotoma storage merge-db, neotoma backup restore, and neotoma processes kill. --scope both also writes the rules to ~/.claude/settings.json, and --skip-permissions writes none. For Cursor the same two rules go to .cursor/allowlist.json; for Codex they go to the user-level ~/.codex/config.toml whatever the scope.

For another host, use its row in Connect your tools; neotoma setup --help lists the supported hosts. To check MCP config files later, neotoma mcp config scans for them and asks before adding a missing entry.

Prerequisites: Node.js 20.x (see .nvmrc) and npm 9+. No .env is required for local storage. The neotoma doctor command checks your environment, database, and security configuration.

The CLI also handles MCP config scanning and sync, harness configuration, lifecycle hook installation, peers and access management, plans, transcript and onboarding import, server and database management, memory export, and the canonical mirror. See the CLI reference.

Example

neotoma store --json='[{"entity_type":"task","title":"Submit expense report","status":"open"}]'
neotoma entities list --type task
neotoma upload ./invoice.pdf

Agents do the same through MCP tool calls such as store, retrieve_entities, and retrieve_entity_by_identifier. The MCP server and the CLI are separate implementations, so some behaviour differs (see Known limitations). The MCP server logs the equivalent CLI command for each tool call.

Connect your tools

Neotoma works across MCP-capable hosts. Most are a single setup command; some add lifecycle hooks, which capture turns automatically when the local API is running.

Host

Modes

Install

Cursor

MCP + hooks

neotoma setup --tool cursor

Claude Code

MCP + hooks

neotoma setup --tool claude-code

Claude Desktop

MCP (local + remote)

neotoma setup --tool claude-desktop

Codex CLI

MCP + hooks

neotoma setup --tool codex

OpenClaw

Native plugin + MCP

neotoma setup --tool openclaw

ChatGPT

MCP App + Custom GPT Actions

Manual HTTPS + OAuth

Windsurf, Continue, VS Code (Copilot)

MCP

Manual MCP config; see the per-host guides in the Integrations matrix (neotoma setup --help lists only the five hosts above)

OpenCode

hooks

plugin install

Full matrix: Integrations.

Hooks are the reliability floor (capture of each turn while the local API is running, retrieval injection, compaction awareness) and MCP is the quality ceiling (agent-driven structured writes). The Claude Code plugin's hooks post to 127.0.0.1:3080 unless NEOTOMA_BASE_URL is set, and fail quietly when nothing is listening there (see Known limitations). Per-harness packages live under packages/: claude-code-plugin, cursor-hooks, opencode-plugin, codex-hooks, claude-agent-sdk-adapter.

Client SDKs: @neotoma/client (TypeScript) and neotoma-client (Python).

OpenClaw native plugin: Neotoma ships as a native OpenClaw plugin with kind: "memory", so it can fill the dedicated memory slot with all MCP tools registered as agent tools.

Vision and execution status

As agent use expands, reality fragments across chat histories, agent memories, files, databases, tickets, and application state. The failure is not retrieval. It is that humans and agents can no longer answer: which assertion is current? which source introduced it? was it observed, inferred, or decided? who had authority to correct it? which agents relied on the previous version? is there legitimate disagreement? Neotoma's long-term role is collective epistemic coherence: not "better semantic memory," but deterministic, governed, multi-party state that humans and agents maintain together: attributed, correctable, disagreement-aware, portable, and independent of any one model or application.

Vision phase

What it means

Status

P0: Deterministic single-writer state

Observations the write path does not edit, hash-derived entity IDs, deterministic reduction, field-level provenance, corrections as new observations, per-entity observation history

✅ Shipped: in daily use; a correction stays authoritative on its field until a later correction replaces it

P1: Multi-user state foundations

Human and agent identities, attributed writes, tenant isolation, access controls, shared instances

🟡 Partially shipped: hosted multi-user mode with user_id tenancy, per-agent identity (AAuth, optional hardware attestation), capability grants, guest tokens, peer federation. Not yet: organizations/workspaces, membership lifecycle, invitations, offboarding

P2: Authority over state

Domain ownership, correction rights, supersession, policy ownership, temporary grants, disclosure logs

🟡 Mechanisms exist (corrections, SUPERSEDES relationships, per-operation access controls); authority semantics (who may correct what, scoped by domain) are future work

P3: Multi-principal semantics

Competing interpretations, disputed claims, official positions, consensus vs unresolved disagreement, field-level policies, historical validity

🔜 Planned: interpretations are first-class today; dispute/official-position semantics are not

P4: Organizational intelligence substrate

Non-developer governance UI, "why does the system believe this?" explanations, impact analysis, authority-graph views, enterprise administration

🔭 Vision: the Inspector's provenance and audit views are the seed

Ateles is the other half. Its repository describes it as an operating system for humans and AI agents to jointly initiate, execute, approve, and reconcile work under explicit authority. Neotoma holds the record that this work runs against. Neither is sufficient alone. A record without authority tells you what happened but not whether it was permitted; authority without a record tells you who may act but not against what state.

Skills

Skills are guided workflows that teach an agent to import, extract, and persist data. They ship with the npm package and are installed by neotoma setup.

Skill

Description

ensure-neotoma

Install Neotoma, configure MCP, verify connectivity. Prerequisite for the rest.

remember-email

Import email, extract contacts, tasks, events, and transactions.

remember-conversations

Import ChatGPT/Claude/Slack exports, reconstruct a decision timeline.

remember-meetings

Ingest transcripts, extract decisions and action items.

remember-finances

Import statements, receipts, and invoices as structured transactions.

remember-contacts

Consolidate contacts from email, calendar, chat, vCards.

remember-calendar

Import events and commitments.

remember-codebase

Repository integration: inventory, decisions, MCP wiring.

recover-sqlite-database

Check integrity and recover a corrupted database.

Ordinary storage and retrieval need no skill: the live MCP tool instructions delivered at session start cover store/query/recall directly.

Record types

Neotoma stores typed entities with versioned history and provenance. The schema is flexible: store any entity type with whatever fields the data implies, and the system infers and evolves the schema.

Type

Stores

Examples

Contacts

People, companies, roles

contact, company, account

Tasks

Obligations, deadlines, goals

task, habit, goal

Transactions

Payments, receipts, invoices

transaction, invoice, receipt

Contracts

Agreements, clauses, amendments

contract, clause, amendment

Decisions

Choices, rationale, reviews

decision, assessment, review

Events

Meetings, milestones, outcomes

event, meeting, milestone

Storage, privacy, and security

  • Storage: Local SQLite (better-sqlite3, WAL mode) plus content-addressed file storage, under NEOTOMA_DATA_DIR. Separate dev and prod profiles. Semantic search uses sqlite-vec locally.

  • Privacy: Your data is never used for training; logs and event payloads carry IDs, not PII. No telemetry. Update checks ask the npm registry for the latest version: the interactive CLI about once a day (off with NO_UPDATE_NOTIFIER=1, or --no-update-check, which applies to the CLI only), and the stdio MCP server when a session starts and about every 6 hours (off with NO_UPDATE_NOTIFIER=1). Each request carries only the package name, and the registry sees ordinary connection metadata such as your address. The npm_check_update MCP tool can make the same request when an agent calls it, and is not turned off by NO_UPDATE_NOTIFIER. In single-user mode data stays local; MCP OAuth, peer sync, and hosted instances share data as configured (see Deployment modes).

  • Encryption: Optional AES-256-GCM column encryption of sensitive content and metadata, keyed by a key file or BIP-39 mnemonic. Some tables (for example the event log) are not yet column-encrypted; pair with an encrypted volume for full coverage. See architecture.

  • Auth: Local auth for single-user installs, MCP OAuth for hosted use, optional hardware attestation for agents, and explicit per-operation access controls. Run neotoma doctor to verify your setup. See Auth and Privacy.

Deployment modes

The same SQLite-backed engine runs in two modes:

  • Local single-user. Run it on your machine for your own record across assistants. Local auth (or key-based auth when encryption is enabled), data stays local, no network exposure required.

  • Hosted multi-user. Run a shared instance with MCP OAuth, per-agent hardware-attested identity (AAuth) and capability grants, guest submission tokens, peer federation, and a sandbox demo user. Deploy targets include Docker and Fly. Tenancy is enforced by user_id scoping over the same local SQLite engine.

The "stays local" and privacy claims above apply to the local single-user mode. Hosted, OAuth, and peer-sync modes share data per their configuration.

Who this is for

Neotoma is primarily for developers building and operating AI agents who need a shared record that connected agents can defer to, with provenance on every field. The product ships as an MCP server and REST API with drop-in hook packages (Claude Code, Cursor, Codex, OpenCode), a Claude Agent SDK adapter, and TypeScript and Python clients. Its core value (versioned observations, deterministic snapshots, field-level provenance, idempotency) is record-keeping infrastructure for agent loops, not an end-user app.

Two further audiences follow from the same code: individuals keeping a personal record across assistants (the built-in types are personal-information shaped, and the single-user SQLite plus Inspector path serves one person across assistants), and operators of a shared or hosted instance (OAuth, peer federation, and Docker or Fly deploy targets). The agent-identity and grants machinery is infrastructure supporting attribution and provenance for every audience, not a reason a particular audience adopts Neotoma.

It is not aimed at casual note-taking, PKM/Obsidian-style human-driven knowledge bases, or users who need a fully hosted product with nothing running on their own machine. Neotoma is local-first by design, and that part is not a temporary state. Installation is: today it is npm and the CLI, and a guided installation is intended but not yet available. If today's install path is what stands between you and trying it, that is our problem to fix, not a sign you are the wrong user.

Full profile: ICP from functionality: the canonical public ICP statement. For how it relates to the other ICP documents and which is canonical for which surface, see ICP reconciliation.

Status

Version: see the latest release · License: MIT · Storage: local-first (SQLite + local files); hosted multi-user mode is opt-in.

Neotoma is used daily in real agent workflows. The project moves fast, and APIs or storage formats can change between versions. The core guarantees (deterministic reduction, per-entity observation history, per-field provenance) are stable, phase P0 of the vision roadmap, with P1 partially shipped in hosted mode. Schemas, extraction across versions, and backward compatibility are not yet guaranteed.

Known limitations: docs/known_limitations.md lists the specific ones, each with what you will observe, a workaround, and its tracking issue.

Development

git clone https://github.com/markmhendrickson/neotoma.git
cd neotoma
npm install
npm test

Common commands:

npm run dev          # MCP server (stdio)
npm run dev:full     # API + UI + build watch
npm run cli:dev      # CLI in dev mode (tsx)
npm run type-check   # TypeScript
npm run lint         # ESLint
npm test             # unit/contract/security tests
npm run test:integration
npm run test:e2e     # Playwright (Inspector)

Documentation

Documentation is stored under docs/ (start at the documentation index) and served in-app at /docs, browsable in the Inspector.

Contributing

Neotoma is in active development. Open an issue or discussion for questions or collaboration. See CONTRIBUTING.md and SECURITY.md. License: MIT.

Available Tools

67 tools
add_entity_messageA

Append a follow-up conversation_message to the thread for a submitted root entity (entity_id).

Inputs: message (required), entity_id (required).

add_issue_messageA

Append a message to an issue thread. Provide entity_id or issue_number (issue_number is an integer). Pass guest_access_token when the local issue snapshot does not already carry the operator token. If remote append fails after local/GitHub side effects, inspect remote_submission_error instead of retrying blindly.

Inputs: body (required), entity_id, issue_number, reporter_channel, +4 more.

add_peerC

Register a Neotoma peer (peer_config) for bidirectional sync: peer_url, entity_types allowlist, shared_secret for POST /sync/webhook verification.

Inputs: peer_id (required), peer_url (required), direction (required), peer_name (required), +7 more.

analyze_schema_candidatesB

Analyze raw_fragments to identify fields that should be promoted to schema fields. Returns recommendations with confidence scores based on frequency and type consistency.

Inputs: user_id, entity_type, min_frequency, min_confidence.

audit_undeclared_fragmentsA

Report the accumulated undeclared raw_fragments awaiting schema declaration — fields stored on observations but excluded from the entity snapshot because no active schema declares them. Read-only; declares nothing. Returns, per entity_type, the undeclared fragment_keys with occurrence and affected-entity counts and a schema_missing flag (true when the type has stranded fragments but no active schema), plus total_entity_types / total_undeclared_fields rollups, in deterministic order (occurrences desc, affected desc, key asc; types by total occurrences desc). Optional entity_type scopes the audit to one type. The aggregate counterpart to the per-store unknown_fields signal: use it to triage the backlog before drafting update_schema_incremental / register_schema work.

Inputs: user_id, entity_type.

correctA

Create a priority-1000 correction observation to override lower-priority extracted fields. Higher-priority sources remain authoritative; equal-priority ties follow configured source-kind precedence, then deterministic recency. Read back the entity snapshot and field provenance before claiming the correction took effect.

Inputs: field (required), value (required), entity_id (required), entity_type (required), +4 more.

correct_transactionA
DestructiveIdempotent

Atomically compare and correct declared fields on multiple entities. Supply each loaded observation count and expected fields. Any conflict, denied write or snapshot mismatch rolls back every change. Retry the identical request and key after uncertainty. This primitive does not authorize application-level approval.

Inputs: entities (required), idempotency_key (required), user_id.

create_interpretationB

Create an interpretation row for an existing source from agent-extracted flat entities. Observations produced by this tool are linked to both source_id and interpretation_id. Use store with an interpretation block when the source-derived extraction can be batched in one store call.

Inputs: entities (required), source_id (required), user_id, relationships, +2 more.

create_relationshipA

Create a typed relationship between two entities. The relationship_type vocabulary is a runtime registry, not a fixed list: call list_relationship_types to read what this instance accepts, and register_relationship_type to add to it. Use EMBEDS when a container entity (e.g. blog post, document) embeds an asset entity (e.g. image, attachment): source_entity_id = container, target_entity_id = asset. For images/files stored in Neotoma: store the file via store (get source_id), create an image/media entity with source_id, then create_relationship(EMBEDS, post_entity_id, image_entity_id). Optional metadata: caption, order. Both source_entity_id and target_entity_id must be entities you own; store the entity first (or in the same store call), then link it.

Inputs: source_entity_id (required), target_entity_id (required), relationship_type (required), user_id, +2 more.

create_relationshipsB

Create multiple typed relationships between existing entities in one batch

Inputs: relationships (required), user_id, source_id.

delete_entityB

Hide an entity from current snapshots and queries through an immutable lifecycle observation. After the explicit authority cutover, serialized action sequence controls visibility; before cutover, existing legacy behavior applies. Each accepted request is a separate action, without an idempotency key or conditional rollback baseline. Physical legal erasure is a separate operation.

Inputs: entity_id (required), entity_type (required), reason, user_id.

delete_relationshipA

Delete a relationship. Requires the exact relationship_type between the two entities; if unknown, call list_relationships with source_entity_id and target_entity_id first to discover it. Creates a deletion observation so the relationship is excluded from snapshots and queries. Immutable and reversible for audit. Returns 404 with a discovery hint when no live relationship matches the supplied triple.

Inputs: source_entity_id (required), target_entity_id (required), relationship_type (required), reason, +1 more.

describe_entity_typeA

Return the full active schema for one entity_type: field_names, per-field type/required/description (field_summary), required_fields, schema_version, and the raw schema_definition. Call this before store when you know the entity_type but not its declared field shape, so the first store lands with zero unknown_fields and zero required_fields_missing warnings. Repair workflow: when a prior store or correct returned unknown_fields_count > 0 (or unknown_field: true), call describe_entity_type to see the declared fields, then re-store or correct the value into a declared field that fits; only add a new field (update_schema_incremental / register_schema, per the response hint) when no declared field fits. Read-only; no persistence side effects. Errors with InvalidParams when the entity_type has no active schema (use list_entity_types to discover types, or register_schema to create one). Scope note: describe prefers user scope (always passes the session user id), and so does update_schema_incremental when user_specific is omitted, so a type visible here can be extended without restating scope. Only an explicit user_specific: false narrows the update to global and can return ERR_SCHEMA_SCOPE_MISMATCH; never register_schema for that code.

Inputs: entity_type (required), user_id.

describe_instance_policyA

Return this instance's data policy: what it is for (purpose), which entity types are in scope (in_scope_entity_types) or explicitly out of scope (out_of_scope_entity_types), whether person-data writes require a lawful-basis tag or provenance metadata, and the maximum field sensitivity class it will hold. Call this at the start of a session against an unfamiliar or shared instance, before your first store, so your writes comply instead of being rejected — policy discovered by trial-and-error rejection is strictly worse than reading it up front. Response shape is {"policy": <object|null>, "entity_id": <string|null>}. Returns {"policy": null, "entity_id": null} when the instance declares no policy; that means unrestricted, NOT deny-all. entity_id is an opaque identifier — pass it to correct() when authoring/updating the policy remotely rather than re-deriving or guessing it. When enforcement is "enforced", violating store/correct calls are rejected with ERR_STORE_POLICY_DENIED and nothing in the request is persisted; when "advisory", violating writes are accepted but you are still expected to comply. Takes no arguments: the policy is instance-wide, identical for every caller. Read-only; no persistence side effects.

Inputs: no named parameters.

get_authenticated_userA

Get the authenticated user ID for the current MCP session. Returns the user_id that is automatically used for all authenticated actions.

Inputs: no named parameters.

get_entity_submission_statusA
Read-only

JSON entity snapshot for a submitted entity_id; optional guest_access_token for token-scoped read-back.

Inputs: entity_id (required), guest_access_token.

get_entity_type_countsA

Return canonical entity counts by entity_type for the authenticated user. Uses the same dashboard stats aggregation behind GET /stats and sorts results by count descending, then entity_type ascending. Use this when the user asks for counts by type, histograms, or sorted totals. This returns row counts, unlike list_entity_types where field_count is schema field width.

Inputs: user_id.

get_issue_statusA
Read-only

Return issue status and thread messages. Provide entity_id or issue_number (issue_number is an integer). Pass guest_access_token when token-scoped remote read-through is needed.

Inputs: entity_id, skip_sync, issue_number, guest_access_token.

get_peer_statusB

Fetch peer_config by peer_id plus remote_health (/health probe, semver compat vs this server).

Inputs: peer_id (required).

get_relationship_snapshotC

Get the current snapshot of a specific relationship with provenance

Inputs: source_entity_id (required), target_entity_id (required), relationship_type (required).

get_schema_recommendationsB

Get schema update recommendations for an entity type from raw_fragments analysis, agent suggestions, or inference.

Inputs: entity_type (required), source, status, user_id.

get_session_identityA

Resolve the current session's attribution: trust tier, AAuth / clientInfo fields, active anonymous-write policy, and whether the session is eligible for trusted writes. Safe to call as a preflight health check; does not write any rows.

Inputs: no named parameters.

get_subscription_statusA

Return one subscription snapshot by subscription_id (webhook secret omitted).

Inputs: subscription_id (required).

health_check_snapshotsA

Check for stale entity snapshots (snapshots with observation_count=0 but observations exist). Returns health status and count of stale snapshots.

Inputs: auto_fix.

identify_entity_by_signalsA

Resolve an entity from a multi-signal bundle (name, email, company, domain, phone, and open-ended string props). Returns best_match with identity_score, resolution_band (high/medium/low/unresolved), ranked candidates, and matched_signals. Use when you have partial or combined identity information and want a single-call resolution with confidence scoring.

Inputs: signals (required), user_id, entity_type, entity_types, +2 more.

list_entity_submissionsC
Read-only

List recent entities of a given entity_type for the authenticated user.

Inputs: entity_type (required), limit, offset.

list_entity_typesA

List available entity types. Without keyword: returns a short summary (entity_type, schema_version, field_count) for all types. field_count here means schema field width, not stored entity row count. With keyword: returns full schema (field_names, field_summary) for matching types; pass summary: true to get summary only. Uses hybrid search when keyword is provided: keyword matching first, then vector semantic search. Use before storing structured data to determine the correct entity_type.

Inputs: keyword, summary, user_id.

list_interpretationsC

List interpretation runs for the authenticated user, optionally filtered by source_id.

Inputs: limit, offset, user_id, source_id.

list_observationsC

List all observations for a given entity

Inputs: limit, offset, entity_id, created_since, +1 more.

list_peersA

List peer_config rows for the current user (secrets redacted).

Inputs: no named parameters.

list_potential_duplicatesA

List candidate duplicate entity pairs for an entity_type. Read-only fuzzy post-hoc detector; never auto-merges. Per-schema thresholds and fields come from the schema's duplicate_detection_threshold and duplicate_detection_fields; callers may override threshold per call. Confirm pairs with the user, then hand off to merge_entities.

Inputs: entity_type (required), limit, user_id, threshold.

list_recent_changesA
Read-only

List the most recently changed records across core Neotoma tables (entities, sources, observations, interpretations, relationships, timeline_events) for the authenticated user. Returns items ordered by latest activity_at.

Inputs: limit, offset.

list_relationshipsA

List relationships for an entity, or discover the relationship type(s) between two specific entities. Filter by entity_id (with direction), or by source_entity_id and/or target_entity_id, and optionally relationship_type. To discover the type before delete_relationship, pass both source_entity_id and target_entity_id: each returned relationship carries its relationship_type. Soft-deleted relationships are excluded by default, so a deleted edge will not be re-offered for deletion; pass include_deleted: true to include them for audit. Paginated via limit/offset.

Inputs: limit, offset, user_id, direction, +5 more.

list_relationship_typesA

Discover the registered relationship vocabulary visible to the authenticated user, including types with no edges. Read this before writing an unfamiliar edge. Registration metadata is advisory except acyclic, which is enforced.

Inputs: scope, keyword, include_deactivated, include_edge_counts.

list_subscriptionsA

List the current user's active subscriptions (webhook secrets omitted).

Inputs: no named parameters.

list_timeline_eventsB
Read-only

Query timeline events with filters (type, date range, source). Returns chronological events derived from date fields in sources.

Inputs: limit, offset, user_id, end_date, +3 more.

manage_bundlesA

Inspect and manage Neotoma bundles (the deliverable unit shipping schemas, record-type docs, and skills). action=list returns all bundles with type/version/enabled/always_active/provides count; action=info (with bundle) returns full manifest detail; action=install/enable/disable (with bundle) toggle persisted enable state. Default-install bundles (core, infrastructure, core_workflows) are always active and cannot be disabled. Disabling a schema bundle stops its types from auto-creating under guided/locked while preserving existing data. Returns structured JSON.

Inputs: action (required), bundle.

merge_entitiesB

Merge duplicate entities. Rewrites observations from source entity to target entity and marks source as merged.

Inputs: user_id, merge_reason, to_entity_id, from_entity_id.

neotoma_turn_summaryA

FU-2026-05-002. Compute the per-turn Neotoma status line (msg N/M, stored K, retrieved L) plus an optional ui:// widget URI for ext-apps clients. Call at the end of every turn after the closing assistant store completes. Pass the assistant message's conversation_id and turn_key; the server resolves stored/retrieved/issue entities, turn ordinal, and total message count. Agents emit the returned status_line in the user-visible reply; ext-apps clients additionally render widget_uri inline when present.

Inputs: turn_key (required), conversation_id (required), user_id.

npm_check_updateA

Check if a newer npm version is available. Returns updateAvailable, message, suggestedCommand, and when include_release_notes is true optional release_url, release_notes_excerpt, breaking_changes_excerpt, enrichment_error. When include_capability_delta is true, adds new_tools (tools added since currentVersion), removed_tools (tools removed since currentVersion), capability_delta_recommendation (upgrade guidance string), and capability_delta_note (present ONLY when delta computation degraded — e.g. unparseable versions or missing manifest — its absence signals no degradation). Call at session start to encourage user to upgrade.

Inputs: packageName (required), currentVersion (required), distTag, include_release_notes, +1 more.

parse_fileA

Parse local or base64-encoded files into agent-readable text and first-page PDF images without storing anything. Use before store when you need to extract entities from a file.

Inputs: file_path, mime_type, file_content, original_filename.

patch_array_itemA

Atomically patch one item of a structured array field by a portable non-null scalar key, instead of read-modify-full-array-write. Requires the active schema to declare that array with merge_array_by_key and the same key_field; missing/mismatched policy fails closed. Loads current state and writes inside one transaction, so concurrent patches to DIFFERENT keys both survive. Pass expected_item_version to detect a same-key race or expected_item_absent for race-safe creation; a conflict returns the current item instead of overwriting.

Inputs: item (required), field (required), entity_id (required), key_field (required), +6 more.

publish_rendered_pageA

Turn a rendered_page into a ready-to-share guest URL in one call. Pass an existing rendered_page entity_id, or inline {title, html_body, custom_css} to create one. Mints a guest_access_token scoped to that page and returns the absolute …/entities//html?access_token= URL plus ttl_seconds. html_body is injected verbatim into a server template — no html/head/body wrappers. Each call mints a fresh token (tokens are hash-only at rest).

Inputs: title, user_id, entity_id, html_body, +3 more.

query_contacts_at_companyA

Answer "who do we have connected at company X": resolve company_name to the canonical company entity (exact-normalized match first, then a conservative fuzzy pass — the same resolution order used when a contact's organization field auto-links to a company at store time) and return every contact linked to it via a live works_at edge. Read-only: never creates a company entity — when no company matches, returns company: null and an empty contacts list. Optional owner_user_id scopes the search to a specific partner's network (must equal the authenticated user's id today; no cross-tenant admission yet); omit to search the authenticated user's own graph.

Inputs: company_name (required), limit, owner_user_id.

register_relationship_typeA

Register a relationship type after discovering the vocabulary with list_relationship_types. Requires an active grant with register_relationship_type and relationship_types permission; global scope additionally needs explicit global permission. Defaults to user scope. Refresh cached tool definitions after registration, then write the edge and read it back.

Inputs: relationship_type (required), scope, acyclic, inverse, +4 more.

register_schemaC

Register a new schema or schema version. Supports both global and user-specific schemas.

Inputs: entity_type (required), reducer_config (required), schema_definition (required), force, +4 more.

remove_peerC

Deactivate a peer by peer_id.

Inputs: peer_id (required).

resolve_sync_conflictD

Stub; use correct on the entity for manual resolution today.

Inputs: strategy (required), entity_id (required), sender_peer_url, guest_access_token.

restore_entityB

Restore visibility of an entity through an immutable lifecycle observation and expose its current factual reduction, including ordinary facts accepted while hidden. After the explicit authority cutover, serialized action sequence controls visibility; before cutover, existing legacy behavior applies. Each accepted request is a separate action, without an idempotency key or historical beforeimage restoration.

Inputs: entity_id (required), entity_type (required), reason, user_id.

restore_relationshipA

Restore a deleted relationship. Creates an immutable restoration observation that outranks earlier observations. Relationship becomes visible in snapshots and queries again; later explicit deletion remains possible.

Inputs: source_entity_id (required), target_entity_id (required), relationship_type (required), reason, +1 more.

retrieve_entitiesA

Use this when you need filtered entity lists (by type, pagination, and optional published/date filters) or lexical/semantic retrieval via search. Strict mode: search cannot be combined with non-default sorting or published filters. Set include_snapshots=false for lightweight responses that omit snapshot/provenance/raw_fragments. Use snapshot_filters to filter by snapshot field values server-side (e.g. { "status": { "op": "eq", "value": "active" } } for active entities). Compatibility aliases search_query and query are accepted but search is canonical. For deep pagination under the default sort (sort_by=entity_id), pass the next_cursor from a prior response back as cursor instead of a large offset; offset beyond a bounded depth is rejected and cursor is not supported with search, non-default sort_by, or a non-zero offset in the same call.

Inputs: limit, query, cursor, offset, +17 more.

retrieve_entity_by_identifierA

Retrieve entity by identifier (name, email, etc.) across entity types or specific type. Falls back to semantic search when keyword match returns no results. A literal entity_id (ent_) short-circuits to a direct primary-key lookup; if no entity has that id, the response carries a hint pointing to retrieve_entity_snapshot.

Inputs: identifier (required), by, limit, entity_type, +2 more.

retrieve_entity_snapshotB

Retrieve the current snapshot of an entity with provenance information. Supports historical snapshots via 'at' parameter.

Inputs: at, format, entity_id, at_ingested, +1 more.

retrieve_field_provenanceC

Retrieve the provenance chain for a specific field in an entity snapshot

Inputs: field, entity_id.

retrieve_file_urlC

Retrieve a signed URL for accessing a file

Inputs: file_path (required), expires_in.

retrieve_graph_neighborhoodB

Retrieve complete graph neighborhood around a node (entity or source): related entities, relationships, sources, and events.

Inputs: node_id (required), limit, offset, node_type, +4 more.

split_entityA

R5 inverse of merge_entities. Re-points a predicate-selected subset of an entity's observations onto a new or pre-existing entity to repair over-merges (typically the pre-v1.2 heuristic name_key:title collapse on session-scoped types). Schema-agnostic predicate — supply any of observed_at_gte, source_id_in, or observation_field_equals. Observation content is never modified; only entity_id FK is re-bound. Idempotent via (user_id, idempotency_key) — reuse with a different predicate returns ERR_IDEMPOTENCY_MISMATCH. Typed relationships remain bound to the source; rebuild them with create_relationship.

Inputs: predicate (required), new_entity (required), idempotency_key (required), source_entity_id (required), +2 more.

storeA

Unified storing for structured, file-backed, or combined payloads in one request. Choose path by source: file- or resource-sourced (attachment/file to preserve) → use file_content+mime_type or file_path; conversation- or tool-sourced (chat or other MCP) → use entities. You may send both entities and file input in the same call. File bytes create a content-addressed sources row (SHA-256 dedup per user); the response includes source_id / content_hash for the unstructured leg. The server does not invent structured fields from opaque blobs without an explicit interpretation block or a separate interpretation flow. Agents should parse and extract entities first when they need structured data from a readable file, then send those entities alongside the raw file. IMPORTANT FOR STRUCTURED DATA: Include ALL fields from source data. Schema fields go to observations; non-schema fields go to raw_fragments for future schema expansion. Relationships must link entities you own; a refused relationship is reported in relationships_refused rather than failing the call.

Inputs: commit, strict, user_id, entities, +14 more.

submit_entityA

Generic submission for entity types with an active submission_config. Creates the primary row plus optional conversation thread and guest_access_token; use submit_issue for GitHub-backed issues.

Inputs: fields (required), entity_type (required), initial_message.

submit_issueA

Submit an issue to the configured operator Neotoma instance; returns guest_access_token when accepted so callers can use token-scoped get_issue_status / add_issue_message read-back. No prior identity is required: a remote caller with no Bearer token, no AAuth signature and no guest token may submit whenever the operator instance's issue guest access policy permits guest writes — the token is an OUTPUT of the submit, not a precondition for it, so first contact needs no credential. Anonymity covers submission only; use the returned guest_access_token for add_issue_message and get_issue_status. Callers must provide at least one of reporter_git_sha or reporter_app_version. Deprecated alias: visibility 'advisory' is accepted as 'private' for one minor release and returns _deprecation: "visibility 'advisory' is deprecated; use 'private' instead."

Inputs: body (required), title (required), labels, visibility, +8 more.

subscribeA

Create a substrate event subscription (webhook with HMAC delivery, or SSE via GET /events/stream). Requires at least one filter among entity_types, entity_ids, or event_types. Optional sync_peer_id skips webhook delivery when the substrate event carries the same source_peer_id (cross-instance loop prevention).

Inputs: delivery_method (required), entity_ids, event_types, webhook_url, +4 more.

sync_entity_submissionsA

Sync external mirrors for submissions (issue → GitHub sync; other types may no-op until providers are added).

Inputs: entity_type.

sync_issuesA

Sync local Neotoma with a GitHub repo's issues. Pull leg: GitHub issues and their messages are pulled into local entities. Push leg: local public issues with no github_number are sanitized (PII stripped) and created on GitHub, then updated locally with the returned number/url. Target repo defaults to the server-configured repo (NEOTOMA_ISSUES_REPO); pass repo (owner/name) to mirror a different repo for one call without changing server config. The push leg is ON by default only for the configured default repo and OFF for any other repo unless push: true is passed. Pass commit: false for a dry run that reports what would be created, updated and pushed and writes nothing. Requires a GitHub token: NEOTOMA_ISSUES_GITHUB_TOKEN in the server environment (or gh auth login on the server host). Supports filtering by state, labels, and since date.

Inputs: push, repo, since, state, +2 more.

sync_peerC

Bounded outbound peer sync: POST signed /sync/webhook for observations (requires NEOTOMA_PUBLIC_BASE_URL, NEOTOMA_LOCAL_PEER_ID, peer sync_target_user_id). Optional limit.

Inputs: peer_id (required), limit.

unsubscribeC

Deactivate a subscription by subscription_id.

Inputs: subscription_id (required).

update_schema_incrementalA

Incrementally update a schema by adding fields (fields_to_add — minor version bump), removing fields (fields_to_remove — major version bump; observation data preserved, snapshot-excluded until re-added), or changing the identity rule (canonical_name_fields — major version bump). Creates a new schema version and activates it immediately, so all new data stored after this call uses the updated schema. Optionally migrates existing raw_fragments to observations for historical data backfill.

canonical_name_fields re-keys how the type derives canonical_name / identity. Rules are ordered precedence with fallback — the first rule whose fields are all present wins, e.g. [{composite:["linkedin_url"]},"email","name"] keys on linkedin_url, else email, else name. Reach for it when same-name-different-entity collisions appear (e.g. a bulk import collapses distinct people who share a name because identity resolves on name alone). The existing reducer_config is preserved automatically, so this is the safe way to re-key without a full register_schema re-supply. Applies to NEW writes only — it does not retroactively re-key existing entities, so it will not by itself merge existing duplicates. Omit to keep the current rule; pass [] to clear it (succeeds only if the schema also declares identity_opt_out). The response echoes the resolved canonical_name_fields; call describe_entity_type first to see the current rule before replacing it.

Scope: omit user_specific to write to whichever scope your schema resolves to (your user-scoped override if you have one, else global); the response's scope (and user_id when scope is user) reports what was written. Pass user_specific: true or false only to override that. migrated_existing is true only when migrate_existing actually promoted fragments; migration_result.skipped says why any did not. If entity_type is rejected by a naming guard whose message names force: true as the remedy, retry with force: true.

On ERR_SCHEMA_SCOPE_MISMATCH (only after an explicit user_specific: false, when the type has no global registry row and no code-defined baseline) an active schema exists in a different scope than the call checked (details.guard_scope vs details.found_scope). Retry without user_specific, or with user_specific matching details.found_scope. Never call register_schema for that code — a second active row for the same entity_type risks dual-active corruption (#2374/#2378).

Inputs: entity_type (required), force, user_id, activate, +6 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    C2.8/5.0

    Scored across 67 tools

    Disambiguation3/5

    Many tools overlap in purpose: retrieval is split across retrieve_entities, retrieve_entity_by_identifier, identify_entity_by_signals, retrieve_entity_snapshot, retrieve_related_entities, and retrieve_graph_neighborhood; writing/correcting across store, correct, correct_transaction, and patch_array_item; and generic vs issue-specific submission across submit_entity, submit_issue, add_entity_message, and add_issue_message. Detailed descriptions help, but the boundaries still require careful reading and invite misselection.

    Naming Consistency4/5

    Most tools follow a snake_case verb_noun pattern (list_observations, retrieve_entity_snapshot, create_relationship, delete_entity), with reasonable variety among verbs like get, list, and retrieve. Minor deviations include single-word imperative tools (store, correct, subscribe) and noun-first names (neotoma_turn_summary, npm_check_update, health_check_snapshots), but the overall convention remains readable and predictable.

    Tool Count1/5

    With 67 tools, the surface is far beyond the typical 3-15 range and qualifies as an extreme mismatch according to the rubric. Even for a complex knowledge-graph platform, this volume creates selection overhead and makes it hard for an agent to hold the whole toolset in working memory.

    Completeness4/5

    The server covers a broad lifecycle: storing, correcting, deleting, and restoring entities; managing schemas, relationships, observations, provenance, submissions, subscriptions, peers, bundles, and issues. Some gaps remain, such as resolve_sync_conflict being an explicit stub and no direct update_relationship operation, but core workflows are otherwise well supported.

    Maintenance

    ActivityActive
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      A local-first shared memory layer for MCP-aware agents like Claude, Codex, and Hermes, enabling persistent memory across chats and clients via Markdown files and SQLite FTS.
      6
      2
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Local-first, source-traceable memory for AI agents — no LLM at ingest, $0 per message, zero data egress. Gives Claude Code, Cursor, and any MCP client one shared persistent memory with semantic recall, belief revision, selective forgetting, and a provenance guard that blocks acting on stale or unconfirmed memories.
      23
      14
      MIT
    • A
      license
      Not graded
      quality
      F
      maintenance
      Local-first, auditable memory for AI agents. Provides durable context for MCP hosts with SQLite storage, CLI, and MCP tools for memory management.
      2
      Apache 2.0
    • A
      license
      C
      quality
      B
      maintenance
      Local-first, governable long-term memory for AI agents. Provides SQLite-backed storage, cross-session recall, and traceable memory corrections through a standard MCP interface.
      40
      36 PyPI
      1
      MIT