Skip to main content
Glama
BrianVia

fcpl-mcp

README.md
# fcpl-mcp

A remote [Model Context Protocol](https://modelcontextprotocol.io/) server for one Fairfax County Public Library patron account, hosted on Cloudflare Workers. It searches the Polaris catalog, reads account activity, and manages physical-item holds.

One Worker serves a bearer-protected, stateless `/mcp` endpoint. A Durable Object owns the Polaris cookies and serializes requests because catalog searches store state in the server session.

## Setup

```sh
npm install
npx wrangler login
cp wrangler.jsonc wrangler.local.jsonc
```

In `wrangler.local.jsonc`, replace `fcpl.example.com` with a hostname on a Cloudflare zone you manage. Deploy using that local config:

```sh
WRANGLER_CONFIG=wrangler.local.jsonc npm run deploy
npx wrangler secret put FCPL_USERNAME --config wrangler.local.jsonc
npx wrangler secret put FCPL_PASSWORD --config wrangler.local.jsonc
openssl rand -hex 32 | tee /dev/stderr | npx wrangler secret put MCP_BEARER --config wrangler.local.jsonc
```

The custom-domain route creates its DNS record on first deploy.

## Tools

| Tool | Description |
|---|---|
| `search_catalog` | Search by keyword, title, author, subject, or ISBN |
| `get_title` | Get title details and availability by branch |
| `get_checkouts` | List checkouts sorted by due date |
| `get_holds` | List account holds |
| `get_account` | Get patron record fields and the fines total |
| `place_hold` | Place a physical-item hold at the saved default pickup library |
| `cancel_hold` | Cancel a physical-item hold |

`place_hold` and `cancel_hold` change the real account. Confirm those actions with the user first.

## Raw HTTP

The endpoint uses stateless Streamable HTTP and returns SSE-framed MCP responses.

```sh
curl -s https://fcpl.example.com/mcp -X POST \
  -H 'Authorization: Bearer <MCP_BEARER>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```

`GET /` identifies the MCP endpoint. `GET /health` is public and reports whether the Durable Object currently has session cookies without making a network request.

## Local development

```sh
cp .dev.vars.example .dev.vars
npm run check
npx wrangler dev
```

## Notes

- Built against Fairfax County's Polaris PowerPAC 7.8 catalog at `fcplcat.fairfaxcounty.gov`. Other Polaris libraries likely work by changing `BASE` in `src/polaris.ts`, but that is untested.
- Polaris sessions expire after a few idle minutes; the server logs in again automatically.
- No renew tool on purpose: FCPL auto-renews items. Check `renewalsLeft` from `get_checkouts` if you care.
- Holds use the patron account's saved default pickup library.
- eContent holds are visible but must be cancelled in Libby or OverDrive.
- This is an unofficial scraper and may break when Polaris changes its HTML or request flow.
- Credentials are Worker secrets and are sent only to the library catalog login endpoint.
- `/mcp` requires `MCP_BEARER`; `/` and `/health` are public.

## License

MIT. See [LICENSE](LICENSE).