python-docs-mcp-server 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- mcp_server_python_docs/__main__.py +35 -1
- mcp_server_python_docs/cache/__init__.py +1 -0
- mcp_server_python_docs/cache/codec.py +114 -0
- mcp_server_python_docs/ingestion/cpython_versions.py +28 -7
- mcp_server_python_docs/services/persistent_cache.py +35 -7
- {python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/METADATA +3 -2
- {python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/RECORD +10 -8
- {python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/WHEEL +0 -0
- {python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -65,6 +65,8 @@ logging.basicConfig(
|
|
|
65
65
|
logger = logging.getLogger("mcp_server_python_docs")
|
|
66
66
|
|
|
67
67
|
# === Now safe to import everything else ===
|
|
68
|
+
import subprocess # noqa: E402
|
|
69
|
+
|
|
68
70
|
import click # noqa: E402
|
|
69
71
|
|
|
70
72
|
from mcp_server_python_docs.ingestion.cpython_versions import ( # noqa: E402
|
|
@@ -110,6 +112,33 @@ def serve() -> None:
|
|
|
110
112
|
pass # Client disconnected (HYGN-03)
|
|
111
113
|
|
|
112
114
|
|
|
115
|
+
def _verify_cpython_source_sha(
|
|
116
|
+
clone_dir: str,
|
|
117
|
+
*,
|
|
118
|
+
version: str,
|
|
119
|
+
tag: str,
|
|
120
|
+
expected_sha: str,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Abort the docs build if a CPython tag resolves to unexpected content."""
|
|
123
|
+
rev_parse = subprocess.run(
|
|
124
|
+
["git", "-C", clone_dir, "rev-parse", "HEAD"],
|
|
125
|
+
check=True,
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
)
|
|
129
|
+
actual_sha = rev_parse.stdout.strip()
|
|
130
|
+
if actual_sha != expected_sha:
|
|
131
|
+
logger.error(
|
|
132
|
+
"CPython %s source integrity check failed: tag %s "
|
|
133
|
+
"resolved to %s, expected %s. Aborting build.",
|
|
134
|
+
version,
|
|
135
|
+
tag,
|
|
136
|
+
actual_sha,
|
|
137
|
+
expected_sha,
|
|
138
|
+
)
|
|
139
|
+
raise SystemExit(1)
|
|
140
|
+
|
|
141
|
+
|
|
113
142
|
@main.command("build-index")
|
|
114
143
|
@click.option(
|
|
115
144
|
"--versions",
|
|
@@ -124,7 +153,6 @@ def serve() -> None:
|
|
|
124
153
|
def build_index(versions: str, skip_content: bool) -> None:
|
|
125
154
|
"""Build the documentation index from objects.inv and Sphinx JSON."""
|
|
126
155
|
import shutil
|
|
127
|
-
import subprocess
|
|
128
156
|
import tempfile
|
|
129
157
|
import venv
|
|
130
158
|
from pathlib import Path
|
|
@@ -224,6 +252,12 @@ def build_index(versions: str, skip_content: bool) -> None:
|
|
|
224
252
|
capture_output=True,
|
|
225
253
|
text=True,
|
|
226
254
|
)
|
|
255
|
+
_verify_cpython_source_sha(
|
|
256
|
+
clone_dir,
|
|
257
|
+
version=version,
|
|
258
|
+
tag=config["tag"],
|
|
259
|
+
expected_sha=config["sha"],
|
|
260
|
+
)
|
|
227
261
|
|
|
228
262
|
# Create dedicated Sphinx venv (INGR-C-02)
|
|
229
263
|
venv_dir = os.path.join(clone_dir, "_sphinx_venv")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cache support utilities."""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Versioned codecs for cache-at-rest payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
import zstandard as zstd
|
|
9
|
+
|
|
10
|
+
_SUPPORTED_CODECS = ["none", "zstd", "zstd-dict-v1"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class _Codec:
|
|
15
|
+
encode: Callable[[str, object | None], bytes]
|
|
16
|
+
decode: Callable[[bytes, object | None], str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def list_supported() -> list[str]:
|
|
20
|
+
"""Return codec ids in stable preference order."""
|
|
21
|
+
|
|
22
|
+
return list(_SUPPORTED_CODECS)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encode(text: str, codec: str, *, dictionary: object | None = None) -> bytes:
|
|
26
|
+
"""Encode text using a supported cache codec."""
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
handler = _REGISTRY[codec]
|
|
30
|
+
except KeyError as e:
|
|
31
|
+
raise ValueError(f"Unsupported cache codec: {codec}") from e
|
|
32
|
+
return handler.encode(text, dictionary)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def decode(blob: bytes, codec: str, *, dictionary: object | None = None) -> str:
|
|
36
|
+
"""Decode text using the codec stored with the cache row."""
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
handler = _REGISTRY[codec]
|
|
40
|
+
except KeyError as e:
|
|
41
|
+
raise ValueError(f"Unsupported cache codec: {codec}") from e
|
|
42
|
+
return handler.decode(blob, dictionary)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _encode_none(text: str, dictionary: object | None) -> bytes:
|
|
46
|
+
_reject_dictionary("none", dictionary)
|
|
47
|
+
return text.encode("utf-8")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _decode_none(blob: bytes, dictionary: object | None) -> str:
|
|
51
|
+
_reject_dictionary("none", dictionary)
|
|
52
|
+
return blob.decode("utf-8")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _encode_zstd(text: str, dictionary: object | None) -> bytes:
|
|
56
|
+
_reject_dictionary("zstd", dictionary)
|
|
57
|
+
try:
|
|
58
|
+
return zstd.ZstdCompressor().compress(text.encode("utf-8"))
|
|
59
|
+
except zstd.ZstdError as e:
|
|
60
|
+
raise ValueError(f"zstd encode failed: {e}") from e
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _decode_zstd(blob: bytes, dictionary: object | None) -> str:
|
|
64
|
+
_reject_dictionary("zstd", dictionary)
|
|
65
|
+
try:
|
|
66
|
+
return zstd.ZstdDecompressor().decompress(blob).decode("utf-8")
|
|
67
|
+
except zstd.ZstdError as e:
|
|
68
|
+
raise ValueError(f"zstd decode failed: {e}") from e
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _encode_zstd_dict(text: str, dictionary: object | None) -> bytes:
|
|
72
|
+
try:
|
|
73
|
+
return zstd.ZstdCompressor(dict_data=_coerce_dictionary(dictionary)).compress(
|
|
74
|
+
text.encode("utf-8")
|
|
75
|
+
)
|
|
76
|
+
except zstd.ZstdError as e:
|
|
77
|
+
raise ValueError(f"zstd dictionary encode failed: {e}") from e
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _decode_zstd_dict(blob: bytes, dictionary: object | None) -> str:
|
|
81
|
+
try:
|
|
82
|
+
return (
|
|
83
|
+
zstd.ZstdDecompressor(dict_data=_coerce_dictionary(dictionary))
|
|
84
|
+
.decompress(blob)
|
|
85
|
+
.decode("utf-8")
|
|
86
|
+
)
|
|
87
|
+
except zstd.ZstdError as e:
|
|
88
|
+
raise ValueError(f"zstd dictionary decode failed: {e}") from e
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _reject_dictionary(codec: str, dictionary: object | None) -> None:
|
|
92
|
+
if dictionary is not None:
|
|
93
|
+
raise ValueError(f"Codec {codec!r} does not use a dictionary")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _coerce_dictionary(dictionary: object | None) -> zstd.ZstdCompressionDict:
|
|
97
|
+
if dictionary is None:
|
|
98
|
+
raise ValueError("Codec 'zstd-dict-v1' requires an explicit dictionary")
|
|
99
|
+
if isinstance(dictionary, zstd.ZstdCompressionDict):
|
|
100
|
+
return dictionary
|
|
101
|
+
if isinstance(dictionary, bytes):
|
|
102
|
+
return zstd.ZstdCompressionDict(dictionary)
|
|
103
|
+
if isinstance(dictionary, bytearray | memoryview):
|
|
104
|
+
return zstd.ZstdCompressionDict(bytes(dictionary))
|
|
105
|
+
raise TypeError(f"Unsupported zstd dictionary object: {type(dictionary).__name__}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_REGISTRY: dict[str, _Codec] = {
|
|
109
|
+
"none": _Codec(_encode_none, _decode_none),
|
|
110
|
+
"zstd": _Codec(_encode_zstd, _decode_zstd),
|
|
111
|
+
"zstd-dict-v1": _Codec(_encode_zstd_dict, _decode_zstd_dict),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
__all__ = ["decode", "encode", "list_supported"]
|
|
@@ -8,6 +8,7 @@ class CPythonDocsBuildConfig(TypedDict):
|
|
|
8
8
|
"""Build settings for one CPython documentation release."""
|
|
9
9
|
|
|
10
10
|
tag: str
|
|
11
|
+
sha: str
|
|
11
12
|
sphinx_pin: str
|
|
12
13
|
|
|
13
14
|
|
|
@@ -21,12 +22,32 @@ SUPPORTED_DOC_VERSIONS: Final[tuple[str, ...]] = (
|
|
|
21
22
|
|
|
22
23
|
SUPPORTED_DOC_VERSIONS_CSV: Final[str] = ",".join(SUPPORTED_DOC_VERSIONS)
|
|
23
24
|
|
|
24
|
-
# CPython git
|
|
25
|
-
#
|
|
25
|
+
# CPython git SHAs are authoritative for content build integrity. Tags are kept
|
|
26
|
+
# for human-readable version mapping, but a moved tag must fail verification.
|
|
26
27
|
CPYTHON_DOCS_BUILD_CONFIG: Final[dict[str, CPythonDocsBuildConfig]] = {
|
|
27
|
-
"3.10": {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
"3.10": {
|
|
29
|
+
"tag": "v3.10.20",
|
|
30
|
+
"sha": "842e987df856a5d4db37933c62a3456930a19092",
|
|
31
|
+
"sphinx_pin": "sphinx==3.4.3",
|
|
32
|
+
},
|
|
33
|
+
"3.11": {
|
|
34
|
+
"tag": "v3.11.15",
|
|
35
|
+
"sha": "2340a037f7450e70fccfe411e6531afb4d57a312",
|
|
36
|
+
"sphinx_pin": "sphinx~=7.2.0",
|
|
37
|
+
},
|
|
38
|
+
"3.12": {
|
|
39
|
+
"tag": "v3.12.13",
|
|
40
|
+
"sha": "3bb231a6a5dc02b95658877318bf61501a7209e9",
|
|
41
|
+
"sphinx_pin": "sphinx~=8.2.0",
|
|
42
|
+
},
|
|
43
|
+
"3.13": {
|
|
44
|
+
"tag": "v3.13.13",
|
|
45
|
+
"sha": "01104ce1beb3135c2e0c01ec835b994c1f55a1c0",
|
|
46
|
+
"sphinx_pin": "sphinx<9.0.0",
|
|
47
|
+
},
|
|
48
|
+
"3.14": {
|
|
49
|
+
"tag": "v3.14.4",
|
|
50
|
+
"sha": "23116f998f6789d8c2fbe5ed5b8146854c8c2a4f",
|
|
51
|
+
"sphinx_pin": "sphinx<9.0.0",
|
|
52
|
+
},
|
|
32
53
|
}
|
|
@@ -10,10 +10,13 @@ from typing import NamedTuple
|
|
|
10
10
|
|
|
11
11
|
from pydantic import ValidationError
|
|
12
12
|
|
|
13
|
+
from mcp_server_python_docs.cache.codec import decode as decode_cache_payload
|
|
14
|
+
from mcp_server_python_docs.cache.codec import encode as encode_cache_payload
|
|
13
15
|
from mcp_server_python_docs.models import GetDocsResult
|
|
14
16
|
|
|
15
17
|
logger = logging.getLogger(__name__)
|
|
16
18
|
_NO_ANCHOR_KEY = "\x00mcp-python-docs:no-anchor\x00"
|
|
19
|
+
DEFAULT_RETRIEVED_DOCS_CACHE_CODEC = "zstd"
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class CacheStats(NamedTuple):
|
|
@@ -25,8 +28,15 @@ class CacheStats(NamedTuple):
|
|
|
25
28
|
class PersistentDocsCache:
|
|
26
29
|
"""Persist get_docs results by index fingerprint, version, and request identity."""
|
|
27
30
|
|
|
28
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
cache_path: Path,
|
|
34
|
+
index_path: Path,
|
|
35
|
+
*,
|
|
36
|
+
default_codec: str = DEFAULT_RETRIEVED_DOCS_CACHE_CODEC,
|
|
37
|
+
) -> None:
|
|
29
38
|
self._cache_path = Path(cache_path)
|
|
39
|
+
self._default_codec = default_codec
|
|
30
40
|
# Set after fingerprint stat succeeds; stays "" if init fails so the
|
|
31
41
|
# cache disables cleanly without leaking partial state.
|
|
32
42
|
self._fingerprint = ""
|
|
@@ -47,9 +57,11 @@ class PersistentDocsCache:
|
|
|
47
57
|
"CREATE TABLE IF NOT EXISTS retrieved_docs_cache ("
|
|
48
58
|
"index_fingerprint TEXT NOT NULL, version TEXT NOT NULL, slug TEXT NOT NULL, "
|
|
49
59
|
"anchor TEXT NOT NULL, max_chars INTEGER NOT NULL, start_index INTEGER NOT NULL, "
|
|
50
|
-
"result_json TEXT NOT NULL,
|
|
60
|
+
"result_json TEXT NOT NULL, compression TEXT NOT NULL DEFAULT 'none', "
|
|
61
|
+
"created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
|
51
62
|
"PRIMARY KEY (index_fingerprint, version, slug, anchor, max_chars, start_index))"
|
|
52
63
|
)
|
|
64
|
+
self._ensure_compression_column()
|
|
53
65
|
self._conn.execute(
|
|
54
66
|
"DELETE FROM retrieved_docs_cache WHERE index_fingerprint != ?",
|
|
55
67
|
(self._fingerprint,),
|
|
@@ -74,6 +86,18 @@ class PersistentDocsCache:
|
|
|
74
86
|
def _anchor_key(anchor: str | None) -> str:
|
|
75
87
|
return _NO_ANCHOR_KEY if anchor is None else anchor
|
|
76
88
|
|
|
89
|
+
def _ensure_compression_column(self) -> None:
|
|
90
|
+
if self._conn is None:
|
|
91
|
+
return
|
|
92
|
+
columns = {
|
|
93
|
+
row[1] for row in self._conn.execute("PRAGMA table_info(retrieved_docs_cache)")
|
|
94
|
+
}
|
|
95
|
+
if "compression" not in columns:
|
|
96
|
+
self._conn.execute(
|
|
97
|
+
"ALTER TABLE retrieved_docs_cache "
|
|
98
|
+
"ADD COLUMN compression TEXT NOT NULL DEFAULT 'none'"
|
|
99
|
+
)
|
|
100
|
+
|
|
77
101
|
def stats(self) -> CacheStats:
|
|
78
102
|
return CacheStats(self._hits, self._misses, self._writes)
|
|
79
103
|
|
|
@@ -87,7 +111,8 @@ class PersistentDocsCache:
|
|
|
87
111
|
with self._lock:
|
|
88
112
|
try:
|
|
89
113
|
row = self._conn.execute(
|
|
90
|
-
"SELECT result_json FROM retrieved_docs_cache
|
|
114
|
+
"SELECT result_json, compression FROM retrieved_docs_cache "
|
|
115
|
+
"WHERE index_fingerprint = ? "
|
|
91
116
|
"AND version = ? AND slug = ? AND anchor = ? AND max_chars = ? "
|
|
92
117
|
"AND start_index = ?",
|
|
93
118
|
(
|
|
@@ -107,8 +132,10 @@ class PersistentDocsCache:
|
|
|
107
132
|
self._misses += 1
|
|
108
133
|
return None
|
|
109
134
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
payload = row[0].encode("utf-8") if isinstance(row[0], str) else bytes(row[0])
|
|
136
|
+
result_json = decode_cache_payload(payload, row[1])
|
|
137
|
+
result = GetDocsResult.model_validate_json(result_json)
|
|
138
|
+
except (ValidationError, ValueError, TypeError) as e:
|
|
112
139
|
self._misses += 1
|
|
113
140
|
logger.warning("Persistent docs cache entry ignored: %s", e)
|
|
114
141
|
return None
|
|
@@ -123,7 +150,7 @@ class PersistentDocsCache:
|
|
|
123
150
|
self._conn.execute(
|
|
124
151
|
"INSERT OR REPLACE INTO retrieved_docs_cache "
|
|
125
152
|
"(index_fingerprint, version, slug, anchor, max_chars, start_index, "
|
|
126
|
-
"result_json) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
153
|
+
"result_json, compression) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
127
154
|
(
|
|
128
155
|
self._fingerprint,
|
|
129
156
|
result.version,
|
|
@@ -131,7 +158,8 @@ class PersistentDocsCache:
|
|
|
131
158
|
self._anchor_key(result.anchor),
|
|
132
159
|
max_chars,
|
|
133
160
|
start_index,
|
|
134
|
-
result.model_dump_json(),
|
|
161
|
+
encode_cache_payload(result.model_dump_json(), self._default_codec),
|
|
162
|
+
self._default_codec,
|
|
135
163
|
),
|
|
136
164
|
)
|
|
137
165
|
self._conn.commit()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-docs-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: platformdirs>=4.0
|
|
|
22
22
|
Requires-Dist: pydantic<3.0,>=2.0.0
|
|
23
23
|
Requires-Dist: pyyaml<7.0,>=6.0
|
|
24
24
|
Requires-Dist: sphobjinv<3.0,>=2.4
|
|
25
|
+
Requires-Dist: zstandard>=0.23.0
|
|
25
26
|
Provides-Extra: pysqlite3
|
|
26
27
|
Requires-Dist: pysqlite3-binary>=0.5.4; extra == 'pysqlite3'
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
@@ -37,7 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
37
38
|
[](https://github.com/ayhammouda/python-docs-mcp-server/actions/workflows/codeql.yml)
|
|
38
39
|
[](https://scorecard.dev/viewer/?uri=github.com/ayhammouda/python-docs-mcp-server)
|
|
39
40
|
[](https://glama.ai/mcp/servers/ayhammouda/python-docs-mcp-server)
|
|
40
|
-
[](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.ayhammouda%2Fpython-docs-mcp-server)
|
|
41
42
|
[](LICENSE)
|
|
42
43
|
[](https://www.python.org/)
|
|
43
44
|
[](#why-use-it)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mcp_server_python_docs/__init__.py,sha256=sUtQT81momSAPMywIkgnR7BZxJHn53rpcjSE3CTAUws,499
|
|
2
|
-
mcp_server_python_docs/__main__.py,sha256=
|
|
2
|
+
mcp_server_python_docs/__main__.py,sha256=d6ZyRgkmOW7XUUgoGjOMEzCVoHL-aHuqmFKdxxkucvk,22165
|
|
3
3
|
mcp_server_python_docs/app_context.py,sha256=5kK4JcTiKgJGOV_gFE8aVktYdtCpeDbZunIrlt4SxyA,1403
|
|
4
4
|
mcp_server_python_docs/detection.py,sha256=bYYSfpMymHk9wehNW66oItX6yFUoCC2JXDRZLYpM0Vw,2860
|
|
5
5
|
mcp_server_python_docs/diagnostics.py,sha256=FyC6QDFLvozARpYuMnaKfJwaN6BCzVycME_SkNs2YIs,2370
|
|
@@ -7,9 +7,11 @@ mcp_server_python_docs/errors.py,sha256=XTOm1raJfxS2adaOPZdwj0SuhpMAXAdjBsF2-zTv
|
|
|
7
7
|
mcp_server_python_docs/models.py,sha256=A9XtcJPJFDa6jn7pHa4yDS1TPlnpIc3A7WEeKu4qqOw,10247
|
|
8
8
|
mcp_server_python_docs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
mcp_server_python_docs/server.py,sha256=8F27o2hTyi1rwSNoMo8lGYXCvhwEHS7cVHI7n9p5Y2M,15469
|
|
10
|
+
mcp_server_python_docs/cache/__init__.py,sha256=Eba8m5Xi3LYQKz5ictK9whVi_SZJWr5vZE1AtKDb26U,31
|
|
11
|
+
mcp_server_python_docs/cache/codec.py,sha256=tOEuHDqa6ziF2T05JubtDg_QnqvYRuheZijZUTX9lpE,3653
|
|
10
12
|
mcp_server_python_docs/data/synonyms.yaml,sha256=LgJxamZ9QT6Pykva7M1uM8juSd9ybHuv3vUOKrUFMg4,10696
|
|
11
13
|
mcp_server_python_docs/ingestion/__init__.py,sha256=Yd7Jk7hD7XG3eoBVQpYiG94EwJGfI3qXxVCpA8JszYg,66
|
|
12
|
-
mcp_server_python_docs/ingestion/cpython_versions.py,sha256=
|
|
14
|
+
mcp_server_python_docs/ingestion/cpython_versions.py,sha256=_28TX26eGEiiEV_6NthaPg8vwvwjuus2yVLn7ktvcyo,1426
|
|
13
15
|
mcp_server_python_docs/ingestion/inventory.py,sha256=5gQCWOo1tBiQgv-e_A9KR9n2xfP4YAnJrSmb0pFbzwY,5672
|
|
14
16
|
mcp_server_python_docs/ingestion/publish.py,sha256=gra87NgH1i4T92nSwf3sqp4AnKuuzQfrcE7ySnDgcSQ,17247
|
|
15
17
|
mcp_server_python_docs/ingestion/sphinx_json.py,sha256=OXrZ8LMeLFn2har2TVnWVNwMY9b5z1VCMioQdIE5gJE,20270
|
|
@@ -23,15 +25,15 @@ mcp_server_python_docs/services/compare.py,sha256=wWsgD8w9fl3L7gr-mcdd6ntY_V1pGt
|
|
|
23
25
|
mcp_server_python_docs/services/content.py,sha256=7O5XGbR9QWICMxYAd-cp_pPF86tNLcYAMIwyCP_i8dk,5342
|
|
24
26
|
mcp_server_python_docs/services/observability.py,sha256=rwnkIDelNkclHzuwsktGUoU2fzYCuiR1niVRmOwJHJg,4839
|
|
25
27
|
mcp_server_python_docs/services/package_docs.py,sha256=0Qq8PtRL_KdiTFUtllaj4KFHz6u9L903zcORVpYmR1U,5605
|
|
26
|
-
mcp_server_python_docs/services/persistent_cache.py,sha256=
|
|
28
|
+
mcp_server_python_docs/services/persistent_cache.py,sha256=p1uhTkwmXRP-v79jaIPDTEv9Z0G0iqCUnUDKmFQA6GA,6956
|
|
27
29
|
mcp_server_python_docs/services/search.py,sha256=Pjt4IDfnogWTGYKFJ_4ODbq1xbUZ0ybceD6yj7Ep6lw,4545
|
|
28
30
|
mcp_server_python_docs/services/version.py,sha256=nPkWhksZo7B4QIp-knlSEvVXFmKIcVUq6tWRTJAe5aA,1467
|
|
29
31
|
mcp_server_python_docs/services/version_resolution.py,sha256=rULBoqq97t0ZfSWSy-ecI3wALx5t_3OWuok1hZK8w0w,2643
|
|
30
32
|
mcp_server_python_docs/storage/__init__.py,sha256=YNeNr7FW9Vgvo79oXgKXE3eGP0WwWDqRgTZ7lg7MFW4,62
|
|
31
33
|
mcp_server_python_docs/storage/db.py,sha256=Lys8HN3IvgmNpr_qyocUQghcZnwHf2lz9eq_BFmwi54,5634
|
|
32
34
|
mcp_server_python_docs/storage/schema.sql,sha256=4cXPktPQSLfbjkSjirpf0dA2CphlO9UCIKUxnLiIm_g,5637
|
|
33
|
-
python_docs_mcp_server-0.
|
|
34
|
-
python_docs_mcp_server-0.
|
|
35
|
-
python_docs_mcp_server-0.
|
|
36
|
-
python_docs_mcp_server-0.
|
|
37
|
-
python_docs_mcp_server-0.
|
|
35
|
+
python_docs_mcp_server-0.3.0.dist-info/METADATA,sha256=_3VIV56ywUeSNOWBiexQl2RbhI8MO49wLmFsAQDPW34,13913
|
|
36
|
+
python_docs_mcp_server-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
37
|
+
python_docs_mcp_server-0.3.0.dist-info/entry_points.txt,sha256=-1arbtjZhF5mtnJNAh_90UBr1f4AqJxT3Tmfy9p7NMI,142
|
|
38
|
+
python_docs_mcp_server-0.3.0.dist-info/licenses/LICENSE,sha256=-AHGWRwcvoeP4bkY1W2ok_qaI1Mg0Zx2qYgR1k-3CHY,1071
|
|
39
|
+
python_docs_mcp_server-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
{python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_docs_mcp_server-0.2.1.dist-info → python_docs_mcp_server-0.3.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|