Skip to main content
Glama
nickfabiano

Sprout & Stem

by nickfabiano

Sprout & Stem — a customer-service agent with layered guardrails

A working miniature of a production AI customer-service system, built as a learning project: a fictional plant nursery whose support agent can look up orders, change addresses, cancel orders, issue refunds, and escalate to a human — with every dangerous capability constrained by guardrails placed deliberately in the layer where they can't be talked around.

Live demo: the same agent is embedded in a mock storefront (index.html + api/chat.py, deployed on Vercel). Try to talk it into a $75 refund — you can't.

Architecture

flowchart LR
    subgraph clients [Three MCP clients, one server]
        A[Terminal agent<br/>agent/main.py]
        B[Claude Code]
        C[Web demo<br/>api/chat.py + index.html]
    end
    S[MCP server<br/>server/main.py<br/>6 tools + business rules]
    D[(orders.json)]
    A -->|MCP / stdio| S
    B -->|MCP / stdio| S
    C -->|direct import,<br/>same functions & schemas| S
    S --> D

The business rules live in one place — the tool functions in server/main.py. The terminal agent and Claude Code reach them over the MCP wire protocol (stdio); the serverless web demo imports the same functions and derives its Anthropic tool schemas from the same FastMCP registry. One source of truth, three clients.

Related MCP server: ToolBridge

The guardrail hierarchy

Guardrail

Layer

Why this layer

No payment data anywhere

Data

The strongest guardrail is data that doesn't exist — nothing to leak

Identity: email must match the order record

Code (tool)

The tool checks against ground truth; the prompt merely also asks

$50 single-refund cap

Code (tool)

Must survive any conversation; "the policy changed" can't change the code

Cumulative refunds ≤ order total

Code (tool)

Blocks double-dipping via installments

Max 2 refunds / customer / calendar month

Code (tool)

Frequency abuse; calendar month chosen for simplicity (known edge documented)

Refunds go only to the original payment method

API shape

The tool has no destination parameter — a capability not exposed is a rule that can't break

Confirm-before-write

Harness gate

A human keystroke/click approves every write; the model cannot produce that input

Tone, verification etiquette, when to escalate

Prompt

Judgment calls belong to the model; mechanical rules don't

The design rule that fell out of building this: a check is only as strong as the trustworthiness of whoever supplies its input. A confirmed: true tool parameter would be filled in by the model — the untrusted party attesting to its own compliance — so confirmation lives in the harness, where only a human can provide it.

Escalation (escalate_to_human) is deliberately ungated and unverified: never add friction to the safety exit. It writes a full-context handoff file so the customer never repeats themselves.

Every proposed write — approved or declined — is appended to an audit log (data/audit_log.jsonl).

Repository layout

server/main.py    MCP server (FastMCP, stdio) — 6 tools, all business rules
agent/main.py     terminal agent: Anthropic API loop + MCP client + confirmation gate
api/chat.py       the same agent as a Vercel serverless function (gate → browser round-trip)
index.html        mock storefront with the embedded chat widget
data/orders.json  seed database: 20 fake orders, refund history
evals/            simulation-based eval harness (in progress)
tools.md          the tool spec — written before any code, updated as decisions were made
LEARNING_LOG.md   honest record of mistakes, corrections, and design lessons

Run it

Terminal agent (needs an Anthropic API key, or OpenRouter's Anthropic-compatible endpoint):

python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
export ANTHROPIC_API_KEY=sk-ant-...
# or via OpenRouter:
#   export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
#   export ANTHROPIC_AUTH_TOKEN="sk-or-..."
#   export AGENT_MODEL="~anthropic/claude-sonnet-latest"
./.venv/bin/python agent/main.py

As an MCP server for any client (e.g. Claude Code):

claude mcp add sprout-stem -- "$PWD/.venv/bin/python" "$PWD/server/main.py"

Web demo on Vercel: import this repo, set the same env vars (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, AGENT_MODEL — or just ANTHROPIC_API_KEY), deploy. The demo database is a per-instance copy in /tmp, so it self-resets on cold starts — a deliberate tradeoff for a stateless demo.

Things that surprised me

See LEARNING_LOG.md — kept honestly, including the mistakes: inventing tools that didn't exist, arguing against my own architecture, and calling the strongest guardrail in the system "decorative."

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A governed MCP server for integrating AI agents with customer data, featuring role-based access control, field redaction, and human-in-the-loop approval for secure support operations.
    1
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A production-grade MCP server for order management, featuring tools for looking up and refunding orders with safety measures like confirmation tokens, rate limiting, and error handling.
    -
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for a production-shaped service-business agent, enabling grounded knowledge search, intake collection, and human escalation with deterministic safety boundaries.
    3
    MIT