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.
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: truethe 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: trueit 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_logassertions (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:
- docs-chat — RAG over Tessera's docs
- tessera-ops-agent — the ops dashboard + copilot this data is modeled on
- tessera-invoice-inbox — invoice-processing workflow
All data here is synthetic. See DECISIONS.md for architecture rationale and VIDEO-SCRIPT.md for the demo narration.
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。