Skip to main content
Glama
saluca-labs

tartarus-mcp

by saluca-labs

tartarus-mcp

tartarus-mcp

Local-first MCP memory server. Persistent, searchable memory for any AI agent.

Built on the Apache-2.0 Asphodel memory core, vendored in this repo. Zero cloud dependencies. SQLite-backed.

How it works

One tools/call from the client is one handler branch in src/index.ts, one method on the vendored core or the profile store, and one SQLite statement against a single local file.

flowchart TD
  client["MCP client: Claude Code, Cursor, Windsurf, ..."] -- "tools/call over stdio" --> handler["src/index.ts<br/>CallToolRequestSchema handler"]
  handler -- "memory_remember" --> remember["Asphodel.remember()<br/>store.ts"]
  handler -- "memory_recall" --> recall["Asphodel.recall()"]
  handler -- "memory_search" --> search["Asphodel.search()"]
  handler -- "memory_forget, memory_list" --> forget["Asphodel.forget(), Asphodel.list()"]
  handler -- "profile_get, profile_update" --> profile["ProfileStore.get(), ProfileStore.update()<br/>src/profile.ts"]
  remember -- "no topics given" --> topics["extractTopicsLocal()<br/>topic.ts"]
  remember -- "findByContent(), then insert()" --> adapter["SQLiteAdapter<br/>adapters/sqlite.ts"]
  recall --> adapter
  search --> adapter
  forget --> adapter
  adapter -- "insert, delete, list" --> memories[("memories table")]
  adapter -- "recall: topic_index JOIN memories" --> topicIndex[("topic_index table")]
  adapter -- "search: memories_fts MATCH" --> fts[("memories_fts, FTS5")]
  memories -- "triggers mem_ai, mem_ad" --> fts
  profile -- "one row, id = 1" --> agentProfile[("agent_profile table")]

All four tables live in the one file named by TARTARUS_DB. Two things the vendored core can do are not reached from any tool: Asphodel.hybridSearch() (vector search, rank fusion, decay) is never called, and SQLiteAdapter is constructed without vectorDims, so no vector table is created. memory_remember returns the existing memory, with isDuplicate: true, when identical content is already stored.

Related MCP server: heropen

Status

Maintained. Version 0.4.0 (package.json). The server speaks MCP over stdio and exposes seven tools: five memory tools and two agent-profile tools. CI on Ubuntu and Windows builds it, runs the vendored core tests, and runs a smoke test that starts the real server and calls every tool.

Not published to npm or any other registry; install from source (see Distribution).

Correction, 2026-10-22: this README said install "configures Claude Code". It writes an mcpServers entry to ~/.claude/settings.json, and Claude Code does not read MCP servers from that file: its user-scope servers live in ~/.claude.json (written by claude mcp add) and project servers in .mcp.json (Claude Code MCP docs). For Claude Code, run the claude mcp add line that install prints. The Cursor and Windsurf entries are unaffected.

Install

Tested with Node.js 24 and npm 11 on Windows; CI uses Node.js 22 on Ubuntu and Windows (engines says >=18).

Build from source

tartarus-mcp is not published to npm or any other package registry. Build it from this repository, pinned to a reviewed commit:

git clone https://github.com/saluca-labs/tartarus-mcp.git
cd tartarus-mcp
git checkout d21ea0fc72dd4b1b348ce6f4d6f6090e54ffd152
npm ci --ignore-scripts=false
npm run build
node dist/index.js install

install writes an MCP entry for Cursor and Windsurf (and for Claude Code at ~/.claude/settings.json, which Claude Code does not read; see the correction above) that runs node /absolute/path/to/tartarus-mcp/dist/index.js, so keep the checkout where it is. It also prints the equivalent claude mcp add line for the Claude Code CLI; use that line for Claude Code.

--ignore-scripts=false is needed only if your npm config sets ignore-scripts=true: the better-sqlite3 dependency compiles or downloads its native binding in an install script.

Manual setup

After building, add this to your MCP settings, using the absolute path of your checkout:

{
  "mcpServers": {
    "tartarus": {
      "command": "node",
      "args": ["/absolute/path/to/tartarus-mcp/dist/index.js"]
    }
  }
}

Without a local checkout

npm can fetch and build the pinned commit straight from GitHub. This does not use the npm registry for tartarus-mcp itself; its third-party dependencies still come from npm.

{
  "mcpServers": {
    "tartarus": {
      "command": "npx",
      "args": ["-y", "--ignore-scripts=false", "github:saluca-labs/tartarus-mcp#d21ea0fc72dd4b1b348ce6f4d6f6090e54ffd152"]
    }
  }
}

The first start takes about 30 seconds while it builds. Pin a full commit SHA, never a branch.

