Skip to content

aegean.mcp_server

The aegean-mcp Model Context Protocol server (the [mcp] extra): it exposes the toolkit's read/analysis surface to agents as MCP tools. The tool functions are plain, JSON-returning callables; build_server registers them with FastMCP (imported lazily, so import aegean never pulls the MCP SDK).

mcp_server

An MCP server exposing pyaegean to agents (Claude Code and other MCP clients).

The [mcp] extra installs the Model Context Protocol SDK; aegean-mcp then runs a stdio MCP server that wraps the toolkit's read/analysis surface as tools, so an agent can use pyaegean without writing Python:

  • corpora: list_corpora, corpus_info, show_document, search_signs, balance_accounts, query_corpus (the compound query engine), cite_corpus (plain / BibTeX / APA, exact subsets included), geo_sites (find-site coordinates, Pleiades ids, per-site word attestations), corpus_diagnose (a descriptive corpus-health report), and data_status (the local data store);
  • Greek: greek_pipeline, greek_explain (each token's lemma evidence class in plain language), greek_scan, greek_catalog (the ~1,800-work discovery catalogue), greek_work (a work's text by catalogue id, summarized with a capped preview), greek_gloss (the registry dictionaries), and koine_gloss (the bundled Dodson NT lexicon).

Two conventions hold across every tool. Corpora and works are addressed by registry name or catalogue work id only: no tool accepts a filesystem path, so the server never reads or writes arbitrary local files (a deliberate invariant, not an omission). Domain misses (an unknown corpus, document, work, dictionary, style, or query field) return a structured {"error": ...} payload carrying a did-you-mean hint (for a work, a pointer to greek_catalog) instead of raising, so an agent can recover in one step; raised exceptions are reserved for genuine faults. A tool that may need remote data says so in its description: greek_work texts and the non-bundled greek_gloss dictionaries download into the local data store on first use and are offline after (data_status shows the store).

The tool functions are plain, JSON-returning callables (independently testable); build_server registers them with FastMCP, imported lazily so import aegean never pulls the MCP SDK.

list_corpora

list_corpora() -> list[str]

List the corpora that can be loaded by name.

Five are bundled and load offline; the rest ('damos', 'nt', 'sigla', 'isicily', 'iip', 'iospe', 'igcyr', 'edh', 'ddbdp') download into the local data store on first use (data_status shows what is already stored).

corpus_info

corpus_info(corpus: str) -> dict[str, Any]

Overview of a corpus: script, document count, source, license, and a citation.

corpus is a name from list_corpora (e.g. 'lineara', 'damos', 'nt'). Loading a fetch-on-demand corpus ('nt', 'damos', 'sigla', 'isicily', 'iip', 'iospe', 'igcyr', 'edh', 'ddbdp') downloads it on first use (a one-time fetch into the local data store); check data_status first to see what is already stored. 'ddbdp' is heavy: loading it materialises 57k papyri (prefer aegean db search ddbdp from the shell).

show_document

show_document(corpus: str, doc_id: str) -> dict[str, Any]

One document's metadata and text, line by line.

doc_id is e.g. 'HT13'; case and spacing are forgiven ('ht13', 'py ta 641' resolve), and a miss reports the closest ids.

search_signs

search_signs(corpus: str, pattern: str, limit: int = 50) -> list[dict[str, Any]] | dict[str, Any]

Words matching a wildcard sign pattern (e.g. 'KU-*-RO'), with frequencies.

limit caps the matches; limit <= 0 returns every match.

balance_accounts

balance_accounts(corpus: str, doc_id: str | None = None) -> list[dict[str, Any]] | dict[str, Any]

Accounting reconciliation: each stated total (KU-RO / TO-SO) vs the summed items.

Returns one row per total marker (whole corpus, or one doc_id, forgivingly matched): doc, marker, stated (the written total), computed (the summed items or subtotals), difference (computed minus stated), items (the count fed into the sum), and balances. An empty list means the document(s) carry no total markers. The rows are the shared :func:aegean._view.balance_rows mapping, so this tool, the aegean balance command, and the terminal UI cannot disagree.

