A small MCP server modeling a library, plus a client that drives it with the OpenAI Agents SDK. Uses uv for environment and dependency management.
app.py— the MCP server (4 tools, 2 resources, 1 prompt)client.py— connects toapp.pyover stdio using an Agents SDK agentpyproject.toml— project + dependency definition (uv reads this)uv.lock— locked dependency versions (commit this alongside pyproject.toml)
# 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:
uv --versionOpen this folder in VS Code, open a terminal (Ctrl+` / Cmd+`),
and sync dependencies from the lockfile:
uv syncThis 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.
This is the check the assignment asks for — it should report 4 tools, 1 prompt, 1 resource, 1 template:
uv run fastmcp inspect app.pyExpected 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):
uv run python app.pyThe 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.pyis available (the client expectshttp://localhost:11434/v1andmodel="gemma4:e4b"by default). - Then run:
uv run python client.pyOption B — Use OpenAI instead
- Set your OpenAI API key in the environment:
# macOS / Linux
export OPENAI_API_KEY="sk-..."- Edit
client.pyto construct an OpenAI-backed model (or replace the local model block) so the client uses your OpenAI credentials, then run:
uv run python client.pyclient.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.
The agent receives: "Find me books about space, then borrow one for member M001." It should:
- 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). - Call
borrow_book("9780451457998", "M001")→ decrements its stock and records the borrow in M001's history. - 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.
Don't pip install directly into the venv — use uv so pyproject.toml
and uv.lock stay in sync:
uv add some-package- 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_booksmatches onlytitle/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 returnstr/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.