tessera-mcp

tessera-mcp

Enables AI agents to manage a fictional B2B workspace SaaS (Tessera) with tools for ticketing, invoicing, customer management, and trial extensions, featuring a human-in-the-loop confirm pattern for safety.

Category
Visit Server

README

tessera-mcp

A Model Context Protocol (MCP) server that gives an AI agent seven support-ops tools for Tessera, a fictional B2B workspace SaaS. It runs over stdio with a bundled SQLite database — no hosted runtime, no API keys, no LLM calls anywhere in this repo.

Why this exists (60-second pitch). Wiring an LLM to tools is easy; wiring it safely is the hard part. This server demonstrates the pattern I ship for agentic work: typed tools with LLM-readable descriptions, a dry-run/confirm human-in-the-loop gate on every mutation, and a deterministic test suite that proves the tools behave — 20/20, no model in the loop. Point Claude Code at it and watch it run a realistic morning ops workflow: spot a double-billed customer, investigate, refund with approval, rescue a churning trial.


Quickstart

# 1. Create + seed the local database (idempotent — safe to re-run)
npx tessera-mcp --seed

# 2. Register the server with Claude Code
claude mcp add tessera -- npx tessera-mcp

That's it. Claude Code now has the seven tools below. No environment variables required — the server uses a local SQLite file at ~/.tessera-mcp/data.db. The path is home-anchored (not relative to the current directory) so the server finds the same database no matter where the host launches it from, and it auto-seeds on first run if you skip step 1.

Requires Node 20+.

Claude Desktop

Add this to your claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "tessera": { "command": "npx", "args": ["tessera-mcp"] }
  }
}

Claude Desktop launches MCP servers from the filesystem root, so a working-directory-relative database would be empty. This server sidesteps that by using the home-anchored ~/.tessera-mcp/data.db and seeding it on first run — no --seed step required.


The tools

Tool Kind What it does
list_open_tickets({ priority?, limit? }) read Open tickets, worst priority first; optional priority filter + limit
get_customer({ idOrEmail }) read One customer by id or email, with their invoices + tickets summary
search_invoices({ customerId?, status?, minAmount? }) read Filter invoices — spot duplicates, overdue balances, history
daily_summary() read Start-of-day snapshot: open tickets by priority, overdue invoices ($), trials expiring ≤7d
update_ticket_status({ ticketId, status, confirm? }) mutation Move a ticket to open / pending / solved
extend_trial({ customerId, days, confirm? }) mutation Push a trial customer's end date out by N days
issue_refund({ invoiceId, reason, confirm? }) mutation Refund a paid invoice (rejects already-refunded / non-paid)

Every input field carries a description — that text is what the client LLM reads to call the tool correctly. Results come back as concise text tables, not JSON dumps. Invalid ids return clear error strings, never exceptions.


The confirm / dry-run pattern (human-in-the-loop)

MCP has no built-in "approve this action" UI — the protocol hands a tool call straight to your code. So every mutation here is gated by a confirm flag that defaults to false:

  • Without confirm: true the tool changes nothing and returns a preview:
    DRY RUN — would refund INV-1013 ($2,400.00, Atlas Freight) — reason: "double billed".
    Re-call with confirm:true to execute.
    
  • With confirm: true it executes atomically (a single write batch that also appends an audit-log row) and reports what changed:
    Done — refunded INV-1013 ($2,400.00, Atlas Freight). Reason: "double billed".
    

The agent shows the human the dry-run preview, waits for a yes, then re-calls with confirm:true. It turns an irreversible action into a two-step, reviewable one. See DECISIONS.md for why this beats an ungated tool.


Example Claude Code session (illustrative)

Transcript below is an illustration of the intended workflow, lightly trimmed. The deterministic suite (npm run eval) is the actual correctness metric.

You:  Good morning — anything on fire?

Claude:  [calls daily_summary]
  Open tickets: 10 (3 urgent). 4 overdue invoices ($2,847.00). 2 trials expiring within 7 days.
  One thing stands out: Atlas Freight has two identical $2,400 invoices issued the same day.

You:  Dig into Atlas Freight.

Claude:  [calls get_customer({ idOrEmail: "CU-1010" })]
  Atlas Freight (CU-1010), enterprise. INV-1013 and INV-1014 are both $2,400.00,
  both issued Jul 14, 2026 — that's a double charge.

You:  Refund one of them.

Claude:  [calls issue_refund({ invoiceId: "INV-1013", reason: "duplicate charge — double billed" })]
  DRY RUN — would refund INV-1013 ($2,400.00, Atlas Freight). Re-call with confirm:true to execute.
  Want me to go ahead?

You:  Yes.

Claude:  [calls issue_refund({ ..., confirm: true })]
  Done — refunded INV-1013 ($2,400.00, Atlas Freight).

You:  Also, Paperkite's trial is about to lapse and they're angry — give them two more weeks and close their ticket.

Claude:  [extend_trial CU-1003 +14 (dry-run → confirm), then update_ticket_status TK-1003 solved (dry-run → confirm)]
  Done — Paperkite's trial now ends Aug 3, 2026, and TK-1003 is solved.

You:  Re-run the summary.

Claude:  [calls daily_summary]
  Open tickets down to 9 (2 urgent). Overdue unchanged. Trials expiring within 7 days: down to 1.

Eval — the honest metric

npm run eval connects an in-process MCP client to the server over the SDK's linked in-memory transport (no child process, no network, no LLM) and replays 23 scripted cases from data/eval-cases.json against a freshly seeded, isolated database:

  • valid reads, filtered reads, and invalid-id errors
  • each mutation as dry-run and then confirmed
  • post-mutation state assertions (a re-read proves the change landed)
  • a same-status no-op that returns success, not an error
  • direct audit_log assertions (exactly one row per confirmed mutation)
  • refund-twice rejection (a second refund on an already-refunded invoice errors)
  • a concurrency check: two overlapping confirmed refunds race via Promise.all; exactly one wins and exactly one audit row is written (proving the TOCTOU fix)

The eval is isolated — it runs against its own file:eval.db and never inherits TURSO_DATABASE_URL, so npm run eval can never touch your real or hosted data.

Because there is no model in the loop, the suite is fully deterministic: the gate is 100%. Anything less is a real bug and the process exits non-zero.

23/23 cases passed.
All 23 eval cases + concurrency check passed (deterministic gate: 100%).

Configuration

Zero config by default. Optional environment variables:

Variable Effect
TURSO_DATABASE_URL Use a hosted libSQL/Turso database instead of the default ~/.tessera-mcp/data.db
TURSO_AUTH_TOKEN Auth token for the hosted database above
TESSERA_NOW Override the demo's fixed reference clock (ISO 8601); defaults to 2026-07-16T12:00:00Z so the eval stays deterministic. An invalid value is ignored with a stderr warning
EVAL_DB_URL Database the eval uses (default file:eval.db). The eval ignores TURSO_DATABASE_URL entirely so it can never wipe real data
EVAL_ALLOW_REMOTE Set to 1 to allow the eval to target a remote (non-file:) URL; otherwise remote eval URLs are refused

CLI flags: --seed (create + seed, then exit), --db <path> (use a specific SQLite file), --help. The default database is ~/.tessera-mcp/data.db, auto-seeded on first run.

See env.example. This project never reads or writes .env files.


The Tessera universe

Tessera is a fictional B2B workspace SaaS used across a family of portfolio demos. Sibling repos:

All data here is synthetic. See DECISIONS.md for architecture rationale and VIDEO-SCRIPT.md for the demo narration.

License

MIT

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
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
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
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
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
E2B

E2B

Using MCP to run code via e2b.

Official
Featured