Agent quickstart

For an agent setting this up, after the build above. Replace /absolute/path/to/tartarus-mcp with the checkout's absolute path; install prints this exact line with the path filled in.

Claude Code (user or project scope is set with claude mcp add --scope):

claude mcp add tartarus -- node "/absolute/path/to/tartarus-mcp/dist/index.js"

With a non-default database file, pass TARTARUS_DB through Claude Code's -e option:

claude mcp add tartarus -e TARTARUS_DB=/path/to/memory.db -- node "/absolute/path/to/tartarus-mcp/dist/index.js"

Any client that reads an mcpServers block (Cursor's mcp.json, Windsurf's mcp_config.json, a project .mcp.json):

{
  "mcpServers": {
    "tartarus": {
      "command": "node",
      "args": ["/absolute/path/to/tartarus-mcp/dist/index.js"],
      "env": { "TARTARUS_DB": "/path/to/memory.db" }
    }
  }
}

env is optional; without it the database is ~/.tartarus/memory.db. Once connected, call profile_get at the start of a session and memory_search before asking the user something they may already have said. The seven tool names in Usage are the whole surface: there are no resources or prompts.

Quickstart

After npm run build, scripts/demo-tools.mjs starts the built server over stdio the way an editor does, against a throwaway database, lists its tools and makes one store-and-search round trip:

node scripts/demo-tools.mjs

Output from a run on 2026-10-22:

MCP client -> node dist/index.js (stdio)
server: tartarus-mcp 0.4.0

tools/list:
  memory_remember(content, topics?)
  memory_recall(topic, limit?)
  memory_search(query, limit?)
  memory_forget(id)
  memory_list(limit?, offset?)
  profile_get()
  profile_update(patch, replace?)

tools/call memory_remember {"content":"The user prefers SQLite for local tools","topics":["preferences"]}
  -> id 1, topics ["preferences"]
tools/call memory_search {"query":"SQLite"}
  -> id 1: The user prefers SQLite for local tools

npm run smoke runs the longer check CI uses: every tool, including profile merges and forget.

Usage

Tools

Tool

Description

memory_remember

Store a memory (topics auto-extracted)

memory_recall

Retrieve memories by topic

memory_search

Full-text search across all memories

memory_forget

Delete a memory by ID

memory_list

List recent memories

profile_get

Read the agent profile (empty {} on a fresh install)

profile_update

Merge changes into the agent profile

Agent profile

Memory answers what happened. The profile answers who am I working with, and how do we work - the standing facts an agent would otherwise re-derive every session, or re-ask about.

It starts empty and the agent fills it in over time:

// profile_get on a fresh install
{ "profile": {}, "updated_at": null, "revision": 0 }

// profile_update { "patch": { "user": { "name": "Ada" }, "tone": "terse" } }
{ "profile": { "user": { "name": "Ada" }, "tone": "terse" }, "revision": 1, ... }

// profile_update { "patch": { "user": { "role": "engineer" }, "tone": null } }
{ "profile": { "user": { "name": "Ada", "role": "engineer" } }, "revision": 2, ... }

Merge semantics, because this is the part that is easy to get wrong:

  • objects merge recursively, so setting one field keeps its siblings

  • null deletes a key - the only way to remove one

  • arrays replace wholesale; positional merging of lists is never what a caller means

  • "replace": true swaps the whole document, for a deliberate reset

What goes where. Durable facts about the person, the project or the working agreement belong in the profile. Things that happened belong in memory_remember. The distinction matters because memories decay and compete for recall, while the profile is one small document meant to be read in full at the start of a session.

The profile lives in its own table in the same SQLite file, so memory_forget cannot delete part of an identity by id and decay cannot quietly age out a working agreement.

Config

TARTARUS_DB=/path/to/memory.db  # default: ~/.tartarus/memory.db

Architecture

Two processes and one file. The server process is the only writer; install is a separate one-shot run of the same entry point that edits editor config files and exits.

