Skip to main content
Glama
rkrdevone-commits

stationery-inventory

README.md
# Stationery Inventory MCP Server — Phase 0

One tool, one dataset, one job: prove Claude can call real code and get real
data back. Everything here was actually run and tested before being handed
to you — not just written and assumed to work.

## Files

- `server.py` — the MCP server. One tool: `check_stationery_inventory`.
- `inventory_data.csv` — sample product data (edit freely; the server reads
  it fresh on every call, so changes show up immediately).
- `audit_log.jsonl` — created automatically on first tool call. One JSON
  line per call: timestamp, tool name, input, output.
- `requirements.txt` — pinned dependency.

## A version gotcha worth knowing (we hit this so you don't have to)

The MCP Python SDK went through a breaking rename: `FastMCP` (v1.x, what
most tutorials online still show) became `MCPServer` (v2.x, what `pip
install mcp` gives you today). This server uses the current v2 API:

```python
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("stationery-inventory")
```

If you ever copy MCP server code from an older tutorial and get
`ModuleNotFoundError: No module named 'mcp.server.fastmcp'`, this is why.

## Setup

```bash
cd stationery-mcp-server
python3 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt
```

## Test it interactively before wiring it into Claude (recommended first)

The SDK ships an inspector UI for exactly this:

```bash
mcp dev server.py
```

This opens a local web UI where you can call `check_stationery_inventory`
directly and see the raw request/response — confirm it works here before
adding the complexity of a full Claude conversation.

## Wire it into Claude Desktop

Edit your Claude Desktop config file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

Add (using the **absolute** path to this folder's `server.py`, and the
absolute path to the `python` inside your venv):

```json
{
  "mcpServers": {
    "stationery-inventory": {
      "command": "/absolute/path/to/stationery-mcp-server/venv/bin/python",
      "args": ["/absolute/path/to/stationery-mcp-server/server.py"]
    }
  }
}
```

Restart Claude Desktop fully (quit, not just close the window). Then ask it
something like:

> "Do we have the Under the Sea picture book in stock?"

If wired correctly, Claude will call the tool and answer from the real CSV
data — including telling you it's out of stock (SKU BK-CH-002 is
intentionally set to 0 in the sample data, to test that path).

## Wire it into Claude Code

```bash
claude mcp add stationery-inventory -- /absolute/path/to/venv/bin/python /absolute/path/to/server.py
```

Then `claude mcp list` to confirm it's registered.

## What "done" looks like for Phase 0

- [ ] `mcp dev server.py` returns correct results for an in-stock item,
      an out-of-stock item, and a misspelled/partial product name.
- [ ] Claude Desktop or Claude Code can call the tool in a live
      conversation and give a correct answer.
- [ ] `audit_log.jsonl` has one entry per call, with sane input/output.

## Deliberately NOT in scope for Phase 0

- A real database or live inventory API (CSV is fine for now)
- Multiple tools (add `check_order_status` etc. only after this one is solid)
- Remote deployment / auth beyond local stdio
- An orchestration framework on top

Each of those is a later module — adding them now would dilute the one
thing Phase 0 is meant to prove: the full loop works.

---

## Module 2: a real deployed agent (`agent.py`)

Phase 0 proved Claude *inside Claude Desktop/Code* can call your tool.
Module 2 proves you can ship an independent, scriptable agent — the
"one deployed agent" gap called out in your roadmap notes — built on
the Claude Agent SDK, using this same MCP server as its only tool.

### Additional setup

```bash
npm install -g @anthropic-ai/claude-code    # the CLI the SDK runs underneath
pip install claude-agent-sdk
export ANTHROPIC_API_KEY=your-key-here
```

**Why the npm install is needed even for a Python project:** the Claude
Agent SDK (Python or TypeScript) is a thin wrapper around the Claude Code
CLI — it spawns that CLI as a subprocess to run the actual agent loop. If
you skip this step you'll hit `CLINotFoundError` the moment you run
`agent.py`. Confirm it worked with `claude --version`.

### Design choice: narrow scope on purpose

