Skip to main content
Glama

kardex

Google Sheets for AI agents, as an MCP server.

Agents are good at reasoning and bad at bookkeeping. Give one a CSV and it will lose rows, invent columns, misspell a name and then fail to find it, and overwrite what another agent just wrote. kardex is a small SQLite-backed store where every sheet has a typed, described schema, every row has a stable id and a version, every write is logged with its author, and lookups are fuzzy with an explicit match status. It ships as a Model Context Protocol server, so any MCP client can use it: Claude Code, Claude Desktop, Cursor, or your own agent.

you:    Track vendor invoices. Add INV-001 Acme 1200 due 2026-09-30 unpaid, INV-002 Globex 450 paid, ...
        then tell me what's outstanding and who we owe most.

agent:  kardex_create_sheet  → "Vendor Invoices" (invoice_number, vendor, amount, due_date, status: unpaid|paid|...)
        kardex_append_rows   → 5 rows
        kardex_run_sql       → SELECT vendor, SUM(amount) ... WHERE status = 'unpaid'
        "Outstanding: $4,500 across 4 invoices. Largest creditor: Initech ($2,200)."

Why not just a database or a spreadsheet?

  • Schema is data. Column descriptions, allowed options and defaults are stored with the sheet and returned by kardex_describe_sheet, so the next agent knows how to fill a column without being told.

  • Names are messy. kardex_resolve_values and kardex_find_rows match across case, accents, spacing, punctuation and common misspellings, and return a status: exact, unique_fuzzy, ambiguous or none. A fuzzy hit is never silently treated as exact.

  • Rows are not entities. A key column, per-key counts and latest_by make event-log style sheets (one row per status change) answerable without double counting.

  • Writes are safe. Optimistic versions on single-row updates; bulk updates and deletes default to a dry run and then require the expected match count; empty filters are refused; the SQL tool is read-only.

  • Everything is auditable. kardex_history shows who changed what, with before and after snapshots.

Related MCP server: DataBook

Quick start

Requirements: Python 3.10+ and uv (or pip).

git clone https://github.com/gauravhans8/kardex.git && cd kardex
uv venv && uv pip install -e ".[mcp]"        # or: python -m venv .venv && .venv/bin/pip install -e ".[mcp]"
.venv/bin/kardex-mcp --help

The server speaks MCP over stdio. It takes one option, --db PATH (or the KARDEX_DB environment variable), and creates the SQLite file on first start.

Claude Code

Put a .mcp.json in any project folder, using absolute paths:

{
  "mcpServers": {
    "kardex": {
      "command": "/absolute/path/to/kardex/.venv/bin/kardex-mcp",
      "args": ["--db", "/absolute/path/to/your/sheets.db"]
    }
  }
}

Open Claude Code in that folder, approve the project server when prompted, and ask: "What sheets do you have?" or "Create a sheet to track candidates." Several folders can share one database, or each can have its own.

Claude Desktop, Cursor and other clients

Same shape. Add the block above under mcpServers in the client's MCP configuration file (for Claude Desktop, claude_desktop_config.json).

From Python, without MCP

from kardex import SheetStore

store = SheetStore("sheets.db")
store.create_sheet("Hiring Pipeline", [
    {"name": "candidate", "type": "text", "required": True, "description": "Full name"},
    {"name": "email", "type": "text", "description": "Identifies the person"},
    {"name": "stage", "type": "select", "options": ["applied", "screen", "offer", "hired"], "default": "applied"},
], key_column="email")
store.append_rows("Hiring Pipeline", [{"candidate": "Jane Doe", "email": "jane@x.com"}], actor="me")
store.query("Hiring Pipeline", where={"stage": "applied"})
store.resolve_values("Hiring Pipeline", "candidate", "jane do")     # -> status: unique_fuzzy

The core package has no dependencies beyond the standard library.

Tools

All tools are prefixed kardex_. Every result is one envelope: ok, resolved (the sheet and columns actually used), data, warnings (facts about the data that may change the meaning of an answer), suggestions (did-you-mean candidates), and total / returned / truncated / next_offset on lists. Failures the agent can fix come back as isError with error.code in not_found, validation, conflict, refused, read_only.

Discover

Read

Write

kardex_list_sheets (with hygiene flags)

kardex_query_rows (filter, sort, page, latest_by)

kardex_create_sheet

kardex_resolve_sheet

kardex_count_rows (rows or distinct keys)

kardex_add_column

kardex_describe_sheet (schema + column stats)

