Skip to main content
Glama
Kaiser9005

mcp-server-starter

by Kaiser9005
README.md
# mcp-server-starter

**A small but real MCP server (FastMCP) — with a destructive action guarded behind human confirmation.**

Most "MCP server" examples stop at a `hello_world` tool. This one shows the part
that actually matters when an agent is on the other end: a **destructive tool that
refuses to run** until a confirmation token is supplied, forcing the agent to
surface the decision to a human instead of wiping data on its own.

It exposes a tiny inventory domain as MCP tools + one resource, over stdio
transport — drop-in for Claude Desktop, Cursor, or the MCP Inspector.

## Tools

| Tool | Tier | Behavior |
|---|---|---|
| `list_items` / `get_item` | read | runs freely |
| `add_item` / `adjust_quantity` | low-impact write | runs freely (validates inputs) |
| `delete_all_items(confirm_token)` | **destructive** | **guarded** — returns a one-time token first; only deletes when called again with that token |
| `inventory://summary` (resource) | read | human-readable inventory snapshot |

The guard pattern (`delete_all_items`) is the point: call it once with no token →
you get back `{"status": "confirmation_required", "confirm_token": "..."}` → a human
approves → call again with the token → it executes. An irreversible action never
runs on the agent's say-so alone.

## Quick start

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

python server.py            # stdio transport
```

Inspect it interactively:

```bash
npx @modelcontextprotocol/inspector python server.py
```

### Wire it into Claude Desktop / Cursor

Add to your MCP config (`claude_desktop_config.json` or `.cursor/mcp.json`):

```jsonc
{
  "mcpServers": {
    "inventory-starter": {
      "command": "python",
      "args": ["/absolute/path/to/mcp-server-starter/server.py"]
    }
  }
}
```

> Tip (Claude Code schema): for HTTP-based servers use `"type": "http"` — the
> validator rejects the spec-name `"streamable-http"` even though they're the same
> transport. (stdio servers like this one don't need a `type`.)

## Going further

- Swap the in-memory `_ITEMS` dict for Postgres/Supabase (see the companion
  [`fastapi-supabase-multitenant-starter`](https://github.com/Kaiser9005/fastapi-supabase-multitenant-starter)).
- Route the confirmation to Slack/web instead of a token — see
  [`agent-guardrails`](https://github.com/Kaiser9005/agent-guardrails) for a pluggable approver + audit trail.
- For a LangGraph agent that consumes a guarded tool like this, see
  [`langgraph-hitl-agent`](https://github.com/Kaiser9005/langgraph-hitl-agent).

## License

MIT.