Skip to main content
Glama
felixyoma4u

LibraryMCP

by felixyoma4u
README.md
# 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

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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.

Completeness3/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues