python-docs-mcp-server 0.3.2__tar.gz → 0.4.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.
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/CHANGELOG.md +21 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/PKG-INFO +4 -3
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/README.md +3 -2
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/pyproject.toml +1 -1
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/server.json +2 -2
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/app_context.py +2 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/models.py +26 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/server.py +28 -0
- python_docs_mcp_server-0.4.0/src/mcp_server_python_docs/services/whatsnew.py +172 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_retrieval_regression.py +2 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_services.py +5 -2
- python_docs_mcp_server-0.4.0/tests/test_whatsnew.py +415 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/uv.lock +1 -1
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.coderabbit.yaml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.cursorignore +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/CODEOWNERS +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/INTEGRATION-TEST.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/ISSUE_TEMPLATE/autonomous-agent.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/PULL_REQUEST_TEMPLATE/agent.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/RELEASE.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/TEST-STRATEGY.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/dependabot.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/scripts/install_publisher.sh +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/scripts/release_gate.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/ci.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/codeql.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/e2e.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/product-quality.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/release.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/scorecard.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.github/workflows/security.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.gitignore +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/.mcp.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/AGENT-EXECUTION-PIPELINE.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/AGENTS.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/CLAUDE.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/CONTRIBUTING.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/LICENSE +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/OPENCLAW-FORGE-PROTOCOL.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/SECURITY.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/STRATEGIC-ROADMAP-2026-05-29.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/__main__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/base.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/claude_tokens.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/context7_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/deepwiki_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/eligibility.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/gitmcp_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/google_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/guard.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/openai_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/python_docs_mcp_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/adapters/ref_tools_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/corpus.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/model_matrix.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/regression.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/report.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/runner.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/benchmarks/scoring.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/ADR-001-source-adapters.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/ADR-006-serialization.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/ADR-007-cache.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/ADR-008-transport.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/DESIGN.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/architecture/YAML-TRUST-BOUNDARY.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/PUBLIC-BENCHMARK-METHODOLOGY.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/competitor-manifest.template.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/corpus.schema.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/corpus.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/model-matrix.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/docs/benchmarks/regression-baseline.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/glama.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/README.md +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/client.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/configure_apps.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/control.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/install.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/ops/vision/worker_shell.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/__main__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/cache/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/cache/codec.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/data/synonyms.yaml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/detection.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/diagnostics.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/errors.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/ingestion/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/ingestion/cpython_versions.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/ingestion/inventory.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/ingestion/publish.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/ingestion/sphinx_json.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/py.typed +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/retrieval/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/retrieval/budget.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/retrieval/query.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/retrieval/ranker.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/cache.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/compare.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/content.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/observability.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/package_docs.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/persistent_cache.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/search.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/version.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/services/version_resolution.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/storage/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/storage/db.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/storage/schema.sql +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/fixtures/corpus.sample.yml +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_adapters.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_claude_tokens.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_competitor_adapters.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_corpus_validation.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_model_matrix.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_python_docs_mcp_adapter.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_regression.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_report.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_runner.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/benchmarks/test_scoring.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/cache/__init__.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/cache/test_codec.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/conftest.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/retrieval_regression_cases.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/sample_broken.fjson +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/sample_library.fjson +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/schema-get_docs-input.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/schema-get_docs-output.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/schema-list_versions-output.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/schema-search_docs-input.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/fixtures/schema-search_docs-output.json +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_ci_workflows.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_compare_versions.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_compare_versions_spike.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_detection.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_doctor.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_ingestion.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_mcp_get_docs_cache_smoke.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_multi_version.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_package_docs.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_packaging.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_persistent_docs_cache.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_phase1_integration.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_publish.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_release_gate.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_retrieval.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_schema.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_schema_snapshot.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_search_spare_slot.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_search_symbol_fallback.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_stability.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_stdio_hygiene.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_stdio_smoke.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_synonyms.py +0 -0
- {python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_vision_control.py +0 -0
|
@@ -4,6 +4,27 @@ All notable changes to `python-docs-mcp-server` are documented here.
|
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
5
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.0] — Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added the seventh tool, `whatsnew_for_version`, for offline, read-only
|
|
12
|
+
discovery of indexed official Python release notes for an explicit version.
|
|
13
|
+
Filtered pagination preserves canonical sections, their order and fields,
|
|
14
|
+
and advances through matching sections without gaps. Returned sections have
|
|
15
|
+
nonempty excerpts; truncated excerpts include canonical `get_docs`
|
|
16
|
+
continuation instructions. A section that cannot fit with nonempty content
|
|
17
|
+
and its required canonical fields returns an explicit bounded error.
|
|
18
|
+
Successful responses are bounded to 20,000 Unicode characters in the model
|
|
19
|
+
JSON, not the MCP wire envelope.
|
|
20
|
+
([#153](https://github.com/ayhammouda/python-docs-mcp-server/pull/153))
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Aligned package and MCP Registry metadata to `0.4.0`. Canonical,
|
|
25
|
+
version-aware documentation queries remain offline and read-only;
|
|
26
|
+
distribution remains free under the MIT license.
|
|
27
|
+
|
|
7
28
|
## [0.3.2] — 2026-10-10
|
|
8
29
|
|
|
9
30
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-docs-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: The canonical Python stdlib oracle for AI coding agents — exact symbols, exact sections, exact versions, offline, always free, always MIT, token-frugal.
|
|
5
5
|
Author-email: Aymen Hammouda <hammouda.aymen@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -79,7 +79,7 @@ tools for lookup and section retrieval.
|
|
|
79
79
|
- page and section retrieval with truncation and pagination
|
|
80
80
|
- a local SQLite + FTS5 index; no runtime web scraping
|
|
81
81
|
- results for each Python version you index
|
|
82
|
-
-
|
|
82
|
+
- seven read-only MCP tools
|
|
83
83
|
|
|
84
84
|
## Quick example
|
|
85
85
|
|
|
@@ -247,7 +247,7 @@ Contributor commands and validation steps live in
|
|
|
247
247
|
|
|
248
248
|
## Tools
|
|
249
249
|
|
|
250
|
-
The server currently exposes
|
|
250
|
+
The server currently exposes seven MCP tools:
|
|
251
251
|
|
|
252
252
|
| Tool | Description |
|
|
253
253
|
|------|-------------|
|
|
@@ -257,6 +257,7 @@ The server currently exposes six MCP tools:
|
|
|
257
257
|
| `list_versions` | List all indexed Python versions with metadata. |
|
|
258
258
|
| `detect_python_version` | Detect the user's local Python version and report whether that version has been indexed. |
|
|
259
259
|
| `compare_versions` | Diff a Python stdlib symbol between two indexed versions. Returns `change=added|removed|changed|unchanged` with optional `new_in`, `changed_in`, `deprecated_in`, `signature_delta` (advisory heuristic), `see_also_added/removed`, `section_diff`, and `note` deltas. Token-frugal — emits only changed fields, not full content. |
|
|
260
|
+
| `whatsnew_for_version` | Browse nonempty sections of the already-indexed official What's New page for one Python version. Optional `kind` filters by conservative heading-hierarchy category. `start_index` and `max_sections` paginate the filtered results; the structured response is capped at 20,000 characters, and truncated excerpts include a stable anchor and `get_docs` follow-up hint. Canonical titles and anchors are unchanged; if one section cannot fit its metadata plus nonempty indexed text and any required continuation hint, an explicit bounded error is returned. Page size may be reduced to reserve usable excerpts. The cap counts Unicode characters in the model JSON, excluding the MCP envelope. Querying is offline. |
|
|
260
261
|
|
|
261
262
|
For `search_docs` with `kind="auto"` and an explicit indexed `version`, the
|
|
262
263
|
original lookup and full-text search run first. If both return no hits, the
|
|
@@ -50,7 +50,7 @@ tools for lookup and section retrieval.
|
|
|
50
50
|
- page and section retrieval with truncation and pagination
|
|
51
51
|
- a local SQLite + FTS5 index; no runtime web scraping
|
|
52
52
|
- results for each Python version you index
|
|
53
|
-
-
|
|
53
|
+
- seven read-only MCP tools
|
|
54
54
|
|
|
55
55
|
## Quick example
|
|
56
56
|
|
|
@@ -218,7 +218,7 @@ Contributor commands and validation steps live in
|
|
|
218
218
|
|
|
219
219
|
## Tools
|
|
220
220
|
|
|
221
|
-
The server currently exposes
|
|
221
|
+
The server currently exposes seven MCP tools:
|
|
222
222
|
|
|
223
223
|
| Tool | Description |
|
|
224
224
|
|------|-------------|
|
|
@@ -228,6 +228,7 @@ The server currently exposes six MCP tools:
|
|
|
228
228
|
| `list_versions` | List all indexed Python versions with metadata. |
|
|
229
229
|
| `detect_python_version` | Detect the user's local Python version and report whether that version has been indexed. |
|
|
230
230
|
| `compare_versions` | Diff a Python stdlib symbol between two indexed versions. Returns `change=added|removed|changed|unchanged` with optional `new_in`, `changed_in`, `deprecated_in`, `signature_delta` (advisory heuristic), `see_also_added/removed`, `section_diff`, and `note` deltas. Token-frugal — emits only changed fields, not full content. |
|
|
231
|
+
| `whatsnew_for_version` | Browse nonempty sections of the already-indexed official What's New page for one Python version. Optional `kind` filters by conservative heading-hierarchy category. `start_index` and `max_sections` paginate the filtered results; the structured response is capped at 20,000 characters, and truncated excerpts include a stable anchor and `get_docs` follow-up hint. Canonical titles and anchors are unchanged; if one section cannot fit its metadata plus nonempty indexed text and any required continuation hint, an explicit bounded error is returned. Page size may be reduced to reserve usable excerpts. The cap counts Unicode characters in the model JSON, excluding the MCP envelope. Querying is offline. |
|
|
231
232
|
|
|
232
233
|
For `search_docs` with `kind="auto"` and an explicit indexed `version`, the
|
|
233
234
|
original lookup and full-text search run first. If both return no hits, the
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-docs-mcp-server"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "The canonical Python stdlib oracle for AI coding agents — exact symbols, exact sections, exact versions, offline, always free, always MIT, token-frugal."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"url": "https://github.com/ayhammouda/python-docs-mcp-server",
|
|
9
9
|
"source": "github"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.4.0",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "pypi",
|
|
15
15
|
"registryBaseUrl": "https://pypi.org",
|
|
16
16
|
"identifier": "python-docs-mcp-server",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.4.0",
|
|
18
18
|
"runtimeHint": "uvx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
@@ -16,6 +16,7 @@ from mcp_server_python_docs.services.package_docs import PackageDocsService
|
|
|
16
16
|
from mcp_server_python_docs.services.persistent_cache import PersistentDocsCache
|
|
17
17
|
from mcp_server_python_docs.services.search import SearchService
|
|
18
18
|
from mcp_server_python_docs.services.version import VersionService
|
|
19
|
+
from mcp_server_python_docs.services.whatsnew import WhatsNewService
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
@dataclass
|
|
@@ -28,6 +29,7 @@ class AppContext:
|
|
|
28
29
|
content_service: ContentService
|
|
29
30
|
compare_service: CompareService
|
|
30
31
|
version_service: VersionService
|
|
32
|
+
whatsnew_service: WhatsNewService
|
|
31
33
|
package_docs_service: PackageDocsService = field(default_factory=PackageDocsService)
|
|
32
34
|
persistent_docs_cache: PersistentDocsCache | None = None
|
|
33
35
|
synonyms: dict[str, list[str]] = field(default_factory=dict)
|
{python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/models.py
RENAMED
|
@@ -301,3 +301,29 @@ class CompareVersionsResult(BaseModel):
|
|
|
301
301
|
"therefore based on symbol presence alone."
|
|
302
302
|
),
|
|
303
303
|
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# --- whatsnew_for_version models ---
|
|
307
|
+
|
|
308
|
+
WhatsNewKind = Literal[
|
|
309
|
+
"new_module", "new_feature", "deprecation", "removal",
|
|
310
|
+
"performance", "syntax", "other",
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class WhatsNewSection(BaseModel):
|
|
315
|
+
"""A nonempty, bounded section from an indexed official release page."""
|
|
316
|
+
|
|
317
|
+
title: str = Field(description="Official section heading")
|
|
318
|
+
anchor: str = Field(description="Stable section anchor for get_docs follow-up")
|
|
319
|
+
body: str = Field(
|
|
320
|
+
description="Bounded section text; truncated sections include a get_docs hint"
|
|
321
|
+
)
|
|
322
|
+
kind: WhatsNewKind = Field(description="Conservative heading-hierarchy category")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class WhatsNewResult(BaseModel):
|
|
326
|
+
"""Paginated sections from What's New in one Python version."""
|
|
327
|
+
|
|
328
|
+
sections: list[WhatsNewSection] = Field(default_factory=list)
|
|
329
|
+
next_start_index: int | None = Field(default=None)
|
{python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/src/mcp_server_python_docs/server.py
RENAMED
|
@@ -34,6 +34,8 @@ from mcp_server_python_docs.models import (
|
|
|
34
34
|
ListVersionsResult,
|
|
35
35
|
PackageDocsResult,
|
|
36
36
|
SearchDocsResult,
|
|
37
|
+
WhatsNewKind,
|
|
38
|
+
WhatsNewResult,
|
|
37
39
|
)
|
|
38
40
|
from mcp_server_python_docs.services.compare import CompareService
|
|
39
41
|
from mcp_server_python_docs.services.content import ContentService
|
|
@@ -41,6 +43,7 @@ from mcp_server_python_docs.services.package_docs import PackageDocsService
|
|
|
41
43
|
from mcp_server_python_docs.services.persistent_cache import PersistentDocsCache
|
|
42
44
|
from mcp_server_python_docs.services.search import SearchService
|
|
43
45
|
from mcp_server_python_docs.services.version import VersionService
|
|
46
|
+
from mcp_server_python_docs.services.whatsnew import WhatsNewService
|
|
44
47
|
from mcp_server_python_docs.storage.db import (
|
|
45
48
|
get_cache_dir,
|
|
46
49
|
get_index_path,
|
|
@@ -162,6 +165,7 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
|
162
165
|
content_svc = ContentService(db, persistent_cache=persistent_docs_cache)
|
|
163
166
|
compare_svc = CompareService(db, content_svc)
|
|
164
167
|
version_svc = VersionService(db)
|
|
168
|
+
whatsnew_svc = WhatsNewService(db)
|
|
165
169
|
package_docs_svc = PackageDocsService()
|
|
166
170
|
|
|
167
171
|
# Detect user's Python version and match to indexed versions
|
|
@@ -188,6 +192,7 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
|
188
192
|
content_service=content_svc,
|
|
189
193
|
compare_service=compare_svc,
|
|
190
194
|
version_service=version_svc,
|
|
195
|
+
whatsnew_service=whatsnew_svc,
|
|
191
196
|
package_docs_service=package_docs_svc,
|
|
192
197
|
persistent_docs_cache=persistent_docs_cache,
|
|
193
198
|
detected_python_version=matched,
|
|
@@ -411,6 +416,29 @@ def create_server() -> FastMCP:
|
|
|
411
416
|
logger.exception("Unexpected error in compare_versions")
|
|
412
417
|
raise ToolError(f"Internal error: {type(e).__name__}")
|
|
413
418
|
|
|
419
|
+
@mcp.tool(annotations=_TOOL_ANNOTATIONS)
|
|
420
|
+
def whatsnew_for_version(
|
|
421
|
+
version: CompareVersionParam,
|
|
422
|
+
kind: WhatsNewKind | None = None,
|
|
423
|
+
start_index: StartIndexParam = 0,
|
|
424
|
+
max_sections: MaxResultsParam = 20,
|
|
425
|
+
ctx: Context = None, # type: ignore[assignment]
|
|
426
|
+
) -> WhatsNewResult:
|
|
427
|
+
"""Browse indexed official What's New sections in document order.
|
|
428
|
+
|
|
429
|
+
Use kind to narrow the release topics. Sections are bounded; for full
|
|
430
|
+
text use get_docs(slug='whatsnew/X.Y', version='X.Y', anchor=section.anchor).
|
|
431
|
+
Pagination start_index counts nonempty sections after kind filtering.
|
|
432
|
+
"""
|
|
433
|
+
app_ctx: AppContext = ctx.request_context.lifespan_context
|
|
434
|
+
try:
|
|
435
|
+
return app_ctx.whatsnew_service.get(version, kind, start_index, max_sections)
|
|
436
|
+
except DocsServerError as e:
|
|
437
|
+
raise ToolError(str(e))
|
|
438
|
+
except Exception as e:
|
|
439
|
+
logger.exception("Unexpected error in whatsnew_for_version")
|
|
440
|
+
raise ToolError(f"Internal error: {type(e).__name__}")
|
|
441
|
+
|
|
414
442
|
# SRVR-07: _meta hint for get_docs tool.
|
|
415
443
|
# FastMCP 1.27 does not expose a public API for setting _meta on tool
|
|
416
444
|
# definitions. Deferred until the mcp SDK adds _meta support to the
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Offline, version-scoped discovery of sections on the official What's New page."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sqlite3
|
|
5
|
+
|
|
6
|
+
from mcp_server_python_docs.errors import DocsServerError, PageNotFoundError
|
|
7
|
+
from mcp_server_python_docs.models import WhatsNewKind, WhatsNewResult, WhatsNewSection
|
|
8
|
+
from mcp_server_python_docs.retrieval.budget import apply_budget
|
|
9
|
+
from mcp_server_python_docs.services.version_resolution import validate_version
|
|
10
|
+
|
|
11
|
+
# Cap the whole structured response, including metadata and continuation hints.
|
|
12
|
+
_RESULT_CHARS = 20_000
|
|
13
|
+
_BODY_CHARS = 7800
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _body_cost(section: WhatsNewSection, body: str) -> int:
|
|
17
|
+
return (
|
|
18
|
+
len(section.model_copy(update={"body": body}).model_dump_json())
|
|
19
|
+
- len(section.model_copy(update={"body": ""}).model_dump_json())
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _fit_body(section: WhatsNewSection, body: str, hint: str, share: int) -> str:
|
|
24
|
+
# Full text has no hint: its cost is outside the monotonic truncated search.
|
|
25
|
+
if len(body) <= _BODY_CHARS and _body_cost(section, body) <= share:
|
|
26
|
+
return body
|
|
27
|
+
low, high = 1, min(_BODY_CHARS, len(body) - 1)
|
|
28
|
+
chosen = section.body
|
|
29
|
+
while low <= high:
|
|
30
|
+
middle = (low + high) // 2
|
|
31
|
+
excerpt, truncated, _ = apply_budget(body, middle)
|
|
32
|
+
candidate = excerpt + hint if truncated else excerpt
|
|
33
|
+
if _body_cost(section, candidate) <= share:
|
|
34
|
+
chosen = candidate
|
|
35
|
+
low = middle + 1
|
|
36
|
+
else:
|
|
37
|
+
high = middle - 1
|
|
38
|
+
return chosen
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _classify(headings: tuple[str, ...]) -> WhatsNewKind:
|
|
42
|
+
"""Classify only when an explicit heading or its ancestry supports it.
|
|
43
|
+
|
|
44
|
+
A leaf such as ``asyncio`` or an opaque ``#id6`` cannot classify itself.
|
|
45
|
+
Nearest relevant ancestor wins, so a future pending removal inside a
|
|
46
|
+
deprecations chapter remains a deprecation, not a removal in this release.
|
|
47
|
+
"""
|
|
48
|
+
for heading in reversed(headings):
|
|
49
|
+
title = heading.casefold().strip()
|
|
50
|
+
if title.startswith("pending removal"):
|
|
51
|
+
return "deprecation"
|
|
52
|
+
if title in {"deprecated", "new deprecations", "deprecated c apis"}:
|
|
53
|
+
return "deprecation"
|
|
54
|
+
if title in {"removed", "removed modules and apis", "removed c apis"}:
|
|
55
|
+
return "removal"
|
|
56
|
+
if title in {"new modules"}:
|
|
57
|
+
return "new_module"
|
|
58
|
+
if title in {"optimizations", "performance"}:
|
|
59
|
+
return "performance"
|
|
60
|
+
if "syntax" in title or title == "syntax changes":
|
|
61
|
+
return "syntax"
|
|
62
|
+
if title in {"new features", "new features related to type hints", "improved modules"}:
|
|
63
|
+
return "new_feature"
|
|
64
|
+
return "other"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class WhatsNewService:
|
|
68
|
+
"""Read the already-indexed release page; never fetch at query time."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, db: sqlite3.Connection) -> None:
|
|
71
|
+
self._db = db
|
|
72
|
+
|
|
73
|
+
def get(
|
|
74
|
+
self,
|
|
75
|
+
version: str,
|
|
76
|
+
kind: WhatsNewKind | None = None,
|
|
77
|
+
start_index: int = 0,
|
|
78
|
+
max_sections: int = 20,
|
|
79
|
+
) -> WhatsNewResult:
|
|
80
|
+
validate_version(self._db, version)
|
|
81
|
+
if start_index < 0 or not 1 <= max_sections <= 20:
|
|
82
|
+
raise ValueError("start_index must be nonnegative and max_sections must be 1..20")
|
|
83
|
+
slug = f"whatsnew/{version}"
|
|
84
|
+
document = self._db.execute(
|
|
85
|
+
"SELECT d.id FROM documents d JOIN doc_sets ds ON ds.id = d.doc_set_id "
|
|
86
|
+
"WHERE ds.version = ? AND ds.source = 'python-docs' "
|
|
87
|
+
"AND ds.language = 'en' AND d.slug = ?",
|
|
88
|
+
(version, slug),
|
|
89
|
+
).fetchone()
|
|
90
|
+
if document is None:
|
|
91
|
+
raise PageNotFoundError(
|
|
92
|
+
f"official What's New page {slug!r} is not indexed for Python {version}; "
|
|
93
|
+
"rebuild the full documentation index (without --skip-content)"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
rows = self._db.execute(
|
|
97
|
+
"SELECT anchor, heading, level, content_text FROM sections "
|
|
98
|
+
"WHERE document_id = ? ORDER BY ordinal, id",
|
|
99
|
+
(document["id"],),
|
|
100
|
+
).fetchall()
|
|
101
|
+
ancestors: list[tuple[int, str]] = []
|
|
102
|
+
matches: list[tuple[sqlite3.Row, WhatsNewKind]] = []
|
|
103
|
+
for row in rows:
|
|
104
|
+
level = max(1, int(row["level"]))
|
|
105
|
+
# Sphinx levels may skip; compare actual levels, not stack depth.
|
|
106
|
+
while ancestors and ancestors[-1][0] >= level:
|
|
107
|
+
ancestors.pop()
|
|
108
|
+
ancestors.append((level, row["heading"]))
|
|
109
|
+
body = row["content_text"].strip()
|
|
110
|
+
if not body:
|
|
111
|
+
continue
|
|
112
|
+
category = _classify(tuple(title for _, title in ancestors))
|
|
113
|
+
if kind is not None and category != kind:
|
|
114
|
+
continue
|
|
115
|
+
matches.append((row, category))
|
|
116
|
+
# Reserve indexed text and any required hint before admitting each section.
|
|
117
|
+
# Stop at the first non-fitting section; the next offset must never skip it.
|
|
118
|
+
page: list[tuple[str, str]] = []
|
|
119
|
+
sections: list[WhatsNewSection] = []
|
|
120
|
+
minimum_costs: list[int] = []
|
|
121
|
+
result = WhatsNewResult()
|
|
122
|
+
for row, category in matches[start_index : start_index + max_sections]:
|
|
123
|
+
section = WhatsNewSection(
|
|
124
|
+
title=row["heading"], anchor=row["anchor"], body="", kind=category
|
|
125
|
+
)
|
|
126
|
+
next_index = start_index + len(sections) + 1
|
|
127
|
+
candidate = WhatsNewResult(
|
|
128
|
+
sections=[*sections, section],
|
|
129
|
+
next_start_index=next_index if next_index < len(matches) else None,
|
|
130
|
+
)
|
|
131
|
+
if not sections and len(candidate.model_dump_json()) > _RESULT_CHARS:
|
|
132
|
+
raise DocsServerError(
|
|
133
|
+
f"What's New section at start_index={start_index} has metadata "
|
|
134
|
+
"larger than the 20,000-character response limit"
|
|
135
|
+
)
|
|
136
|
+
body = row["content_text"].strip()
|
|
137
|
+
hint = (
|
|
138
|
+
"\n\n[Section truncated. Continue with "
|
|
139
|
+
f"get_docs(slug={slug!r}, version={version!r}, "
|
|
140
|
+
f"anchor={row['anchor']!r}).]"
|
|
141
|
+
)
|
|
142
|
+
excerpt, truncated, _ = apply_budget(body, 1)
|
|
143
|
+
minimum = excerpt + hint if truncated else excerpt
|
|
144
|
+
if len(body) <= _BODY_CHARS and (
|
|
145
|
+
_body_cost(section, body) <= _body_cost(section, minimum)
|
|
146
|
+
):
|
|
147
|
+
minimum = body
|
|
148
|
+
section.body = minimum
|
|
149
|
+
if len(candidate.model_dump_json()) > _RESULT_CHARS:
|
|
150
|
+
if not sections:
|
|
151
|
+
raise DocsServerError(
|
|
152
|
+
f"What's New section at start_index={start_index} cannot fit "
|
|
153
|
+
"nonempty indexed text and its required continuation within "
|
|
154
|
+
"the 20,000-character response limit"
|
|
155
|
+
)
|
|
156
|
+
break
|
|
157
|
+
page.append((body, hint))
|
|
158
|
+
minimum_costs.append(_body_cost(section, minimum))
|
|
159
|
+
sections.append(section)
|
|
160
|
+
result = candidate
|
|
161
|
+
remaining = _RESULT_CHARS - len(result.model_dump_json()) + sum(minimum_costs)
|
|
162
|
+
reserved = sum(minimum_costs)
|
|
163
|
+
for index, (body, hint) in enumerate(page):
|
|
164
|
+
section = sections[index]
|
|
165
|
+
reserved -= minimum_costs[index]
|
|
166
|
+
share = min(
|
|
167
|
+
max(remaining // (len(page) - index), minimum_costs[index]),
|
|
168
|
+
remaining - reserved,
|
|
169
|
+
)
|
|
170
|
+
section.body = _fit_body(section, body, hint, share)
|
|
171
|
+
remaining -= _body_cost(section, section.body)
|
|
172
|
+
return result
|
{python_docs_mcp_server-0.3.2 → python_docs_mcp_server-0.4.0}/tests/test_retrieval_regression.py
RENAMED
|
@@ -14,6 +14,7 @@ from mcp_server_python_docs.services.compare import CompareService
|
|
|
14
14
|
from mcp_server_python_docs.services.content import ContentService
|
|
15
15
|
from mcp_server_python_docs.services.search import SearchService
|
|
16
16
|
from mcp_server_python_docs.services.version import VersionService
|
|
17
|
+
from mcp_server_python_docs.services.whatsnew import WhatsNewService
|
|
17
18
|
from mcp_server_python_docs.storage.db import bootstrap_schema, get_readwrite_connection
|
|
18
19
|
|
|
19
20
|
_CASES_PATH = Path(__file__).parent / "fixtures" / "retrieval_regression_cases.json"
|
|
@@ -134,6 +135,7 @@ def _make_app_context(db, detected_python_version: str | None) -> AppContext:
|
|
|
134
135
|
content_service=content_service,
|
|
135
136
|
compare_service=CompareService(db, content_service),
|
|
136
137
|
version_service=VersionService(db),
|
|
138
|
+
whatsnew_service=WhatsNewService(db),
|
|
137
139
|
detected_python_version=detected_python_version,
|
|
138
140
|
detected_python_source="test fixture",
|
|
139
141
|
)
|
|
@@ -447,12 +447,15 @@ class TestToolRegistration:
|
|
|
447
447
|
annotations.openWorldHint is False
|
|
448
448
|
), f"{name} openWorldHint should be False"
|
|
449
449
|
|
|
450
|
-
def
|
|
450
|
+
def test_seven_tools_registered(self):
|
|
451
451
|
from mcp_server_python_docs.server import create_server
|
|
452
452
|
|
|
453
453
|
server = create_server()
|
|
454
454
|
tools = server._tool_manager._tools
|
|
455
|
-
assert
|
|
455
|
+
assert set(tools) == {
|
|
456
|
+
"search_docs", "get_docs", "lookup_package_docs", "list_versions",
|
|
457
|
+
"detect_python_version", "compare_versions", "whatsnew_for_version",
|
|
458
|
+
}
|
|
456
459
|
|
|
457
460
|
def test_runtime_tool_schemas_include_input_constraints(self):
|
|
458
461
|
import anyio
|