LibraryMCP
# Library Server
A small MCP server modeling a library, plus a client that drives it with the
OpenAI Agents SDK. Uses [uv](https://docs.astral.sh/uv/) for environment and
dependency management.
## Files
- `app.py` — the MCP server (4 tools, 2 resources, 1 prompt)
- `client.py` — connects to `app.py` over stdio using an Agents SDK agent
- `pyproject.toml` — project + dependency definition (uv reads this)
- `uv.lock` — locked dependency versions (commit this alongside pyproject.toml)
## 1. Install uv (if you don't have it yet)
```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
Restart your terminal, then confirm it's on PATH:
```bash
uv --version
```
## 2. Set up the project in VS Code
Open this folder in VS Code, open a terminal (`` Ctrl+` `` / `` Cmd+` ``),
and sync dependencies from the lockfile:
```bash
uv sync
```
This creates a `.venv` in the project folder and installs exactly what's
pinned in `uv.lock`. Point VS Code at it: Command Palette
(`Ctrl+Shift+P`) → **Python: Select Interpreter** → pick the one at
`.venv/bin/python` (or `.venv\Scripts\python.exe` on Windows).
You don't need to manually activate the venv for the commands below —
`uv run` does that for you automatically.
## 3. Verify the server structure
This is the check the assignment asks for — it should report **4 tools,
1 prompt, 1 resource, 1 template**:
```bash
uv run fastmcp inspect app.py
```
Expected output:
```
Components
Tools: 4
Prompts: 1
Resources: 1
Templates: 1
```
You can also run the server directly to confirm it starts cleanly
(it just sits there listening on stdio — `Ctrl+C` to stop):
```bash
uv run python app.py
```
## 4. Run the client
The client normally needs an LLM provider to decide which tools to invoke. This project defaults to using a local LLM (via Ollama) instead of the remote OpenAI API.
Option A — Use the local LLM (default)
- Ensure an Ollama daemon is running and the model referenced in `client.py` is available (the client expects `http://localhost:11434/v1` and `model="gemma4:e4b"` by default).
- Then run:
```bash
uv run python client.py
```
Option B — Use OpenAI instead
- Set your OpenAI API key in the environment:
```bash
# macOS / Linux
export OPENAI_API_KEY="sk-..."
```
- Edit `client.py` to construct an OpenAI-backed model (or replace the local model block) so the client uses your OpenAI credentials, then run:
```bash
uv run python client.py
```
`client.py` spawns `app.py` as a subprocess automatically via `MCPServerStdio`, using `uv run python app.py` as the launch command — so it always runs inside this project's own uv-managed environment.
### What should happen
The agent receives: *"Find me books about space, then borrow one for
member M001."* It should:
1. Call `search_books("space")` → finds **2001: A Space Odyssey** (the only
catalog entry with "space" in the title — matches per the assignment's
title/author search spec).
2. Call `borrow_book("9780451457998", "M001")` → decrements its stock and
records the borrow in M001's history.
3. Print a final natural-language summary confirming both steps.
If you want to see the raw tool-call trace (not just the final answer),
add `print(result.new_items)` after `result = await Runner.run(...)` in
`client.py`, or inspect `result.raw_responses`.
## Adding more dependencies later
Don't `pip install` directly into the venv — use uv so `pyproject.toml`
and `uv.lock` stay in sync:
```bash
uv add some-package
```
## Notes on design choices
- All four tools return **plain strings** for both success and error cases
(e.g. no copies left, unknown ISBN) — never exceptions — so a calling LLM
always gets something it can read and relay to the user.
- `search_books` matches only `title`/`author` (per the assignment spec),
case-insensitively, substring match.
- The dynamic resource (`member://{member_id}/history`) returns JSON as a
string; FastMCP resource functions need to return `str`/`bytes`/specific
content types, not raw Python lists of dicts.
- The mock catalog is 8 well-known sci-fi/fantasy titles; stock levels are
deliberately mixed (some at 0) so you can test both the success and
no-copies-left paths in `borrow_book`.
TDQS
Scored across 4 tools
Each tool targets a distinct operation: search, availability check, borrow, and return. The descriptions clearly delineate when each should be used (search when ISBN unknown, check_availability when ISBN known, borrow/return with ISBN + member). No two tools have overlapping purposes.
Three tools follow a consistent verb_noun pattern (search_books, check_availability, borrow_book, return_book). The pair borrow_book/return_book is perfectly symmetric. Only check_availability deviates slightly by using a gerund-ish structure instead of an imperative verb, though it remains readable and predictable.
Four tools is within a reasonable range for a library catalog server, but it's on the thinner side. The core borrow/return/search/check lifecycle is covered, yet one might expect more tools for a fuller library domain (e.g., catalog a new book, list member history, list popular books). Still, the stated purpose is narrow enough that 4 is defensible.
The core borrowing lifecycle is covered: search to find a book, check availability, borrow, and return. However, there are notable gaps such as no way to list a member's borrowing history, no add_book/register member operations, and no reservation/hold feature. A member could not get a full picture of their loans without an additional tool, creating potential dead ends.