Metadata-Version: 2.4
Name: z3950-mcp
Version: 0.1.1
Summary: MCP server for searching library catalogs over Z39.50 and retrieving MARC 21 records.
Project-URL: Homepage, https://github.com/mpasternak/z3950-mcp
Project-URL: Repository, https://github.com/mpasternak/z3950-mcp
Project-URL: Issues, https://github.com/mpasternak/z3950-mcp/issues
Author-email: Michał Pasternak <michal.dtz@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: catalog,library,marc,mcp,pymarc,yaz,z39.50,z3950
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.10
Requires-Dist: mcp>=1.2.0
Requires-Dist: pymarc>=5.1
Description-Content-Type: text/markdown

# z3950-mcp

[![CI](https://github.com/mpasternak/z3950-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/mpasternak/z3950-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant search
library catalogs over **Z39.50** and get back structured **MARC 21** records.

It talks to catalogs through [YAZ](https://www.indexdata.com/resources/software/yaz/)
(`yaz-client`) and parses the returned records with
[pymarc](https://gitlab.com/pymarc/pymarc), so you get clean, standards-based
bibliographic data — not scraped HTML.

## Why

Most Polish (and international) library OPACs — Prolib/Integro, Virtua, Aleph,
Alma, Koha — expose a Z39.50 server, the decades-old library-standard search
protocol. It returns MARC 21, which is far more reliable to consume than
scraping a web catalog. This server puts that behind two simple MCP tools.

## Requirements

- Python ≥ 3.10
- The **YAZ** toolkit on your `PATH` (`yaz-client`):
  - macOS: `brew install yaz`
  - Debian/Ubuntu: `apt-get install yaz`

## Install

```bash
git clone https://github.com/mpasternak/z3950-mcp
cd z3950-mcp
uv sync
```

## Tools

### `list_targets()`

Lists the preconfigured catalogs — see [Public Z39.50 resources](#public-z3950-resources)
below. Each entry's `key` can be passed to `search`.

### `search(target, query, by="any", start=1, count=5, include_raw_marc=false)`

Searches a catalog and returns parsed MARC records.

- **target** — a registry `key` above, or an ad-hoc `host:port/database` string.
- **by** — index to search: `any`, `title`, `author`, `subject`, `isbn`,
  `issn`, `publisher`, `year`, `lccn`, `id`, or `pqf` to pass `query` through
  as a raw [PQF](https://software.indexdata.com/yaz/doc/tools.html#PQF) query.
- **count** — how many records to return (max 50). Use `count=1` with a narrow
  query (e.g. by ISBN) to fetch a single full record.
- **include_raw_marc** — also include the full MARC record as text.

Example result for `search("bn", "Pan Tadeusz", by="title", count=1)`:

```json
{
  "target": "bn-nl.alma.exlibrisgroup.com:1921/48OMNIS_NLOP",
  "query": "@attr 1=4 \"Pan Tadeusz\"",
  "hits": 1406,
  "returned": 1,
  "records": [
    {
      "control_number": "991000041299705066",
      "title": "Pan Tadeusz czyli Ostatni zajazd na Litwie : historia szlachecka...",
      "author": "Mickiewicz, Adam (1798-1855)",
      "year": "1994",
      "isbn": ["8370010938"],
      "publisher": "\"Alfa-Wero\"",
      "language": "pol",
      "subjects": ["Poezja polska -- 19 w."]
    }
  ]
}
```

## Public Z39.50 resources

Catalogs below were **verified reachable (2026-07)** — they return live MARC 21.
Ones with a `key` are built in (`search("bn", …)`); the rest you pass as an
ad-hoc `host:port/database`.

### Polish

| key  | library                        | connection                                       |
|------|--------------------------------|--------------------------------------------------|
| `bn` | Biblioteka Narodowa            | `bn-nl.alma.exlibrisgroup.com:1921/48OMNIS_NLOP` |

The National Library has the broadest Polish holdings. **Most other Polish
libraries expose Z39.50 only through the KaRo federation and don't publish
their host/port openly** — to find more:

- **KaRo** (Katalog Rozproszony Bibliotek Polskich) — <https://karo.umk.pl>,
  a YAZ broker over ~120 Polish library targets.
- **IRSpy** target registry — <https://irspy.indexdata.com>.
- **NUKAT** (union catalog) provides its Z39.50 parameters
  [on request only](https://centrum.nukat.edu.pl/pl/poznaj-nukat/pobieranie-danych).

### English / international

| key        | library                       | connection                        |
|------------|-------------------------------|-----------------------------------|
| `loc`      | Library of Congress (Voyager) | `z3950.loc.gov:7090/VOYAGER`      |
| `loc-lcdb` | Library of Congress (LCDB)    | `lx2.loc.gov:210/LCDB`            |
| `nkc`      | Národní knihovna ČR           | `aleph.nkp.cz:9991/NKC`           |
| `demo`     | Index Data demo (test)        | `z3950.indexdata.com:210/marc`    |

The Library of Congress also serves a MARC-8 database at
`lx2.loc.gov:210/LCDB_MARC8` (pymarc transcodes it to Unicode). The Index Data
server is a tiny demo database, handy for testing this MCP without hitting a
real library.

### Examples

```
search("demo", "computer")                           # tiny test database — smoke test
search("bn", "Lem Solaris")                          # Polish National Library
search("loc", "9780262033848", by="isbn")            # Library of Congress, by ISBN
search("nkc", "Kafka", by="author", count=3)         # Czech National Library
search("aleph.nkp.cz:9991/NKC", "Švejk")             # same, as an ad-hoc target
```

## Use with Claude Code / Claude Desktop

### Claude Code (CLI)

Register the server with `claude mcp add`. The `-s user` scope makes it
available in all your projects; drop it for the current project only.

Straight from the public repo (no clone needed):

```bash
claude mcp add z3950 -s user -- uvx --from git+https://github.com/mpasternak/z3950-mcp z3950-mcp
```

From PyPI once released:

```bash
claude mcp add z3950 -s user -- uvx z3950-mcp
```

From a local checkout (for development):

```bash
claude mcp add z3950 -s user -- uv run --directory /path/to/z3950-mcp z3950-mcp
```

Remove it again with `claude mcp remove z3950 -s user`.

### Claude Desktop (JSON config)

Add to `claude_desktop_config.json` (stdio transport):

```json
{
  "mcpServers": {
    "z3950": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/mpasternak/z3950-mcp", "z3950-mcp"]
    }
  }
}
```

## Adding catalogs

Append a `Target(...)` to `_TARGETS` in `src/z3950_mcp/targets.py`, or just pass
an ad-hoc `host:port/database` to `search`. Finding a library's Z39.50
coordinates: the [IRSpy registry](https://irspy.indexdata.com/) lists many;
some (e.g. NUKAT) provide them on request.

## Development

```bash
uv run pytest              # unit tests (offline)
uv run pytest -m integration   # live tests against BN + LoC (network + yaz)
uv run ruff check .
```

## Releasing

Releases publish to [PyPI](https://pypi.org/p/z3950-mcp) via
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no
API tokens). Publishing a GitHub Release (or pushing a `v*` tag) triggers
`.github/workflows/publish.yml`, which builds and uploads from the `pypi`
environment. The PyPI project must have a matching trusted publisher
(repo `mpasternak/z3950-mcp`, workflow `publish.yml`, environment `pypi`).

## License

MIT — see [LICENSE](LICENSE).