`agent.py` restricts `allowed_tools` to exactly one tool —
`mcp__stationery-inventory__check_stationery_inventory` — and the system
prompt explicitly refuses anything outside stock/pricing questions. This
isn't a limitation to fix later; it's the point. Your notes cite a
benchmark where a policy-constrained, tool-scoped agent hit 110/110
successful runs, while a fully autonomous version of the same task failed
all 330 attempts. Every additional tool or open-ended instruction you add
is a branch point where the agent can go wrong — add them deliberately,
one at a time, not by default.

### Run it

```bash
python agent.py "Is the Spiral Notebook A5 in stock?"
python agent.py                       # interactive mode, 'quit' to exit
```

### Run the eval harness (`eval.py`)

Five test cases checking two things that actually matter for a production
agent — not "did it sound plausible":
1. Did it actually call the tool, or hallucinate an answer?
2. Does the final answer contain the fact you know is true from the CSV?

```bash
python eval.py
```

This is a deliberately small seed of Module 5 (evaluation/observability)
from your roadmap — introduced now because retrofitting eval discipline
onto an agent later costs far more than building the habit from agent #1.

### What we verified before handing this to you (and what we couldn't)

Tested in this environment, without a real API key:
- The SDK's exact API surface (`ClaudeAgentOptions`, `query`, message
  types) matches what the code uses — checked against the installed
  package, not assumed from memory.
- The Claude Code CLI dependency is real and required — confirmed via
  `CLINotFoundError` existing in the SDK and installing the CLI to
  resolve it.
- The full pipeline — CLI spawn → MCP subprocess launch → tool discovery
  → API call — works end to end. With a placeholder key, the session
  initialized, the MCP server showed `status: connected`, and the tool
  registered as exactly `mcp__stationery-inventory__check_stationery_inventory`,
  confirming the wiring is correct up to the authentication boundary.

Not tested here (needs your real key, on your machine):
- An actual model response and tool call with real data
- The `eval.py` pass/fail results

Run `eval.py` first thing after setup — if all 5 cases pass, Module 2 is
done.
---

## Module 3: tool-scoped subagents (`orchestration/`)

Module 2 proved one deployed agent (`agent.py`) can call this server's
single tool. Module 3 proves the same MCP server can sit *underneath*
multiple delegated subagents instead of one flat agent — each subagent
scoped to the minimum tools it needs, using the Claude Agent SDK's native
`AgentDefinition`/`Agent` tool primitives (no LangGraph or other
orchestrator here — that comparison is Module 4, see
`learning-notes/module-3-native-subagents-vs-langgraph.md`... [see note below]).

- `inventory-checker` — the only agent allowed to call this server's
  `check_stationery_inventory` tool.
- `order-status` — read-only, calls a separate mock orders tool
  (`orchestration/tools/orders_server.py`; swap for a real order source later).
- `refund-calculator` — **zero tools**. Computes only from what the parent
  agent supplies in its prompt; cannot look anything up or issue anything.
  This is the access-control pattern this module is meant to demonstrate.

### Additional setup

```bash
pip install -r orchestration/requirements.txt
```

Reuses the `claude-agent-sdk` and CLI dependency already installed for
Module 2 — no new external tooling needed.

### Run it

```bash
cd orchestration
python run_orchestrator.py --dry-run     # no API key needed
python run_orchestrator.py               # live, needs ANTHROPIC_API_KEY
```

### What "done" looks like for Module 3

- [ ] `--dry-run` passes and resolves the real path to `../server.py`
- [ ] A live run routes an inventory question to `inventory-checker` and
      gets a real answer from this server's CSV
- [ ] A live run routes an order question to `order-status`
- [ ] A live run routes a refund question to `refund-calculator`, which
      computes a number without calling any tool
- [ ] One explicit-invocation prompt ("Use the X agent...") works

### Deliberately NOT in scope for Module 3

- Real order data (still a mock tool)
- A cross-provider orchestrator (LangGraph — that's the next module)
- Actually issuing a refund (compute-only, by design)