Skip to content

Commit 8f6736c

Browse files
authored
Merge pull request #7 from ayhammouda/fix/issue-5-python-310-314-support
[codex] Support Python docs 3.10 through 3.14
2 parents 5c8666f + eebc2e3 commit 8f6736c

14 files changed

Lines changed: 293 additions & 57 deletions

File tree

.github/INTEGRATION-TEST.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Release-specific sign-off still lives in [`.github/RELEASE.md`](RELEASE.md).
1313
- `uv run pyright src/`
1414
- `uv run pytest --tb=short -q`
1515
- Local index build completed:
16-
- `uv run mcp-server-python-docs build-index --versions 3.12,3.13`
16+
- `uv run mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14`
1717
- Doctor passes:
1818
- `uv run mcp-server-python-docs doctor`
1919
- Slow E2E workflow passes when preparing a release:
@@ -113,7 +113,7 @@ locked.
113113

114114
- [ ] `uvx mcp-server-python-docs --version`
115115
- Expected: prints the current package version
116-
- [ ] `uvx mcp-server-python-docs build-index --versions 3.12,3.13`
116+
- [ ] `uvx mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14`
117117
- Expected: index build completes successfully
118118
- [ ] `uvx mcp-server-python-docs doctor`
119119
- Expected: all required checks pass
@@ -131,8 +131,8 @@ or supported Python versions.
131131
- Expected: both Python 3.13 and Python 3.14 jobs start
132132
- [ ] Confirm each job installs the built wheel into a clean virtual environment
133133
- Expected: the command path is the installed `mcp-server-python-docs`, not editable source
134-
- [ ] Confirm `build-index --versions 3.12,3.13` passes
135-
- Expected: both versions produce content, not symbol-only fallback
134+
- [ ] Confirm `build-index --versions 3.10,3.11,3.12,3.13,3.14` passes
135+
- Expected: all five versions produce content, not symbol-only fallback
136136
- [ ] Confirm `doctor` and `validate-corpus` pass
137137
- Expected: corpus smoke checks include requested versions and the default version
138138
- [ ] Inspect uploaded logs if a job fails

.github/RELEASE.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ Before the first release, configure PyPI Trusted Publishing:
1919

2020
## Notes
2121

22-
**Python version coverage:** The release workflow builds and tests against Python 3.13 only.
23-
Python 3.12 is covered by the CI workflow (`ci.yml`) which runs a 2x2 matrix (3.12/3.13 x
24-
ubuntu/macos) on every push to `main`. Since tags are created from commits that have already
25-
passed CI, 3.12 compatibility is verified before the release workflow runs. This is an accepted
26-
trade-off to keep the release artifact pipeline simple (single Python version produces the wheel).
22+
**Runtime coverage:** The release workflow builds and tests against Python 3.13 only.
23+
Python 3.12 is covered by the CI workflow (`ci.yml`) which runs a 2x2 matrix
24+
(3.12/3.13 x ubuntu/macos) on every push to `main`. Since tags are created
25+
from commits that have already passed CI, 3.12 compatibility is verified before
26+
the release workflow runs. This is an accepted trade-off to keep the release
27+
artifact pipeline simple (single Python version produces the wheel).
28+
29+
**Documentation coverage:** The full docs index target is Python documentation
30+
versions 3.10 through 3.14.
2731

2832
## Creating a Release
2933

