MCP server for Perseus Greek and Latin text research
Project description
Perseus-mcp
Give Claude / Cursor / Windsurf direct access to the Perseus Digital Library — ancient Greek and Latin texts, precise CTS navigation, plaintext, search, and more.
A high-quality MCP server for Classical Greek and Latin literature. It runs as a local FastMCP server so MCP-capable applications can attach these Perseus tools to the LLM/model provider of your choice.
Features
This server exposes twenty-three MCP tools. Every tool returns a text payload: some are raw Perseus CTS XML or Scaife JSON, while the discovery and plaintext helpers return locally shaped JSON or readable text.
get_passage(urn)— fetch a CTS passage by URN.get_passage_plus(urn)— fetch passage text plus contextual metadata.get_passage_plaintext(urn)— fetch a CTS passage as plain readable text.get_valid_references(urn, level=None)— retrieve navigable citation references for a work or edition.get_valid_references_json(urn, level=None, limit=100, offset=0)— retrieve paged citation references as JSON.count_valid_references(urn, level=None)— count valid references without returning the full list.get_capabilities()— list available texts/editions from Perseus CTS.get_cache_status()— inspect local metadata cache state.refresh_metadata_cache()— refresh cached CTS capabilities.clear_metadata_cache()— clear in-memory and disk metadata cache entries.list_text_groups(language=None, query=None, limit=100)— list matching authors/textgroups and works.get_author_resources(author, language=None)— list works, editions, and translations for a matching author name or CTS textgroup URN.find_author_names(query, language=None, limit=100)— find author/textgroup names by partial name match.get_work_resources(urn_or_title, language=None)— list editions, translations, and resources for a work, optionally filtered by original language.get_label(urn)— fetch human-readable metadata labels for a URN.get_first_urn(urn)— get the first navigable URN under a work/edition.get_prev_next_urn(urn)— get neighboring passage URNs for navigation.search_perseus(query, language="greek", query_format="auto", author=None, search_kind="form", preserve_operators=False, page_num=1, text_group=None, work=None, result_format="instances")— search texts via Scaife search API. Greek queries may be entered as Unicode Greek (for exampleμῆνιν) or Beta Code (for examplemh=nin).search_within_text(query, text_urn, ...)— search within a single Scaife text/edition URN.get_passage_highlights(query, passage_urn, ...)— get Scaife token highlight positions for one passage.get_scaife_library_metadata(urn)— get Scaife JSON metadata for a library URN.get_scaife_passage_json(urn)— get Scaife JSON for a passage URN.get_scaife_passage_text(urn)— get Scaife plaintext for a passage URN.
Greek Search Input
search_perseus normalizes Greek search terms before sending them to Scaife.
You can pass Unicode Greek directly, or use Beta Code such as mh=nin a)/eide.
The default query_format="auto" detects explicit Beta Code marks like =, /, (, ), and *, and also treats short unaccented Greek-looking queries such as logos as Beta Code.
If an ASCII query is ambiguous, set query_format="betacode" to force conversion or query_format="unicode" to preserve it exactly.
Search queries are normalized to composed Greek Unicode (NFC), matching sampled Perseus Greek text.
The tool uses Scaife's JSON search route and returns the JSON response as text.
The language argument controls Greek query normalization; it is not currently
sent to Scaife as a corpus language filter.
For CTS inventory discovery, list_text_groups, find_author_names,
get_author_resources, and get_work_resources accept language="greek" or
language="latin" (and common codes such as grc or lat) as an actual work
language filter. Passage and navigation tools use CTS URNs, whose
greekLit/latinLit namespace and edition identifier already select the text.
Pass author to resolve a CTS author/textgroup name or URN. When it resolves
to exactly one textgroup, Scaife receives a server-side text_group filter;
ambiguous matches fall back to local CTS URN-prefix filtering of the current
result page.
Use search_kind="lemma" for lemma search; the default search_kind="form"
keeps existing form-search behavior. For Scaife operator queries such as
quoted phrases, -, |, *, or ~, set preserve_operators=True so Beta
Code auto-detection does not consume operator characters. For example:
search_perseus('"μῆνιν ἄειδε"', query_format="unicode", preserve_operators=True),
search_perseus("μῆνιν -ἄειδε", query_format="unicode", preserve_operators=True),
or search_perseus("λόγος | ἀνήρ", search_kind="lemma", query_format="unicode", preserve_operators=True).
Use page_num for pagination and pass text_group or work to use Scaife's
server-side scope filters. When author resolves to exactly one CTS textgroup,
search_perseus sends that textgroup to Scaife instead of filtering only the
returned page locally.
Local Metadata Cache
Discovery and navigation tools cache stable CTS metadata locally to avoid
repeated multi-megabyte GetCapabilities and GetValidReff requests. The
default disk cache lives in .cache/perseus-mcp under the current working
directory and also uses an in-memory cache for the running server process.
Configure it with:
PERSEUS_MCP_CACHE_DIR— override the disk cache directory.PERSEUS_MCP_CACHE_TTL_SECONDS— set cache TTL; default is 86400 seconds.PERSEUS_MCP_DISABLE_CACHE=1— disable both memory and disk cache reads/writes.
The current working directory is the directory from which the Python process is
started. Running the MCP server from the repository root uses
.cache/perseus-mcp; running a notebook from examples/ would otherwise use
examples/.cache/perseus-mcp. That is not a second server instance, only a
second cache location for a separate Python process. To keep one cache location
across notebooks and MCP clients, set PERSEUS_MCP_CACHE_DIR to an absolute
path such as /path/to/Perseus-mcp/.cache/perseus-mcp.
URN Discovery
Available edition URNs can differ between Perseus CTS and Scaife search results,
and the live inventory can change. Use get_author_resources,
get_work_resources, or list_text_groups before constructing
edition-specific CTS passage URNs. The notebooks select advertised CTS editions
from discovery results instead of assuming that a Scaife edition URN is valid
for Perseus CTS.
The live Perseus CTS implementation may return malformed HTML for
GetFirstUrn and GetPrevNextUrn. The MCP tools detect that response and
derive valid XML results from GetValidReff.
Perseus may also return 429 Too Many Requests when a workflow sends many CTS
requests in a short period. Pause before retrying, reduce concurrency, and add
delays to passage-processing loops. The server currently exposes the upstream
HTTP error instead of retrying automatically.
Setup
1) Install dependencies
Using uv:
uv sync
Or with pip:
pip install -e .
Once a release is published to PyPI, users can install it without cloning the repository:
pip install perseus-mcp
For development and tests:
pip install -e ".[dev]"
2) Run tests
pytest
With uv, use:
uv run --extra dev pytest
3) Run locally
uv run perseus-mcp
The installed console command and module entry point are equivalent:
perseus-mcp
python -m perseus_mcp
4) Inspect tools (optional)
npx @modelcontextprotocol/inspector uv run perseus-mcp
Example notebooks
The examples/ directory includes Jupyter notebooks that demonstrate both direct endpoint calls and MCP client usage with real Greek and Latin data:
examples/00_install_and_run_perseus_mcp.ipynb— installation and launch guide covering PyPI, pip, uv, local repository development, MCP client configuration, verification, upgrades, and troubleshooting.examples/01_basic_cts_workflow.ipynb— minimal direct CTS requests.examples/02_search_and_navigation.ipynb— direct Scaife JSON search and CTS navigation from valid references.examples/03_mcp_connection_homer_iliad.ipynb— FastMCP client connection, Homer resource discovery, and Iliad Greek passage analysis.examples/04_mcp_greek_search_and_navigation.ipynb— MCP Greek search with Unicode/Beta Code, valid references, and passage navigation.examples/05_mcp_all_tools.ipynb— complete MCP tool catalog with descriptions and input schemas.examples/06_openrouter_llm_mcp_interaction.ipynb— optional OpenRouter LLM tool-calling loop over the local MCP tools, using OpenRouter's Free Models Router by default.examples/07_mcp_advanced_search_options.ipynb— MCP form/lemma search, Scaife operator queries, and author-scoped search examples.examples/08_mcp_cache_and_search_tools.ipynb— advanced demonstration of cache tools, paged references, scoped search, reader search, highlights, and Scaife metadata/text retrieval.examples/09_openrouter_philo_politeia_analysis.ipynb— OpenRouter-assisted, evidence-first analysis ofπολιτείαin Philo of Alexandria using scoped MCP search results and cited passages.examples/10_mcp_latin_augustine_workflow.ipynb— Latin-language discovery, CTS navigation, passage retrieval, and a small text analysis using Augustine's Epistulae selections.
Run them after installing the project dependencies. The MCP notebooks use FastMCP's in-process client transport and call the same tools exposed to external MCP clients. The optional OpenRouter notebook also requires an OpenRouter API key; the MCP server itself does not.
Configure the OpenRouter API key
For examples/06_openrouter_llm_mcp_interaction.ipynb and
examples/09_openrouter_philo_politeia_analysis.ipynb, copy .env.example to
.env in the project root and replace the placeholder:
OPENROUTER_API_KEY=sk-or-v1-...
Get your API key at openrouter.ai. See
OpenRouter's API key documentation for
authentication details.
The .env file is ignored by Git. You can also set OPENROUTER_API_KEY in your
environment or enter it securely when the notebook prompts.
Both OpenRouter notebooks default to openrouter/free. This router selects
among free models currently available on OpenRouter and filters for capabilities
required by the request, such as tool calling or structured output. It avoids
binding the examples to one free model that may later be removed or temporarily
unavailable. The tradeoff is reduced reproducibility: separate runs may use
different concrete models, so the notebooks record the resolved model returned
by OpenRouter. Set OPENROUTER_MODEL to a fixed model slug when exact model
selection matters.
Notebook 06_ can be saved and committed with its LLM and tool-call outputs so
they render on GitHub. Python variables and kernel memory are not stored in an
.ipynb file, and the notebook does not print the API key. Before committing a
credentialed run, review the visible outputs and scan for a full OpenRouter key:
rg "sk-or-v1-[A-Za-z0-9_-]{20,}" examples/06_openrouter_llm_mcp_interaction.ipynb
The command should produce no output. It does not match the documented
sk-or-v1-... placeholder.
Using with any MCP-capable LLM client
This project does not require a specific LLM. Configure your client to launch the local MCP server with:
uv --directory /full/path/to/Perseus-mcp run perseus-mcp
Most MCP clients need the same pieces: server name perseus, command uv, args --directory /full/path/to/Perseus-mcp run perseus-mcp, and an empty environment unless you have local customizations. See docs/enduser.md for generic client guidance and docs/architecture.md for the architecture choices, including why FastMCP is used.
Claude Desktop and Claude Code
The server runs with Claude over stdio, with no OpenRouter or API key required (OpenRouter is only needed for the optional demo client).
Claude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"perseus": {
"command": "uv",
"args": ["--directory", "/full/path/to/Perseus-mcp", "run", "perseus-mcp"]
}
}
}
Restart Claude Desktop; the Perseus tools appear in the tools list.
Claude Code — one line:
claude mcp add perseus -- uv --directory /full/path/to/Perseus-mcp run perseus-mcp
Verified against a stdio MCP handshake: all 23 tools register and live calls return (tested with search_perseus and list_text_groups).
Build a PyPI distribution
Install the development dependencies, then build and validate both distribution formats:
python -m pip install -e ".[dev]"
python -m build
python -m twine check dist/*
The build creates a wheel and source archive under dist/. Test the wheel in a
clean virtual environment before publishing. Upload to TestPyPI first:
python -m twine upload --repository testpypi dist/*
After verifying installation from TestPyPI, upload the same artifacts to PyPI:
python -m twine upload dist/*
PyPI does not allow replacing an existing release. Update project.version in
pyproject.toml, rebuild from a clean dist/ directory, and publish each
version only once. The package build workflow also builds and checks artifacts
in CI without publishing them.
Automated GitHub release and PyPI publishing
The release automation follows the same trusted-publishing pattern as MorphKit:
- Set the release version in
pyproject.toml, for example1.0.0. - Merge the version change to the commit that should be released.
- Create and push the matching tag, for example
v1.0.0. - The
Build release artifactsworkflow verifies the tag/version match, builds and validates both distributions, and attaches them to a generated GitHub release. - That workflow dispatches
Publish to PyPI, which rebuilds and validates the package before publishing through PyPI trusted publishing.
Configure the repository once before the first automated upload:
- Create a GitHub Actions environment named
pypi. - In the existing PyPI project settings, or as a pending publisher before the
first upload, add a trusted publisher for owner
tonyjurg, repositoryPerseus-mcp, workflowpublish.yml, and environmentpypi. - Do not add a PyPI API token; the workflow uses GitHub OIDC with
id-token: write.
The workflows reject a tag such as v1.1.0 when project.version is still
1.0.0. PyPI versions are immutable, so increment the version before retrying
a release that was already uploaded.
Contributing and reporting issues
Bug reports, documentation fixes, focused feature requests, and pull requests are welcome. Please report problems through the GitHub issue tracker and include the command, Python version, MCP client, tool arguments, traceback, and any relevant CTS URN or Greek search query when possible.
See docs/contributing.md for contribution guidance.
Responsible disclosure
This project was created with assistance from OpenAI Codex. The human maintainer remains responsible for reviewing, testing, and accepting all code and documentation changes.
License
This project is released under the MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file perseus_mcp-1.0.1.tar.gz.
File metadata
- Download URL: perseus_mcp-1.0.1.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad6806095f38b9948190ab013acb1b6c74b433d91e3077a7c9ee68014743662f
|
|
| MD5 |
1312c861606373056485ffc7b467c68a
|
|
| BLAKE2b-256 |
7048bfca67d99a2b251bc12312d34e1b3c41594ac6570e45e7feb813c32ec9e4
|
Provenance
The following attestation bundles were made for perseus_mcp-1.0.1.tar.gz:
Publisher:
publish.yml on tonyjurg/Perseus-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
perseus_mcp-1.0.1.tar.gz -
Subject digest:
ad6806095f38b9948190ab013acb1b6c74b433d91e3077a7c9ee68014743662f - Sigstore transparency entry: 1871373264
- Sigstore integration time:
-
Permalink:
tonyjurg/Perseus-mcp@ee86f1a6f9cbc007df4a46afe26598d88dd8708b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/tonyjurg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee86f1a6f9cbc007df4a46afe26598d88dd8708b -
Trigger Event:
release
-
Statement type:
File details
Details for the file perseus_mcp-1.0.1-py3-none-any.whl.
File metadata
- Download URL: perseus_mcp-1.0.1-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05057f4d40be80b8cf1ffcf608101c95e68d92187613ab2db15e22c03e12d4b7
|
|
| MD5 |
e1bb6d48f87b0d484a6f762349036fce
|
|
| BLAKE2b-256 |
3831b294a64b855c849e8d34274420ae34b8ec95d8256e0b2b03d150813d2c24
|
Provenance
The following attestation bundles were made for perseus_mcp-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on tonyjurg/Perseus-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
perseus_mcp-1.0.1-py3-none-any.whl -
Subject digest:
05057f4d40be80b8cf1ffcf608101c95e68d92187613ab2db15e22c03e12d4b7 - Sigstore transparency entry: 1871373306
- Sigstore integration time:
-
Permalink:
tonyjurg/Perseus-mcp@ee86f1a6f9cbc007df4a46afe26598d88dd8708b -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/tonyjurg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee86f1a6f9cbc007df4a46afe26598d88dd8708b -
Trigger Event:
release
-
Statement type: