chist

chist

MCP server for unified full-text search across chat histories from Claude Code, Codex, Cursor CLI, and Antigravity CLI, using SQLite FTS5. Provides read-only tools to search sessions, list conversations, and retrieve session details.

Category
Visit Server

README

chist

License: MIT Python MCP

日本語版は README.ja.md

Unified full-text search across your Claude Code, Codex, Cursor CLI, and Antigravity CLI chat history. Ingests every transcript into one SQLite database and lets you search it from a web UI, an MCP server, or the command line.

Ingestion and search run on the Python 3.9 standard library — zero dependencies. Only the MCP server needs an external package.

python3 -m chist ingest              # incremental import
python3 -m chist search 'trigram'    # full-text search
python3 -m chist show <ext_id>       # read a conversation

Why

Chat history is scattered: each CLI keeps its own format in its own directory, and none of them can see the others. When you already solved something months ago in a different tool, you can't find it. chist normalizes all four into one schema and one index, so a single query reaches everything — and project_key (derived from the git remote) groups the same project across different machines and different tools.

Features

  • Four sources, one index — JSONL for Claude Code / Codex / Cursor CLI, and raw protobuf-in-SQLite for Antigravity CLI (decoded without a .proto definition)
  • Incremental ingest — unchanged files are skipped by mtime+size; changed ones resume from a byte offset, verified against a hash of the first 4 KB
  • CJK-capable search — FTS5 with the trigram tokenizer, so Japanese substrings match (the default unicode61 tokenizer cannot)
  • Web UI — Svelte SPA with search, session browsing, and statistics
  • MCP server — lets an agent query its own history (search_history, list_sessions, get_session)
  • Multi-machine aggregation — each machine ingests locally and pushes deltas to a central server over HTTP
  • Secret masking — credentials are masked on display, and always before storage on the aggregation server

Measured performance

macOS, 1,034 files / ~1.86 GB of transcripts:

Metric Value
Initial ingest (all files) 14.9 s
Second run (no changes) < 0.1 s
Database size 130 MB (including the FTS index)
Search latency ~0.05 s
source messages sessions
claude-code 49,263 769
codex 1,488 10
cursor-cli 410 9
antigravity-cli 225 3

Quick start (local, no Docker)

git clone https://github.com/you/chist-server.git
cd chist-server
python3 -m chist ingest
python3 -m chist search 'docker compose'

The database defaults to ~/.local/share/chist/history.db (mode 600). Override it with --db or CHIST_DB.

Options

search  -s/--source  filter by source (repeatable)
        --host       filter by machine (repeatable)
        -c/--cwd     substring match on the project path
        -r/--role    user | assistant | system | tool
        --since / --until   ISO 8601 (e.g. 2026-07-01)
        -n/--limit   default 20
        --raw        do not mask secrets
show    --thinking   include thinking blocks
ingest  --limit N    cap the number of files (for smoke tests)
prune   --before / --after  time range (ISO 8601)
        -s/--source  by source
        --session    by session (ext_id or id)
        --match      substring of the body (to undo a bad import)
        --dry-run    report counts without deleting
        -y/--yes     skip confirmation (required non-interactively)
        --vacuum     reclaim file size after deleting

Pruning history

Ingest is append-only, so the database only grows. Measured at 3.8–5.9 KB per message (the trigram index alone is ~1.9× the body), which is roughly 150–300 MB per year in daily use. Capacity is rarely the issue — the real need is removing something imported by mistake, or a conversation you don't want kept.

chist prune --before 2026-01-01 --dry-run     # check the count first
chist prune --before 2026-01-01 --yes --vacuum
chist prune --match 'password' --dry-run      # undo a bad import
chist prune --session <ext_id> --yes

Conditions combine with AND. With no condition it does nothing — deleting everything is deliberately not offered (remove the database file instead).

On the aggregation server, run the same CLI inside the container:

docker exec chist-server python -m chist prune --before 2026-01-01 --dry-run

Notes:

  • Sources without timestamps (Cursor CLI) are still covered by time ranges — the session's end time is used as a fallback, so nothing is silently skipped
  • Without --vacuum the file does not shrink (space is only reused). VACUUM rewrites the database, so it needs free space equal to its size
  • Sessions left with no body are removed too (--keep-sessions keeps them). Empty sessions outside the delete set are never touched
  • If the original log files still exist and you clear ingest_state, a re-ingest brings the messages back. Delete the source files to remove them for good
  • No prune tool is exposed over MCP — the tools are declared read_only_hint, and a mistaken deletion cannot be undone, so deletion stays in the CLI

Server deployment

For multiple machines, run the aggregation server in Docker behind an existing reverse proxy. Each machine ingests locally and pushes only the delta.

cp .env.example .env      # CHIST_TOKEN, CHIST_HOST, CHIST_BASICAUTH, …
docker compose up -d --build
# on each machine (still dependency-free)
export CHIST_TOKEN=<same token>
python3 -m chist ingest
python3 -m chist push https://chist.example.com