@@ -106,7 +110,7 @@ Complete these steps in order. Each step has a checkbox -- do not skip ahead.
106110
First public release of mcp-server-python-docs.
107111
108112
A read-only, version-aware MCP retrieval server over Python
109-
standard library documentation (3.12 + 3.13).
113+
standard library documentation (3.10 through 3.14).
110114
111115
Installable via: uvx mcp-server-python-docs"
112116
```
@@ -137,7 +141,7 @@ Complete these steps in order. Each step has a checkbox -- do not skip ahead.
137141
# Should print 0.1.0
138142

139143
# Step 2: Build index
140-
uvx mcp-server-python-docs build-index --versions 3.12,3.13
144+
uvx mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14
141145
# Should complete successfully
142146

143147
# Step 3: Doctor check
@@ -148,7 +152,8 @@ Complete these steps in order. Each step has a checkbox -- do not skip ahead.
148152
- Run GitHub Actions workflow `Slow E2E`
149153
- Confirm Python 3.13 and Python 3.14 jobs both pass
150154
- Confirm each job installs the built wheel, runs
151-
`build-index --versions 3.12,3.13`, `doctor`, and `validate-corpus`
155+
`build-index --versions 3.10,3.11,3.12,3.13,3.14`, `doctor`, and
156+
`validate-corpus`
152157
- [ ] Claude Desktop test with published package:
153158
Configure `mcpServers` with `uvx mcp-server-python-docs` and verify
154159
"what is asyncio.TaskGroup" returns a correct hit

.github/workflows/e2e.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,13 @@ jobs:
99
installed-build-index:
1010
name: Installed build-index (Python ${{ matrix.python-version }})
1111
runs-on: ubuntu-latest
12-
timeout-minutes: 60
12+
timeout-minutes: 120
1313

1414
strategy:
1515
fail-fast: false
1616
matrix:
1717
python-version: ["3.13", "3.14"]
1818

19-
env:
20-
HOME: ${{ runner.temp }}/mcp-python-docs-home
21-
XDG_CACHE_HOME: ${{ runner.temp }}/mcp-python-docs-cache
22-
2319
steps:
2420
- uses: actions/checkout@v4
2521

@@ -40,9 +36,12 @@ jobs:
4036
.e2e-venv/bin/mcp-server-python-docs --version
4137
4238
- name: Build and validate full docs index
39+
env:
40+
HOME: ${{ runner.temp }}/mcp-python-docs-home
41+
XDG_CACHE_HOME: ${{ runner.temp }}/mcp-python-docs-cache
4342
run: |
4443
set -o pipefail
45-
.e2e-venv/bin/mcp-server-python-docs build-index --versions 3.12,3.13 \
44+
.e2e-venv/bin/mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14 \
4645
2>&1 | tee "${RUNNER_TEMP}/build-index-${{ matrix.python-version }}.log"
4746
.e2e-venv/bin/mcp-server-python-docs doctor
4847
.e2e-venv/bin/mcp-server-python-docs validate-corpus

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ uv run pytest tests/test_retrieval_regression.py -q
4242
The server needs a local SQLite index before runtime validation:
4343

4444
```bash
45-
uv run mcp-server-python-docs build-index --versions 3.12,3.13
45+
uv run mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14
4646
uv run mcp-server-python-docs doctor
4747
uv run mcp-server-python-docs validate-corpus
4848
```

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,13 @@ shell or use `python -m uv ...` as a fallback for local contributor commands.
8080
Build the local documentation index:
8181

8282
```bash
83-
uvx mcp-server-python-docs build-index --versions 3.12,3.13
83+
uvx mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14
8484
```
8585

8686
If you installed the package persistently, you can drop the `uvx` prefix:
8787

8888
```bash
89-
mcp-server-python-docs build-index --versions 3.12,3.13
89+
mcp-server-python-docs build-index --versions 3.10,3.11,3.12,3.13,3.14
9090
```
9191

9292
This downloads Python's `objects.inv` files, clones CPython docs sources, runs
@@ -178,7 +178,7 @@ The server currently exposes four MCP tools:
178178
Use this server when you need:
179179

180180
- exact Python stdlib symbol resolution
181-
- consistent version-aware answers across Python 3.12 and 3.13
181+
- consistent version-aware answers across Python 3.10 through 3.14
182182
- token-efficient section retrieval from official docs
183183
- a local, read-only MCP server with a simple operational story
184184

@@ -292,7 +292,7 @@ For contributor setup and verification:
292292
Tested on macOS and Linux. Windows should work, but it is not verified on
293293
every release.
294294

295-
Python 3.12 and 3.13 are currently supported.
295+
Python documentation versions 3.10 through 3.14 are currently supported.
296296

297297
## License
298298

src/mcp_server_python_docs/__main__.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def _consume_saved_stdout_fd() -> int:
6666
# === Now safe to import everything else ===
6767
import click # noqa: E402
6868

69+
from mcp_server_python_docs.ingestion.cpython_versions import ( # noqa: E402
70+
SUPPORTED_DOC_VERSIONS_CSV,
71+
)
72+
6973

7074
@click.group(invoke_without_command=True)
7175
@click.option("--version", "show_version", is_flag=True, help="Show version and exit.")
@@ -105,7 +109,7 @@ def serve() -> None:
105109
@click.option(
106110
"--versions",
107111
required=True,
108-
help="Comma-separated Python versions (e.g., 3.12,3.13)",
112+
help=f"Comma-separated Python versions (e.g., {SUPPORTED_DOC_VERSIONS_CSV})",
109113
)
110114
@click.option(
111115
"--skip-content",
@@ -120,6 +124,9 @@ def build_index(versions: str, skip_content: bool) -> None:
120124
import venv
121125
from pathlib import Path
122126

127+
from mcp_server_python_docs.ingestion.cpython_versions import (
128+
CPYTHON_DOCS_BUILD_CONFIG,
129+
)
123130
from mcp_server_python_docs.ingestion.inventory import ingest_inventory
124131
from mcp_server_python_docs.ingestion.publish import (
125132
_version_sort_key,
@@ -128,6 +135,7 @@ def build_index(versions: str, skip_content: bool) -> None:
128135
publish_index,
129136
)
130137
from mcp_server_python_docs.ingestion.sphinx_json import (
138+
build_sphinx_bootstrap_requirements,
131139
build_sphinx_json_command,
132140
ingest_sphinx_json_dir,
133141
make_sphinx_json_env,
@@ -142,15 +150,12 @@ def build_index(versions: str, skip_content: bool) -> None:
142150
get_readwrite_connection,
143151
)
144152

145-
# Version tag mapping: CPython git tag and Sphinx constraints (INGR-C-02)
146-
VERSION_CONFIG: dict[str, dict[str, str]] = {
147-
"3.12": {"tag": "v3.12.13", "sphinx_pin": "sphinx~=8.2.0"},
148-
"3.13": {"tag": "v3.13.12", "sphinx_pin": "sphinx<9.0.0"},
149-
}
150-
151153
version_list = parse_expected_versions(versions)
152154
if not version_list:
153-
logger.error("No valid versions specified. Example: --versions 3.13")
155+
logger.error(
156+
"No valid versions specified. Example: --versions %s",
157+
SUPPORTED_DOC_VERSIONS_CSV,
158+
)
154159
raise SystemExit(1)
155160

156161
# Validate version format before sorting (CR-03, WR-04)
@@ -188,7 +193,7 @@ def build_index(versions: str, skip_content: bool) -> None:
188193
continue
189194

190195
# === Content ingestion (INGR-C-01 through INGR-C-03) ===
191-
config = VERSION_CONFIG.get(version)
196+
config = CPYTHON_DOCS_BUILD_CONFIG.get(version)
192197
if not config:
193198
logger.warning(
194199
"No CPython build config for %s, skipping content ingestion",
@@ -226,9 +231,22 @@ def build_index(versions: str, skip_content: bool) -> None:
226231
)
227232
pip_path = os.path.join(scripts_dir, "pip")
228233

229-
# Install Sphinx with the version pin for this CPython branch
234+
# Install Sphinx with the version pin for this CPython branch.
235+
bootstrap_requirements = build_sphinx_bootstrap_requirements(
236+
config["sphinx_pin"]
237+
)
238+
if len(bootstrap_requirements) > 1:
239+
logger.info(
240+
"Installing Sphinx bootstrap packages for Python %s: %s",
241+
version,
242+
", ".join(bootstrap_requirements[:-1]),
243+
)
230244
subprocess.run(
231-
[pip_path, "install", config["sphinx_pin"]],
245+
[
246+
pip_path,
247+
"install",
248+
*bootstrap_requirements,
249+
],
232250
check=True,
233251
capture_output=True,
234252
text=True,
@@ -381,7 +399,10 @@ def validate_corpus(db_path: str | None) -> None:
381399

382400
if not target.exists():
383401
logger.error("Index not found at %s", target)
384-
logger.error("Run: mcp-server-python-docs build-index --versions 3.13")
402+
logger.error(
403+
"Run: mcp-server-python-docs build-index --versions %s",
404+
SUPPORTED_DOC_VERSIONS_CSV,
405+
)
385406
raise SystemExit(1)
386407

387408
logger.info("Validating corpus at %s", target)
@@ -506,7 +527,8 @@ def doctor() -> None:
506527
index_detail = str(index_path)
507528
if not index_exists:
508529
index_detail += (
509-
" (not found -- run: mcp-server-python-docs build-index --versions 3.13)"
530+
f" (not found -- run: mcp-server-python-docs build-index --versions "
531+
f"{SUPPORTED_DOC_VERSIONS_CSV})"
510532
)
511533
else:
512534
size_mb = index_path.stat().st_size / (1024 * 1024)

src/mcp_server_python_docs/detection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ def match_to_indexed(
7474
"""Match a detected version to the closest indexed version.
7575
7676
Returns the detected version if it's in the index, otherwise None.
77-
We don't guess if 3.11 is detected but only 3.12/3.13 are indexed,
78-
return None and let the normal default resolution handle it.
77+
We don't guess -- if a detected version is not indexed, return None and
78+
let the normal default resolution handle it.
7979
"""
8080
if detected in indexed_versions:
8181
return detected
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Pinned CPython documentation build targets."""
2+
from __future__ import annotations
3+
4+
from typing import Final, TypedDict
5+
6+
7+
class CPythonDocsBuildConfig(TypedDict):
8+
"""Build settings for one CPython documentation release."""
9+
10+
tag: str
11+
sphinx_pin: str
12+
13+
14+
SUPPORTED_DOC_VERSIONS: Final[tuple[str, ...]] = (
15+
"3.10",
16+
"3.11",
17+
"3.12",
18+
"3.13",
19+
"3.14",
20+
)
21+
22+
SUPPORTED_DOC_VERSIONS_CSV: Final[str] = ",".join(SUPPORTED_DOC_VERSIONS)
23+
24+
# CPython git tags are pinned so content builds are reproducible and do not
25+
# drift when a maintenance branch receives new commits.
26+
CPYTHON_DOCS_BUILD_CONFIG: Final[dict[str, CPythonDocsBuildConfig]] = {
27+
"3.10": {"tag": "v3.10.20", "sphinx_pin": "sphinx==3.4.3"},
28+
"3.11": {"tag": "v3.11.15", "sphinx_pin": "sphinx~=7.2.0"},
29+
"3.12": {"tag": "v3.12.13", "sphinx_pin": "sphinx~=8.2.0"},
30+
"3.13": {"tag": "v3.13.13", "sphinx_pin": "sphinx<9.0.0"},
31+
"3.14": {"tag": "v3.14.4", "sphinx_pin": "sphinx<9.0.0"},
32+
}

src/mcp_server_python_docs/ingestion/publish.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from collections.abc import Iterable
1515
from datetime import datetime
1616
from pathlib import Path
17+
from typing import Final
1718

1819
from mcp_server_python_docs.storage.db import (
1920
get_cache_dir,
@@ -23,6 +24,8 @@
2324

2425
logger = logging.getLogger(__name__)
2526

27+
SMOKE_SENTINEL_SYMBOL: Final[str] = "asyncio.run"
28+
2629

2730
def _version_sort_key(version: str) -> tuple[int, ...]:
2831
"""Sort dotted Python versions numerically."""
@@ -83,7 +86,7 @@ def record_ingestion_run(
8386
Args:
8487
conn: Read-write SQLite connection.
8588
source: Source identifier (e.g., 'python-docs').
86-
version: Version string (e.g., '3.13' or '3.12,3.13').
89+
version: Version string (e.g., '3.13' or '3.10,3.11,3.12,3.13,3.14').
8790
status: Run status ('building', 'smoke_testing', 'published', 'failed').
8891
artifact_hash: SHA256 hash of the build artifact.
8992
notes: Optional notes about the run.
@@ -221,16 +224,18 @@ def run_smoke_tests(
221224
"SELECT 1 FROM symbols "
222225
"JOIN doc_sets ON doc_sets.id = symbols.doc_set_id "
223226
"WHERE doc_sets.version = ? "
224-
"AND symbols.qualified_name = 'asyncio.TaskGroup' LIMIT 1",
225-
(version,),
227+
"AND symbols.qualified_name = ? LIMIT 1",
228+
(version, SMOKE_SENTINEL_SYMBOL),
226229
).fetchone()
227230
if row:
228231
messages.append(
229-
f"OK: sentinel: asyncio.TaskGroup symbol found for version {version}"
232+
f"OK: sentinel: {SMOKE_SENTINEL_SYMBOL} symbol found "
233+
f"for version {version}"
230234
)
231235
else:
232236
messages.append(
233-
f"FAIL: sentinel: asyncio.TaskGroup symbol missing for version {version}"
237+
f"FAIL: sentinel: {SMOKE_SENTINEL_SYMBOL} symbol missing "
238+
f"for version {version}"
234239
)
235240
passed = False
236241

0 commit comments

Comments
 (0)