query_corpus

query_corpus(corpus: str, where: list[dict[str, Any]], output_kind: str = 'inscriptions', limit: int = 50) -> dict[str, Any]

Run the compound query engine over a corpus and cite the exact result set.

Each where row is {"field", "value"} plus optional "connector" ('and'/'or', default 'and') and "negate" (default false); rows chain in order, and an empty list matches the whole corpus. Fields: id-contains, site-is, scribe-is, period-is, support-is, has-image, has-annotation, ins-contains-word (inscription scope); word-contains, word-prefix, word-suffix, word-min-syllables, word-max-syllables, word-contains-sign, word-cooccurs-with, word-sign-pattern (word scope). output_kind is 'inscriptions' or 'words'; a word's count is its document frequency (how many distinct inscriptions carry it), not its token frequency. limit caps the returned id/word lists (limit <= 0 returns all); the totals are always the full counts. citation cites exactly this result set.

cite_corpus

cite_corpus(corpus: str, style: str = 'plain', site: str | None = None, period: str | None = None, scribe: str | None = None, support: str | None = None) -> dict[str, Any]

Cite a corpus (or, with metadata filters, the exact subset) for a paper.

style is 'plain' (one line), 'bibtex' (a @misc entry), or 'apa'. Any of site, period, scribe, support filters the corpus first (exact match, combined with AND); the citation then names the subset, so it states exactly what was used. documents is the cited document count.

geo_sites

geo_sites(corpus: str, word: str | None = None) -> dict[str, Any]

Find-site geography for a corpus: coordinates (WGS84), Pleiades ids, and the contested-provenance flag; with word, the per-site attestation counts of that word (case-insensitive) instead.

Bundled gazetteer, offline. Only the Aegean inscription corpora yield sites (lineara, linearb, cypriot, cyprominoan, sigla, damos); alphabetic Greek corpora (greek, nt) carry no find-spot, and the Greek epigraphy corpora record find-places outside the Aegean gazetteer, so they yield no rows. A non-empty contested value is the reason a find-spot is disputed; treat such sites as unverified provenance.

data_status

data_status() -> dict[str, Any]

The local data store: every fetchable dataset with its downloaded state, on-disk size, size note, and license.

Read-only (nothing is downloaded or deleted here). The corpora that load by name over MCP appear under their asset names ('nt-corpus', 'damos-corpus', 'sigla-corpus', 'isicily-corpus', 'iip-corpus', 'iospe-corpus', 'igcyr-corpus', 'edh-corpus', 'ddbdp-corpus'); a dataset that is not downloaded is fetched automatically (sha256-verified) the first time something needs it, or explicitly from the shell with aegean data fetch NAME.

greek_pipeline

greek_pipeline(text: str) -> list[dict[str, Any]]

Run the (baseline, offline) Greek NLP pipeline: one row per token.

Each row carries text, upos, lemma, lemma_source (the lemma's evidence class: attested / neural / rule / seed / paradigm / identity / unresolved / punct), lemma_known (False marks a lemma to verify), sentence/index position, and the parser/neural fields (head, relation, xpos, feats; None under the baseline). The rows are the shared :func:aegean._view.pipeline_rows mapping, so this tool, the aegean greek pipeline command, and the terminal UI emit identical rows.

greek_explain

greek_explain(text: str) -> list[dict[str, Any]]

Explain what the (baseline, offline) Greek pipeline did to each token.

One row per token, in pipeline order, carrying token, upos, lemma, lemma_source (the lemma's evidence class: attested / neural / rule / seed / paradigm / identity / unresolved / punct), needs_review (True marks a lemma a human should verify: an identity fall-through or an unresolved miss), morphology (the UD FEATS string when a neural backend filled one, else None), and note (a one-line, plain-language account of what that evidence class means). The evidence CLASS is the whole trust claim: there are deliberately no confidence numbers (the neural pipeline's calibrated confidence needs the trained model, which this server does not load). Rendered from the same records greek_pipeline returns, never a re-run, so the two tools cannot disagree.

corpus_diagnose

corpus_diagnose(corpus: str, deep: bool = False) -> dict[str, Any]

A descriptive corpus-health report for a corpus, as structured JSON.

corpus is a name from list_corpora. Reports, as OBSERVABLE facts: the reading-status profile (certain / unclear / restored / lost, and how many documents carry apparatus), provenance and citation, the accounting reconciliation for the Aegean accounting scripts (a discrepancy is a lead, not a verdict on the scribe — Aegean metrology is imperfectly understood), numeral-parse anomalies, and the annotation-review state. deep adds a sign-frequency scan (hapax signs and labels absent from the inventory) for the Aegean syllabic scripts. Every section that does not apply to a corpus is marked applicable: false rather than raising.

Loading a fetch-on-demand corpus ('nt', 'damos', 'sigla', 'isicily', 'iip', 'iospe', 'igcyr', 'edh', 'ddbdp') downloads it on first use; a cold-cache offline failure returns the structured error. 'ddbdp' is heavy (57k papyri materialise), and deep over a large corpus is expensive; check data_status first.

greek_scan

greek_scan(text: str, meter: str = 'hexameter') -> dict[str, Any]

Scan a Greek verse line. meter is 'hexameter' / 'pentameter' / 'trimeter' or an aeolic line type ('sapphic_hendecasyllable', 'glyconic', …). Reports the glyph pattern, or scans: false with the reason if the line does not fit.

greek_catalog

greek_catalog(query: str | None = None, author: str | None = None, title: str | None = None, source: str | None = None, limit: int = 40) -> dict[str, Any]

Search the bundled catalogue of ~1,800 loadable Greek works (Perseus canonical-greekLit + First1KGreek): id, author, English and Greek title.

All filters are case-insensitive substrings combined with AND; query matches across id, author, and both titles; source limits to 'perseus' or 'first1k'. limit caps the returned rows (limit <= 0 returns all; total is always the full match count). Bundled metadata: offline and instant. Pass an id to greek_work to load the text itself (fetched into the local data store on its first use).

greek_work

greek_work(work_id: str, ref: str | None = None, preview_lines: int = 10) -> dict[str, Any]

Load a real Greek work by its CTS-style catalogue id (e.g. 'tlg0012.tlg001', the Iliad), whole or one section, and summarize it with a short text preview.

work_id is an id from greek_catalog (Perseus canonical-greekLit / First1KGreek), never a filesystem path. ref selects a section by citation address: '1' (a book), '1.2' (a chapter), or '1.1-1.50' (a verse line-range). The first use of a work downloads its TEI file into the local data store (a one-time, commit-pinned network fetch); later calls are offline, exactly like greek_gloss's dictionaries. Returns the work summary (documents, tokens, first document id, name, source, data_version) plus preview: the first preview_lines lines of the first document (preview_lines <= 0 sends none). An unknown or malformed id returns a structured error pointing at the catalog.

greek_gloss

greek_gloss(word: str, dictionary: str = 'lsj', full: bool = False) -> dict[str, Any]

Gloss a Greek word from a registry dictionary: lsj (classical, the default), middle-liddell, cunliffe (Homeric), autenrieth (Homeric), abbott-smith, or dodson (Koine NT).

The word is looked up as given and lemmatized on a miss, so inflected forms resolve. The first use of a dictionary other than dodson downloads and builds its index (a one-time fetch into the local data store, roughly 0.1 to 15 MB depending on the dictionary); later calls are offline. full adds the complete entry body, not just the concise gloss.

koine_gloss

koine_gloss(word: str) -> dict[str, Any]

Koine (NT) gloss for a Greek word via the bundled Dodson lexicon (offline, CC0).

build_server

build_server() -> Any

Build a FastMCP server with every pyaegean tool registered (needs the [mcp] extra).

main

main() -> None

Console-script entry point (aegean-mcp): serve the tools over stdio.