kardex_get_row (with history)

kardex_update_column (also renames)

kardex_column_values

kardex_run_sql (SELECT only, norm() for text)

kardex_annotate (descriptions, aliases, status, key column)

kardex_resolve_values (match status)

kardex_history

kardex_append_rows (warns on duplicate keys)

kardex_find_rows (search all text columns, grouped by entity)

kardex_update_row (expected_version)

kardex_find_duplicates (exact, normalized, fuzzy)

kardex_update_rows / kardex_delete_rows (dry_run, expected_count)

Column types: text, number, integer, boolean, date, datetime, select, json. Values are coerced leniently on write ("3" becomes 3, "yes" becomes true) and rejected with a specific message when they cannot be.

Filters are ANDed conditions in any of these shapes:

{"stage": "offer"}                       equality          {"stage": ["offer", "hired"]}   IN
{"email": null}                          IS NULL           [["score", "gte", 4], ...]      triples
"candidate~~gourav hans"                 fuzzy             "name~=renee dubois"            normalized equality

Operators: eq ne gt gte lt lte in not_in contains starts_with is_null is_not_null normalized_eq fuzzy. For OR, joins and aggregates use kardex_run_sql; tables are named by sheet slug.

How it is built

  • Storage. One SQLite file in WAL mode. Meta tables _sheets, _columns, _changes hold schema and history as data; each sheet's rows live in their own table with system columns _id, _version, _created_at, _created_by, _updated_at, _updated_by. A deterministic norm(text) SQL function provides normalized comparison. Schema version is tracked and migrated on open.

  • Matching. kardex/normalize.py: NFKC, strip zero-width and control characters, casefold, strip diacritics, collapse punctuation and whitespace. Scoring tiers: exact, normalized, token set, prefix, Jaro-Winkler fuzzy, phonetic. Status rules: one exact hit is exact; one fuzzy hit clearly ahead of the next is unique_fuzzy; two within 0.10 are ambiguous; nothing above threshold is none.

  • MCP layer. kardex_mcp/server.py on the official Python mcp SDK (2.x, protocol 2026-07-28 and legacy clients). Tool descriptions and server instructions are in kardex_mcp/descriptions.py and are tuned against the eval below. Every tool has read-only, destructive and idempotent annotations and a strict input schema. Warnings state facts; guidance on what to do with them lives in the descriptions.

  • Design documents. docs/mcp-plan.md explains which agent mistakes each tool exists to prevent. MCP_SERVER_GUIDELINES.md is the checklist the server was built against.

Development

uv pip install -e ".[dev]"
.venv/bin/python -m pytest                       # 48 tests: store, normalization, MCP layer, trap suite
.venv/bin/python examples/hiring_demo.py         # a scripted multi-agent session against the Python API
.venv/bin/python evals/run_traps.py              # model-in-the-loop eval (spends tokens, see below)

The trap suite

Agents fail in predictable ways: wrong sheet, wrong spelling, counting rows as people, treating one fuzzy hit as certain, bulk-editing with a filter that matched too much. tests/fixtures/traps.py seeds a database that contains every one of those traps. tests/test_traps.py drives the tool path deterministically. evals/run_traps.py runs the real server under a headless Claude Code session (claude -p) per question, grades the tool calls and the answer, and writes transcripts to evals/out/. Use --suite happy for the create, load, query and schema-evolution flows, --only <case> for one case. A full run is ten cases and about two dollars of tokens; at the time of writing all ten pass.

Layout

kardex/        core library (store, types, filters, normalize, profile, search)
kardex_mcp/    MCP server (server, descriptions)
tests/              pytest suite and the trap fixture
evals/              model-in-the-loop runner
examples/           demo script
docs/               design plan

Status and roadmap

Works for single-host, multi-agent use today. Not yet done:

  • Postgres backend behind the same SheetStore interface for multi-tenant deployments.

  • Hardened HTTP transport (auth, origin checks). The --transport streamable-http flag exists but is unauthenticated; do not expose it beyond localhost.

  • Idempotency keys on kardex_append_rows so client retries cannot double insert.

  • FTS5 acceleration for sheets beyond tens of thousands of rows.

  • CSV import and export over MCP (available in the Python API).

License

No license file yet. Add one before publishing.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to manage and query SQLite databases through MCP tools, supporting CRUD operations, schema management, and saved views.
    4 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables coding agents to store, recall, and manage persistent memory across sessions through MCP tools, with reviewable handoffs and SQLite-backed local storage.
    1
    MIT