The compose file reads every environment-specific value from .env, publishes no host port, and joins the proxy's network directly. The Svelte UI is built in a node:22-alpine stage and baked into the image, so the server does not need Node installed. See docs/deploy.md.

Web UI

Three tabs — search, sessions, statistics. Opening a URL with ?q=<query> runs that search immediately, and #search / #sessions / #stats select the tab, so results are shareable as links. Light/dark themes follow the OS setting by default.

Filtering by machine is available from the web UI, the CLI (--host), and MCP. In the web UI the machine filter and column appear only when two or more machines have pushed — with a single machine they stay hidden. The list of machines is read from the hosts table at runtime, so adding a machine needs no configuration.

MCP server

claude mcp add --transport http chist https://chist.example.com/mcp

Implements MCP 2026-07-28 over stdio or streamable HTTP, read-only and stateless. Tools: search_history, list_sessions, get_session. See docs/mcp-setup.md.

For stdio use, the SDK requires Python 3.10+:

uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python mcp
.venv/bin/python -m chist.mcp_server

Only chist/mcp_server.py imports mcp. Ingest, search, and the CLI stay dependency-free on the system Python.

Tests

Standard-library unittest only — the zero-dependency rule holds for the tests too.

python3 -m unittest discover -s tests -t .

Server-side tests need the MCP SDK and skip automatically without it. To run everything, use the image:

docker run --rm -v "$PWD/tests":/app/tests:ro -w /app chist:latest \
  python -m unittest discover -s tests -t /app

The suite covers behaviour that has actually broken, or could:

  • masking detects secrets and leaves ordinary prose alone (labels with no value, short placeholders, everyday commands)
  • session titles are masked, not just bodies — Cursor and Antigravity derive titles from the first user message
  • the same ext_id under two sources is ambiguous, and source resolves it
  • the 3-character trigram floor, and that FTS special characters never raise
  • prune refuses to run without a condition, does not sweep up already-empty sessions, covers sources with NULL timestamps, and keeps the FTS index in step
  • foreign keys and ON DELETE CASCADE; deduplication via UNIQUE (source, ext_id, part)
  • authentication (match, mismatch, and deny-all when no token is configured), the 64 MiB body cap, and malformed payloads

Security model

Never expose the app directly. Two route groups cannot authenticate themselves:

Path Why Protection
/mcp routed by the MCP SDK, bypasses the app's bearer check proxy forwardAuth → /auth
/ /api/* browsers cannot send a bearer token proxy basicauth
/ingest /stats /auth bearer token in the app
/health unauthenticated by design (health checks)

The shipped docker-compose.yml wires this up as three Traefik routers with an IP allowlist on each. The server refuses to start without CHIST_TOKEN, so there is no path to running it unauthenticated.

Secret masking

The local database stores raw text and masks on display (--raw disables it). The aggregation server masks before storing, so push --raw still cannot put live credentials into the server database.

Detected: known prefixes (sk-, ghp_, AKIA, AIza, Bearer, JWTs, PEM private keys); labelled values — TOKEN=… plus password: / token: / secret: / api key: / access key: and their Japanese equivalents, with the full-width colon accepted; and credential-bearing commands (curl -u user:pass, htpasswd -nbB user pass).

A bare random string cannot be masked. With no label and no prefix there is nothing to key on, and entropy-based detection produces too many false positives to be usable. Write secrets with a label (password: …) and they will be masked.

Masking is best-effort, not a guarantee. It always covers message bodies and session titles (titles matter: Cursor CLI and Antigravity derive them from the first user message, so a prompt like "deploy with TOKEN=…" would otherwise leak through the title). It still has limits:

  • bare random strings, as above
  • cwd / git_branch / project_key are not masked — filtering and cross-machine project grouping depend on them. Keep secrets out of paths
  • the local database stores bodies verbatim and masks on display

Treat the database file itself as a secret. The local database is 0600; the server keeps it inside a docker volume. Back it up on the same assumption. "Masking exists, so the database is safe" does not hold.

Notes and limitations

  • Queries shorter than 3 characters fall back to LIKE. The trigram tokenizer cannot MATCH fewer than 3 characters; the CLI and UI switch automatically and say so.
  • tool_call / tool_result are stored but not indexed. Large diffs and file dumps drown out the signal. They are over 70% of all rows.
  • Resumed sessions keep their original uuid. Claude Code copies prior history into a new file on resume, so UNIQUE (source, ext_id, part) keeps only one copy. Search never shows duplicates; show <resumed-id> may be missing the earlier part.
  • Cursor CLI has no timestamps or model names. ts is NULL and session spans fall back to file mtime.
  • Codex reasoning is usually encrypted. Only plaintext summary blocks are kept.
  • Antigravity CLI is decoded without a .proto. Field numbers were identified from real data, so a format change could silently drop messages — watch chist stats for unexpected drops.

Documentation

File Contents
docs/deploy.md Server deployment and multi-machine aggregation
docs/mcp-setup.md Registering the MCP server with each tool
db/schema.sql Schema of record, with design notes

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured