perseus-mcp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- perseus_mcp-1.0.0/LICENSE +21 -0
- perseus_mcp-1.0.0/MANIFEST.in +5 -0
- perseus_mcp-1.0.0/PKG-INFO +356 -0
- perseus_mcp-1.0.0/README.md +322 -0
- perseus_mcp-1.0.0/docs/_config.yml +16 -0
- perseus_mcp-1.0.0/docs/_layouts/default.html +34 -0
- perseus_mcp-1.0.0/docs/architecture.md +293 -0
- perseus_mcp-1.0.0/docs/assets/css/style.css +232 -0
- perseus_mcp-1.0.0/docs/assets/img/perseus-mcp-logo.png +0 -0
- perseus_mcp-1.0.0/docs/contributing.md +118 -0
- perseus_mcp-1.0.0/docs/enduser.md +343 -0
- perseus_mcp-1.0.0/docs/index.html +108 -0
- perseus_mcp-1.0.0/docs/license.md +33 -0
- perseus_mcp-1.0.0/docs/llms.txt +34 -0
- perseus_mcp-1.0.0/docs/notebooks.md +115 -0
- perseus_mcp-1.0.0/pyproject.toml +67 -0
- perseus_mcp-1.0.0/server.json +12 -0
- perseus_mcp-1.0.0/setup.cfg +4 -0
- perseus_mcp-1.0.0/src/perseus_mcp/__init__.py +10 -0
- perseus_mcp-1.0.0/src/perseus_mcp/__main__.py +7 -0
- perseus_mcp-1.0.0/src/perseus_mcp/server.py +1253 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/PKG-INFO +356 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/SOURCES.txt +29 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/dependency_links.txt +1 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/entry_points.txt +2 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/requires.txt +8 -0
- perseus_mcp-1.0.0/src/perseus_mcp.egg-info/top_level.txt +1 -0
- perseus_mcp-1.0.0/tests/test_author_resources.py +88 -0
- perseus_mcp-1.0.0/tests/test_exploration_tools.py +250 -0
- perseus_mcp-1.0.0/tests/test_greek_query_normalization.py +211 -0
- perseus_mcp-1.0.0/tests/test_packaging.py +113 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tony Jurg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: perseus-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for Perseus Greek and Latin text research
|
|
5
|
+
Author: Tony Jurg
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tonyjurg/Perseus-mcp
|
|
8
|
+
Project-URL: Documentation, https://tonyjurg.github.io/Perseus-mcp/
|
|
9
|
+
Project-URL: Repository, https://github.com/tonyjurg/Perseus-mcp
|
|
10
|
+
Project-URL: Issues, https://github.com/tonyjurg/Perseus-mcp/issues
|
|
11
|
+
Keywords: mcp,model-context-protocol,perseus,classics,ancient-greek,latin,cts
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Education
|
|
22
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: fastmcp>=2.12.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
[](https://www.repostatus.org/#wip) [](https://opensource.org/licenses/MIT) [](https://doi.org/10.5281/zenodo.20708961) [](https://www.python.org/)  [](https://deepwiki.com/tonyjurg/Perseus-mcp)
|
|
36
|
+
|
|
37
|
+
# Perseus-mcp
|
|
38
|
+
|
|
39
|
+
*Give Claude / Cursor / Windsurf direct access to the Perseus Digital Library* — ancient Greek and Latin texts, precise CTS navigation, plaintext, search, and more.
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
This server exposes twenty-three MCP tools. Every tool returns a text payload: some
|
|
46
|
+
are raw Perseus CTS XML or Scaife JSON, while the discovery and plaintext
|
|
47
|
+
helpers return locally shaped JSON or readable text.
|
|
48
|
+
|
|
49
|
+
- `get_passage(urn)` — fetch a CTS passage by URN.
|
|
50
|
+
- `get_passage_plus(urn)` — fetch passage text plus contextual metadata.
|
|
51
|
+
- `get_passage_plaintext(urn)` — fetch a CTS passage as plain readable text.
|
|
52
|
+
- `get_valid_references(urn, level=None)` — retrieve navigable citation references for a work or edition.
|
|
53
|
+
- `get_valid_references_json(urn, level=None, limit=100, offset=0)` — retrieve paged citation references as JSON.
|
|
54
|
+
- `count_valid_references(urn, level=None)` — count valid references without returning the full list.
|
|
55
|
+
- `get_capabilities()` — list available texts/editions from Perseus CTS.
|
|
56
|
+
- `get_cache_status()` — inspect local metadata cache state.
|
|
57
|
+
- `refresh_metadata_cache()` — refresh cached CTS capabilities.
|
|
58
|
+
- `clear_metadata_cache()` — clear in-memory and disk metadata cache entries.
|
|
59
|
+
- `list_text_groups(language=None, query=None, limit=100)` — list matching authors/textgroups and works.
|
|
60
|
+
- `get_author_resources(author, language=None)` — list works, editions, and translations for a matching author name or CTS textgroup URN.
|
|
61
|
+
- `find_author_names(query, language=None, limit=100)` — find author/textgroup names by partial name match.
|
|
62
|
+
- `get_work_resources(urn_or_title, language=None)` — list editions, translations, and resources for a work, optionally filtered by original language.
|
|
63
|
+
- `get_label(urn)` — fetch human-readable metadata labels for a URN.
|
|
64
|
+
- `get_first_urn(urn)` — get the first navigable URN under a work/edition.
|
|
65
|
+
- `get_prev_next_urn(urn)` — get neighboring passage URNs for navigation.
|
|
66
|
+
- `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 example `mh=nin`).
|
|
67
|
+
- `search_within_text(query, text_urn, ...)` — search within a single Scaife text/edition URN.
|
|
68
|
+
- `get_passage_highlights(query, passage_urn, ...)` — get Scaife token highlight positions for one passage.
|
|
69
|
+
- `get_scaife_library_metadata(urn)` — get Scaife JSON metadata for a library URN.
|
|
70
|
+
- `get_scaife_passage_json(urn)` — get Scaife JSON for a passage URN.
|
|
71
|
+
- `get_scaife_passage_text(urn)` — get Scaife plaintext for a passage URN.
|
|
72
|
+
|
|
73
|
+
## Greek Search Input
|
|
74
|
+
|
|
75
|
+
`search_perseus` normalizes Greek search terms before sending them to Scaife.
|
|
76
|
+
You can pass Unicode Greek directly, or use Beta Code such as `mh=nin a)/eide`.
|
|
77
|
+
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.
|
|
78
|
+
If an ASCII query is ambiguous, set `query_format="betacode"` to force conversion or `query_format="unicode"` to preserve it exactly.
|
|
79
|
+
Search queries are normalized to composed Greek Unicode (NFC), matching sampled Perseus Greek text.
|
|
80
|
+
The tool uses Scaife's JSON search route and returns the JSON response as text.
|
|
81
|
+
The `language` argument controls Greek query normalization; it is not currently
|
|
82
|
+
sent to Scaife as a corpus language filter.
|
|
83
|
+
For CTS inventory discovery, `list_text_groups`, `find_author_names`,
|
|
84
|
+
`get_author_resources`, and `get_work_resources` accept `language="greek"` or
|
|
85
|
+
`language="latin"` (and common codes such as `grc` or `lat`) as an actual work
|
|
86
|
+
language filter. Passage and navigation tools use CTS URNs, whose
|
|
87
|
+
`greekLit`/`latinLit` namespace and edition identifier already select the text.
|
|
88
|
+
Pass `author` to resolve a CTS author/textgroup name or URN. When it resolves
|
|
89
|
+
to exactly one textgroup, Scaife receives a server-side `text_group` filter;
|
|
90
|
+
ambiguous matches fall back to local CTS URN-prefix filtering of the current
|
|
91
|
+
result page.
|
|
92
|
+
Use `search_kind="lemma"` for lemma search; the default `search_kind="form"`
|
|
93
|
+
keeps existing form-search behavior. For Scaife operator queries such as
|
|
94
|
+
quoted phrases, `-`, `|`, `*`, or `~`, set `preserve_operators=True` so Beta
|
|
95
|
+
Code auto-detection does not consume operator characters. For example:
|
|
96
|
+
`search_perseus('"μῆνιν ἄειδε"', query_format="unicode", preserve_operators=True)`,
|
|
97
|
+
`search_perseus("μῆνιν -ἄειδε", query_format="unicode", preserve_operators=True)`,
|
|
98
|
+
or `search_perseus("λόγος | ἀνήρ", search_kind="lemma", query_format="unicode", preserve_operators=True)`.
|
|
99
|
+
Use `page_num` for pagination and pass `text_group` or `work` to use Scaife's
|
|
100
|
+
server-side scope filters. When `author` resolves to exactly one CTS textgroup,
|
|
101
|
+
`search_perseus` sends that textgroup to Scaife instead of filtering only the
|
|
102
|
+
returned page locally.
|
|
103
|
+
|
|
104
|
+
## Local Metadata Cache
|
|
105
|
+
|
|
106
|
+
Discovery and navigation tools cache stable CTS metadata locally to avoid
|
|
107
|
+
repeated multi-megabyte `GetCapabilities` and `GetValidReff` requests. The
|
|
108
|
+
default disk cache lives in `.cache/perseus-mcp` under the current working
|
|
109
|
+
directory and also uses an in-memory cache for the running server process.
|
|
110
|
+
Configure it with:
|
|
111
|
+
|
|
112
|
+
- `PERSEUS_MCP_CACHE_DIR` — override the disk cache directory.
|
|
113
|
+
- `PERSEUS_MCP_CACHE_TTL_SECONDS` — set cache TTL; default is 86400 seconds.
|
|
114
|
+
- `PERSEUS_MCP_DISABLE_CACHE=1` — disable both memory and disk cache reads/writes.
|
|
115
|
+
|
|
116
|
+
The current working directory is the directory from which the Python process is
|
|
117
|
+
started. Running the MCP server from the repository root uses
|
|
118
|
+
`.cache/perseus-mcp`; running a notebook from `examples/` would otherwise use
|
|
119
|
+
`examples/.cache/perseus-mcp`. That is not a second server instance, only a
|
|
120
|
+
second cache location for a separate Python process. To keep one cache location
|
|
121
|
+
across notebooks and MCP clients, set `PERSEUS_MCP_CACHE_DIR` to an absolute
|
|
122
|
+
path such as `/path/to/Perseus-mcp/.cache/perseus-mcp`.
|
|
123
|
+
|
|
124
|
+
## URN Discovery
|
|
125
|
+
|
|
126
|
+
Available edition URNs can differ between Perseus CTS and Scaife search results,
|
|
127
|
+
and the live inventory can change. Use `get_author_resources`,
|
|
128
|
+
`get_work_resources`, or `list_text_groups` before constructing
|
|
129
|
+
edition-specific CTS passage URNs. The notebooks select advertised CTS editions
|
|
130
|
+
from discovery results instead of assuming that a Scaife edition URN is valid
|
|
131
|
+
for Perseus CTS.
|
|
132
|
+
|
|
133
|
+
The live Perseus CTS implementation may return malformed HTML for
|
|
134
|
+
`GetFirstUrn` and `GetPrevNextUrn`. The MCP tools detect that response and
|
|
135
|
+
derive valid XML results from `GetValidReff`.
|
|
136
|
+
|
|
137
|
+
## Setup
|
|
138
|
+
|
|
139
|
+
### 1) Install dependencies
|
|
140
|
+
|
|
141
|
+
Using `uv`:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uv sync
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or with `pip`:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install -e .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Once a release is published to PyPI, users can install it without cloning the
|
|
154
|
+
repository:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pip install perseus-mcp
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
For development and tests:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install -e ".[dev]"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 2) Run tests
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pytest
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
With `uv`, use:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv run --extra dev pytest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 3) Run locally
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv run server.py
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The installed console command and module entry point are equivalent:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
perseus-mcp
|
|
188
|
+
python -m perseus_mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 4) Inspect tools (optional)
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx @modelcontextprotocol/inspector uv run server.py
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
## Example notebooks
|
|
199
|
+
|
|
200
|
+
The `examples/` directory includes Jupyter notebooks that demonstrate both direct endpoint calls and MCP client usage with real Greek and Latin data:
|
|
201
|
+
|
|
202
|
+
- `examples/01_basic_cts_workflow.ipynb` — minimal direct CTS requests.
|
|
203
|
+
- `examples/02_search_and_navigation.ipynb` — direct Scaife JSON search and CTS navigation from valid references.
|
|
204
|
+
- `examples/03_mcp_connection_homer_iliad.ipynb` — FastMCP client connection, Homer resource discovery, and *Iliad* Greek passage analysis.
|
|
205
|
+
- `examples/04_mcp_greek_search_and_navigation.ipynb` — MCP Greek search with Unicode/Beta Code, valid references, and passage navigation.
|
|
206
|
+
- `examples/05_mcp_all_tools.ipynb` — complete MCP tool catalog with descriptions and input schemas.
|
|
207
|
+
- `examples/06_openrouter_llm_mcp_interaction.ipynb` — optional OpenRouter LLM tool-calling loop over the local MCP tools, using NVIDIA Nemotron 3 Super (free) by default.
|
|
208
|
+
- `examples/07_mcp_advanced_search_options.ipynb` — MCP form/lemma search, Scaife operator queries, and author-scoped search examples.
|
|
209
|
+
- `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.
|
|
210
|
+
- `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.
|
|
211
|
+
- `examples/10_mcp_latin_augustine_workflow.ipynb` — Latin-language discovery, CTS navigation, passage retrieval, and a small text analysis using Augustine's *Epistulae* selections.
|
|
212
|
+
|
|
213
|
+
Run them after installing the project dependencies. The MCP notebooks use
|
|
214
|
+
FastMCP's in-process client transport and call the same tools exposed to
|
|
215
|
+
external MCP clients. The optional OpenRouter notebook also requires an
|
|
216
|
+
OpenRouter API key; the MCP server itself does not.
|
|
217
|
+
|
|
218
|
+
### Configure the OpenRouter API key
|
|
219
|
+
|
|
220
|
+
For `examples/06_openrouter_llm_mcp_interaction.ipynb` and
|
|
221
|
+
`examples/09_openrouter_philo_politeia_analysis.ipynb`, copy `.env.example` to
|
|
222
|
+
`.env` in the project root and replace the placeholder:
|
|
223
|
+
|
|
224
|
+
```dotenv
|
|
225
|
+
OPENROUTER_API_KEY=sk-or-v1-...
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Get your API key at [openrouter.ai](https://openrouter.ai/settings/keys). See
|
|
229
|
+
[OpenRouter's API key documentation](https://openrouter.ai/docs/api-keys) for
|
|
230
|
+
authentication details.
|
|
231
|
+
The `.env` file is ignored by Git. You can also set `OPENROUTER_API_KEY` in your
|
|
232
|
+
environment or enter it securely when the notebook prompts.
|
|
233
|
+
|
|
234
|
+
Notebook `06_` can be saved and committed with its LLM and tool-call outputs so
|
|
235
|
+
they render on GitHub. Python variables and kernel memory are not stored in an
|
|
236
|
+
`.ipynb` file, and the notebook does not print the API key. Before committing a
|
|
237
|
+
credentialed run, review the visible outputs and scan for a full OpenRouter key:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
rg "sk-or-v1-[A-Za-z0-9_-]{20,}" examples/06_openrouter_llm_mcp_interaction.ipynb
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The command should produce no output. It does not match the documented
|
|
244
|
+
`sk-or-v1-...` placeholder.
|
|
245
|
+
|
|
246
|
+
## Using with any MCP-capable LLM client
|
|
247
|
+
|
|
248
|
+
This project does not require a specific LLM. Configure your client to launch the local MCP server with:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
uv --directory /full/path/to/Perseus-mcp run server.py
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Most MCP clients need the same pieces: server name `perseus`, command `uv`, args `--directory /full/path/to/Perseus-mcp run server.py`, 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.
|
|
255
|
+
|
|
256
|
+
### Claude Desktop and Claude Code
|
|
257
|
+
|
|
258
|
+
The server runs with Claude over stdio, with no OpenRouter or API key required (OpenRouter is only needed for the optional demo client).
|
|
259
|
+
|
|
260
|
+
**Claude Desktop** — add to `claude_desktop_config.json`:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"mcpServers": {
|
|
265
|
+
"perseus": {
|
|
266
|
+
"command": "uv",
|
|
267
|
+
"args": ["--directory", "/full/path/to/Perseus-mcp", "run", "server.py"]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Restart Claude Desktop; the Perseus tools appear in the tools list.
|
|
274
|
+
|
|
275
|
+
**Claude Code** — one line:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
claude mcp add perseus -- uv --directory /full/path/to/Perseus-mcp run server.py
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Verified against a stdio MCP handshake: all 23 tools register and live calls return (tested with `search_perseus` and `list_text_groups`).
|
|
282
|
+
|
|
283
|
+
## Build a PyPI distribution
|
|
284
|
+
|
|
285
|
+
Install the development dependencies, then build and validate both distribution
|
|
286
|
+
formats:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
python -m pip install -e ".[dev]"
|
|
290
|
+
python -m build
|
|
291
|
+
python -m twine check dist/*
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The build creates a wheel and source archive under `dist/`. Test the wheel in a
|
|
295
|
+
clean virtual environment before publishing. Upload to TestPyPI first:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
python -m twine upload --repository testpypi dist/*
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
After verifying installation from TestPyPI, upload the same artifacts to PyPI:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
python -m twine upload dist/*
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
PyPI does not allow replacing an existing release. Update `project.version` in
|
|
308
|
+
`pyproject.toml`, rebuild from a clean `dist/` directory, and publish each
|
|
309
|
+
version only once. The package build workflow also builds and checks artifacts
|
|
310
|
+
in CI without publishing them.
|
|
311
|
+
|
|
312
|
+
### Automated GitHub release and PyPI publishing
|
|
313
|
+
|
|
314
|
+
The release automation follows the same trusted-publishing pattern as
|
|
315
|
+
MorphKit:
|
|
316
|
+
|
|
317
|
+
1. Set the release version in `pyproject.toml`, for example `1.0.0`.
|
|
318
|
+
2. Merge the version change to the commit that should be released.
|
|
319
|
+
3. Create and push the matching tag, for example `v1.0.0`.
|
|
320
|
+
4. The `Build release artifacts` workflow verifies the tag/version match,
|
|
321
|
+
builds and validates both distributions, and attaches them to a generated
|
|
322
|
+
GitHub release.
|
|
323
|
+
5. That workflow dispatches `Publish to PyPI`, which rebuilds and validates the
|
|
324
|
+
package before publishing through PyPI trusted publishing.
|
|
325
|
+
|
|
326
|
+
Configure the repository once before the first automated upload:
|
|
327
|
+
|
|
328
|
+
- Create a GitHub Actions environment named `pypi`.
|
|
329
|
+
- In the existing PyPI project settings, or as a pending publisher before the
|
|
330
|
+
first upload, add a trusted publisher for owner `tonyjurg`, repository
|
|
331
|
+
`Perseus-mcp`, workflow `publish.yml`, and environment `pypi`.
|
|
332
|
+
- Do not add a PyPI API token; the workflow uses GitHub OIDC with
|
|
333
|
+
`id-token: write`.
|
|
334
|
+
|
|
335
|
+
The workflows reject a tag such as `v1.1.0` when `project.version` is still
|
|
336
|
+
`1.0.0`. PyPI versions are immutable, so increment the version before retrying
|
|
337
|
+
a release that was already uploaded.
|
|
338
|
+
|
|
339
|
+
## Contributing and reporting issues
|
|
340
|
+
|
|
341
|
+
Bug reports, documentation fixes, focused feature requests, and pull requests
|
|
342
|
+
are welcome. Please report problems through the GitHub issue tracker and include
|
|
343
|
+
the command, Python version, MCP client, tool arguments, traceback, and any
|
|
344
|
+
relevant CTS URN or Greek search query when possible.
|
|
345
|
+
|
|
346
|
+
See `docs/contributing.md` for contribution guidance.
|
|
347
|
+
|
|
348
|
+
## Responsible disclosure
|
|
349
|
+
|
|
350
|
+
This project was created with assistance from OpenAI Codex. The human
|
|
351
|
+
maintainer remains responsible for reviewing, testing, and accepting all code
|
|
352
|
+
and documentation changes.
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
This project is released under the MIT License. See `LICENSE` for details.
|