flowchart LR
  subgraph clientProc["MCP client process"]
    client["editor or agent"]
  end
  subgraph serverProc["server process"]
    index["node dist/index.js<br/>src/index.ts, FSL-1.1-ALv2<br/>Server + StdioServerTransport"]
    profile["src/profile.ts<br/>ProfileStore"]
    subgraph vendor["vendored, Apache-2.0"]
      core["src/vendor/asphodel/store.ts<br/>Asphodel"]
      sqlite["adapters/sqlite.ts<br/>SQLiteAdapter"]
    end
  end
  subgraph disk["Local disk"]
    db[("TARTARUS_DB<br/>default #126;/.tartarus/memory.db")]
  end
  subgraph installRun["node dist/index.js install (one-shot)"]
    runInstall["runInstall()"]
  end
  client -- "stdio: tools/list, tools/call" --> index
  index --> core
  core --> sqlite
  sqlite -- "better-sqlite3 connection 1" --> db
  index --> profile
  profile -- "better-sqlite3 connection 2" --> db
  runInstall -- "adds mcpServers.tartarus if the directory exists" --> cfgClaude["#126;/.claude/settings.json<br/>not read by Claude Code"]
  runInstall -- "same" --> cfgCursor["Cursor mcp.json"]
  runInstall -- "same" --> cfgWindsurf["#126;/.codeium/windsurf/mcp_config.json"]
  runInstall -- "prints" --> line["claude mcp add tartarus -- node ..."]
  • src/index.ts: the install command, the tool definitions and the MCP request handlers.

  • src/vendor/asphodel/: the vendored memory core (Asphodel, SQLiteAdapter, topic extraction, full-text search through SQLite FTS5). Its NOTICE lists which files were changed from upstream.

  • src/profile.ts: the agent profile, one JSON document in its own table of the same database.

  • scripts/: check-package-meta.mjs (licence, notices and README pin), smoke-mcp.mjs and smoke-install.mjs (runtime checks used by CI), demo-tools.mjs (the quickstart above).

Limits fixed in the code, not configurable from a tool call: memory_recall and memory_search default to 10 results and memory_list to 20 (store.ts, src/index.ts); a memory keeps at most 10 topics (store.ts, DEFAULT_MAX_TOPICS); and each topic word indexes at most the 10 newest memories, so memory_recall on a busy topic drops the oldest from that topic while memory_search still finds them (adapters/sqlite.ts, maxMemoriesPerTopic).

In this repo and not in this repo

Layer

In this repo

Licence

Where it lives

MCP server, install command, agent profile

yes

FSL-1.1-ALv2

src/index.ts, src/profile.ts

Asphodel memory core (0.4.0 line)

yes, vendored

Apache-2.0

src/vendor/asphodel/, origin commit in its NOTICE

Earlier Asphodel library line (@saluca/asphodel 0.3.0)

no

see that repo

saluca-labs/elysium

Hash-chained audit, multi-tenant isolation, team memory

no

not open source

asphodel.ai

Screenshots

Terminal output of node scripts/demo-tools.mjs: server tartarus-mcp 0.4.0, the seven tools from tools/list with their parameters, then a memory_remember call returning id 1 and a memory_search for SQLite returning it

Captured 2026-10-22 from a clean clone by running node scripts/demo-tools.mjs; the exact text is in docs/img/tools-list-2026-10-22.txt.

Citation

No archived record yet.

License

Functional Source License 1.1, Apache 2.0 Future License (FSL-1.1-ALv2), see LICENSE. Each version becomes available under the Apache License 2.0 on the second anniversary of its release. Copyright Saluca LLC.

The vendored Asphodel core in src/vendor/asphodel/ (and its tests in tests/vendor/asphodel/) is licensed under the Apache License 2.0, not FSL; see its LICENSE and NOTICE, which record the origin commit.

Versions 0.2.0 and earlier were published to npm with an Apache-2.0 licence field, and those releases remain available under Apache 2.0.

GitHub shows this repository's licence as "Other" because its licence detector does not include the FSL; the SPDX identifier is FSL-1.1-ALv2.

Distribution

npm is no longer a distribution channel. On 2026-09-16 Saluca removed all of its packages from npm, including tartarus-mcp and @saluca/asphodel, which tartarus-mcp used to depend on. That is why the Asphodel core is vendored here, why package.json is "private": true, and why the npm publish workflow was removed. Install from source as described above. Do not install a package named tartarus-mcp from npm: it is not ours.

  • Model Context Protocol

  • elysium: an earlier line of the Asphodel memory library (@saluca/asphodel 0.3.0, not the 0.4.0 vendored here)

  • Enterprise: hash-chained audit trails, multi-tenant isolation, compliance controls, and team memory at asphodel.ai

  • llms.txt: a link map of this repo for AI agents

Last updated: 2026-10-22

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A local-first MCP server for durable agent memory using SQLite and FTS5, enabling knowledge graph storage, search, and recall for AI agents.
    20
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    A local memory server for AI agents that stores and retrieves information via MCP, keeping all data in SQLite on your machine.
    204 PyPI
    1
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    A local MCP memory server for AI agents to manage memory with evidence-backed, versioned mutations, using SQLite with FTS5 for storage and search.
    6
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Local-first MCP memory server providing persistent, versioned, queryable memory for AI agents using JSON categories and SQLite FTS5, with optional fleet sync.
    2
    Apache 2.0