Metadata-Version: 2.4
Name: swiss-academic-libraries-mcp
Version: 1.0.5
Summary: MCP Server für Schweizer Wissenschaftsbibliotheken: swisscovery (500+ Bibliotheken via SRU), e-rara, e-periodica und e-manuscripta (OAI-PMH). Kein API-Key erforderlich.
Project-URL: Homepage, https://github.com/malkreide/swiss-academic-libraries-mcp
Project-URL: Repository, https://github.com/malkreide/swiss-academic-libraries-mcp
Project-URL: Issues, https://github.com/malkreide/swiss-academic-libraries-mcp/issues
License: MIT License
        
        Copyright (c) 2026 Hayal Oezkan
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: academic,e-manuscripta,e-periodica,e-rara,libraries,mcp,oai-pmh,open-data,slsp,sru,swisscovery,switzerland
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp[cli]>=1.6.0
Requires-Dist: pydantic>=2.0.0
Description-Content-Type: text/markdown

> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**

# 📚 swiss-academic-libraries-mcp

[![PyPI version](https://img.shields.io/pypi/v/swiss-academic-libraries-mcp)](https://pypi.org/project/swiss-academic-libraries-mcp/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/)
[![No Auth Required](https://img.shields.io/badge/auth-not%20required-lightgrey)](https://github.com/malkreide/swiss-academic-libraries-mcp)
![CI](https://github.com/malkreide/swiss-academic-libraries-mcp/actions/workflows/ci.yml/badge.svg)

> MCP server providing access to Swiss academic libraries — swisscovery, e-rara, e-periodica, e-manuscripta. No API key required.

[🇩🇪 Deutsche Version](README.de.md)

### Demo

![Demo: Claude using swisscovery_search and erara_list_records](docs/assets/demo.svg)

---

## Overview

**swiss-academic-libraries-mcp** connects AI models to the full Swiss academic library infrastructure via standardised, open protocols. It covers the [swisscovery](https://swisscovery.slsp.ch) union catalogue (500+ libraries, 10M+ records) and three digitalisation platforms: historical prints ([e-rara](https://www.e-rara.ch)), periodicals ([e-periodica](https://www.e-periodica.ch)) and manuscripts ([e-manuscripta](https://www.e-manuscripta.ch)).

All data sources use open, authentication-free protocols (SRU/MARC21, OAI-PMH/Dublin Core). The server supports both local use via Claude Desktop (stdio transport) and cloud deployment (Streamable HTTP).

**Anchor demo query:** *"Which Swiss university dissertations on primary school pedagogy are held in Swiss libraries, and are any of them digitised in e-rara?"*

---

## Features

- **11 tools** across 4 data sources — all read-only, no API key required
- **swisscovery search** with full CQL syntax: full-text, title, author, subject, ISBN/ISSN
- **OAI-PMH harvesting** with date range and collection filters plus pagination via resumption tokens
- **MARC21 parser** extracting 20+ fields (title, creator, publication info, subjects, abstract, URLs)
- **Dublin Core parser** for all three digitalisation portals
- **Dual transport**: stdio for Claude Desktop · Streamable HTTP for cloud/self-hosted deployments
- **2 built-in prompts**: `research-workflow` and `education-research`
- **Markdown and JSON output** for all tools
- **34 unit tests** (no network) + 6 live smoke tests

---

## Data Sources

| Source | Protocol | Content | Records |
|--------|----------|---------|---------|
| [swisscovery (SLSP)](https://swisscovery.slsp.ch) | SRU / MARC21 | 500+ Swiss libraries | 10M+ |
| [e-rara](https://www.e-rara.ch) | OAI-PMH / Dublin Core | Digitised historical prints | 250k+ |
| [e-periodica](https://www.e-periodica.ch) | OAI-PMH / Dublin Core | Digitised periodicals (1750–today) | 1M+ articles |
| [e-manuscripta](https://www.e-manuscripta.ch) | OAI-PMH / Dublin Core | Manuscripts & archival material | 100k+ |

---

## Tools

| Tool | Source | Function |
|------|--------|----------|
| `library_info` | — | Entry point: overview of all sources and tools |
| `swisscovery_search` | swisscovery | Full-text / CQL search across the union catalogue |
| `swisscovery_get_record` | swisscovery | Single record by MMS-ID |
| `erara_list_records` | e-rara | Prints filtered by date / collection |
| `erara_get_record` | e-rara | Single item by OAI identifier |
| `erara_list_collections` | e-rara | All participating libraries |
| `eperiodica_list_records` | e-periodica | Articles filtered by date |
| `eperiodica_get_record` | e-periodica | Single article by OAI identifier |
| `emanuscripta_list_records` | e-manuscripta | Manuscripts filtered by date / collection |
| `emanuscripta_get_record` | e-manuscripta | Single object by OAI identifier |
| `emanuscripta_list_collections` | e-manuscripta | All archives / collections |

### Example Use Cases

| Query | Tool |
|-------|------|
| *"Which books about Swiss primary schools are held in Swiss libraries?"* | `swisscovery_search` |
| *"Show digitised historical works from ETH Library"* | `erara_list_records` |
| *"Which Swiss periodicals were digitised in 2023?"* | `eperiodica_list_records` |
| *"What manuscript collections does e-manuscripta hold?"* | `emanuscripta_list_collections` |

---

## Prerequisites

- Python 3.11 or higher
- [uv](https://docs.astral.sh/uv/) / uvx (recommended) or pip
- Internet access (all APIs are publicly available)

---

## Installation

### Claude Desktop (recommended)

Add to `claude_desktop_config.json`:

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`  
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "swiss-academic-libraries": {
      "command": "uvx",
      "args": ["swiss-academic-libraries-mcp"]
    }
  }
}
```

Restart Claude Desktop — the server starts automatically on first use.

### Cloud / Self-hosted (Streamable HTTP)

```bash
uvx swiss-academic-libraries-mcp --http --port 8000 [--host 127.0.0.1]
```

**Security & Deployment Notes**

- **Default binding is `127.0.0.1`** (loopback only). The server has no
  built-in authentication.
- Use `--host 0.0.0.0` only when running **behind a reverse proxy that
  provides authentication and per-IP rate limits** (e.g. nginx with
  `limit_req` + OAuth2-Proxy). Non-loopback bindings emit a WARN log.
- Logs go to **stderr**; set verbosity with `MCP_LOG_LEVEL=DEBUG|INFO|WARNING`.

### Development

```bash
git clone https://github.com/malkreide/swiss-academic-libraries-mcp
cd swiss-academic-libraries-mcp
pip install -e .
```

---

## Quickstart

Start by calling `library_info` for a full overview. Then:

```
"Which books about Swiss primary schools are held in Swiss libraries?"
→ swisscovery_search(query='subject = "Volksschule"', max_records=20)

"Show digitised historical works from ETH Library"
→ erara_list_records(set_spec="zut")

"Which Swiss periodicals were digitised in 2023?"
→ eperiodica_list_records(from_date="2023-01-01", until_date="2023-12-31")

"What manuscript collections does e-manuscripta hold?"
→ emanuscripta_list_collections()

→ [More use cases by audience](EXAMPLES.md) →
```

> 💡 *"No API key — just install and query."*

### CQL Search Syntax (swisscovery)

```
Full text:     Volksschule Zürich
Title:         title = "education reform"
Author:        creator = "Pestalozzi"
Subject:       subject = "pedagogy"
ISBN:          isbn = "978-3-05-006234-0"
Combined:      title = "school" AND creator = "Pestalozzi"
Pagination:    start_record = 11
```

---

## Configuration

No API keys or environment variables required.

| Parameter | Default | Description |
|-----------|---------|-------------|
| `--http` | off | Enable Streamable HTTP transport |
| `--port` | 8000 | Port for HTTP transport |

---

## Project Structure

```
swiss-academic-libraries-mcp/
├── src/
│   └── swiss_academic_libraries_mcp/
│       ├── __init__.py       # Package init
│       ├── server.py         # FastMCP server, 11 tools, 2 prompts, 1 resource
│       └── api_client.py     # HTTP client, MARC21 + OAI-PMH/DC parsers
├── tests/
│   └── test_server.py        # 34 unit tests + 6 live smoke tests
├── pyproject.toml
├── CHANGELOG.md
├── CONTRIBUTING.md            # Contributing guide (English)
├── CONTRIBUTING.de.md         # Contributing guide (German)
├── SECURITY.md               # Security policy (English)
├── SECURITY.de.md            # Security policy (German)
├── LICENSE
├── README.md                 # This file (English)
└── README.de.md              # German version
```

---

## Testing

```bash
# Unit tests (no network required)
PYTHONPATH=src pytest tests/ -m "not live"

# Live smoke tests (internet required)
PYTHONPATH=src pytest tests/ -m "live"
```

---

## Safety & Limits

- **Read-only:** All tools perform HTTP GET requests against public SRU and OAI-PMH endpoints — no data is written, modified, or deleted.
- **No personal data:** The APIs return bibliographic metadata (titles, authors, publication info, subject headings) and public digitisation records. No personally identifiable information (PII) about library users is processed or stored.
- **Rate limits:** swisscovery SRU and the OAI-PMH endpoints are public and have no documented hard limits, but OAI-PMH harvesting is paginated via resumption tokens — use `from_date` / `until_date` and keep `max_records` reasonable. The server enforces a 30s timeout per request.
- **Data freshness:** Results reflect the upstream catalogues at query time. No caching is performed by this server; indexing latency is controlled by SLSP and the digitisation platforms.
- **Terms of service:** Data is subject to the ToS and licences of each source — [swisscovery / SLSP](https://slsp.ch), [e-rara](https://www.e-rara.ch), [e-periodica](https://www.e-periodica.ch), [e-manuscripta](https://www.e-manuscripta.ch). Most digitised material is in the public domain or under Creative Commons licences; always check the rights statement on the individual record before redistribution.
- **No guarantees:** This is a community project, not affiliated with SLSP, ETH Library, or any of the participating institutions. Availability depends on the upstream APIs.

---

## Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:

- Reporting bugs and requesting features
- Setting up the development environment
- Code style and test requirements
- Submitting pull requests

This project follows the conventions of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide).

---

## Security

To report a vulnerability, please follow the responsible disclosure process in [SECURITY.md](SECURITY.md). The server is read-only and requires no API key; see the *Safety & Limits* section above for the security model.

---

## Changelog

See [CHANGELOG.md](CHANGELOG.md)

---

## Deployment for Swiss Public Administration

If you self-host this server for a Swiss school authority, archive,
or municipal use case:

- **Data residency:** prefer on-premise or a CH-based cloud provider.
  The query patterns themselves (which library searches a civil
  servant runs) may reveal ongoing research and are best kept on
  Swiss infrastructure.
- **Upstream calls** go exclusively to CH-hosted services: SLSP /
  swisscovery, ETH-Bibliothek (e-rara, e-periodica, e-manuscripta).
  No data leaves Switzerland.
- **Logging:** logs are written to stderr; configure your IT
  retention policy accordingly (e.g. systemd-journal `MaxRetentionSec`).
- **HTTP transport** must run behind a reverse proxy with
  authentication and per-IP rate limits (see *Security & Deployment
  Notes* above).

---

## License

MIT License — see [LICENSE](LICENSE)

---

## Author

Hayal Oezkan · [github.com/malkreide](https://github.com/malkreide)

---

## Credits & Related Projects

- **Data:** [swisscovery / SLSP](https://swisscovery.slsp.ch) · [e-rara](https://www.e-rara.ch) · [e-periodica](https://www.e-periodica.ch) · [e-manuscripta](https://www.e-manuscripta.ch)
- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) — Anthropic / Linux Foundation
- **Related:** [eth-library-mcp](https://github.com/malkreide/eth-library-mcp) — ETH Library Discovery & Persons API
- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide)

| Server | Description |
|--------|-------------|
| [`zurich-opendata-mcp`](https://github.com/malkreide/zurich-opendata-mcp) | City of Zurich Open Data |
| [`eth-library-mcp`](https://github.com/malkreide/eth-library-mcp) | ETH Library Discovery & Persons API |
| [`swiss-statistics-mcp`](https://github.com/malkreide/swiss-statistics-mcp) | Swiss Federal Statistics (BFS) |
| [`fedlex-mcp`](https://github.com/malkreide/fedlex-mcp) | Swiss Federal Law via Fedlex SPARQL |
| [`swiss-transport-mcp`](https://github.com/malkreide/swiss-transport-mcp) | OJP journey planning, SIRI-SX disruptions |

<!-- mcp-name: io.github.malkreide/swiss-academic-libraries-mcp -->
