python-docs-mcp-server 0.2.1__py3-none-any.whl → 0.3.1__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.
@@ -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 tags are pinned so content builds are reproducible and do not
25
- # drift when a maintenance branch receives new commits.
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": {"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"},
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__(self, cache_path: Path, index_path: Path) -> None:
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, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, "
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 WHERE index_fingerprint = ? "
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
- result = GetDocsResult.model_validate_json(row[0])
111
- except (ValidationError, ValueError) as e:
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.2.1
3
+ Version: 0.3.1
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
  [![CodeQL](https://github.com/ayhammouda/python-docs-mcp-server/actions/workflows/codeql.yml/badge.svg)](https://github.com/ayhammouda/python-docs-mcp-server/actions/workflows/codeql.yml)
38
39
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ayhammouda/python-docs-mcp-server/badge)](https://scorecard.dev/viewer/?uri=github.com/ayhammouda/python-docs-mcp-server)
39
40
  [![python-docs-mcp-server MCP server](https://glama.ai/mcp/servers/ayhammouda/python-docs-mcp-server/badges/score.svg)](https://glama.ai/mcp/servers/ayhammouda/python-docs-mcp-server)
40
- [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-v0.1.4-0f766e)](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.ayhammouda%2Fpython-docs-mcp-server)
41
+ [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-latest-0f766e)](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.ayhammouda%2Fpython-docs-mcp-server)
41
42
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
42
43
  [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/)
43
44
  [![No API Keys](https://img.shields.io/badge/API%20keys-none-success)](#why-use-it)
@@ -47,6 +48,13 @@ Built for the moment your agent needs `asyncio.TaskGroup` signatures, `pathlib.P
47
48
 
48
49
  ## Why this exists
49
50
 
51
+ There is a difference between long context and usable context. Research like the
52
+ [*Lost in the Middle*](https://arxiv.org/abs/2307.03172) study (Liu et al., 2023)
53
+ found that models struggle to use relevant information buried in the middle of a
54
+ long input. Pasting an entire documentation page into the model invites that
55
+ failure. Returning the exact symbol, section, and version keeps the answer in a
56
+ short, high-signal context instead.
57
+
50
58
  Generic docs retrieval is a rough fit for Python stdlib questions:
51
59
 
52
60
  - `asyncio.TaskGroup` should resolve to the actual symbol, not a fuzzy page hit
@@ -139,6 +147,26 @@ cache. It can take several minutes.
139
147
 
140
148
  ## Configure your MCP client
141
149
 
150
+ ### Claude Code
151
+
152
+ This repository includes a project-scoped `.mcp.json` for clients that support
153
+ checked-in MCP server configuration. It points at the published package:
154
+
155
+ ```json
156
+ {
157
+ "mcpServers": {
158
+ "python-docs": {
159
+ "type": "stdio",
160
+ "command": "uvx",
161
+ "args": ["python-docs-mcp-server"]
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ Build the local documentation index with the first-run command above before
168
+ expecting docs queries to return corpus-backed results.
169
+
142
170
  ### Claude Desktop
143
171
 
144
172
  Add this to your Claude Desktop configuration file:
@@ -1,5 +1,5 @@
1
1
  mcp_server_python_docs/__init__.py,sha256=sUtQT81momSAPMywIkgnR7BZxJHn53rpcjSE3CTAUws,499
2
- mcp_server_python_docs/__main__.py,sha256=_LmCa1p3O1gSnOcOC7UXoNbqmIo2LBha3FDsfUUkJ_8,21196
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=mEhjuT7q8Bo1aZ-zj6PSqmbq2Bz64g4xj50ttqA4QUU,977
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=KpjutXAVhFPXB-QhgtShxWuTTG0mZWc7j-st7LglfL8,5747
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.2.1.dist-info/METADATA,sha256=cRyy9YN4VFMSHudIlNbZEW2NB2D6B9F72mwDK5FCUNM,13880
34
- python_docs_mcp_server-0.2.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
35
- python_docs_mcp_server-0.2.1.dist-info/entry_points.txt,sha256=-1arbtjZhF5mtnJNAh_90UBr1f4AqJxT3Tmfy9p7NMI,142
36
- python_docs_mcp_server-0.2.1.dist-info/licenses/LICENSE,sha256=-AHGWRwcvoeP4bkY1W2ok_qaI1Mg0Zx2qYgR1k-3CHY,1071
37
- python_docs_mcp_server-0.2.1.dist-info/RECORD,,
35
+ python_docs_mcp_server-0.3.1.dist-info/METADATA,sha256=OGvwbikJSXf2KSdeA5grj0PwDRhjDECRtmW_Wk6N_MA,14808
36
+ python_docs_mcp_server-0.3.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
37
+ python_docs_mcp_server-0.3.1.dist-info/entry_points.txt,sha256=-1arbtjZhF5mtnJNAh_90UBr1f4AqJxT3Tmfy9p7NMI,142
38
+ python_docs_mcp_server-0.3.1.dist-info/licenses/LICENSE,sha256=-AHGWRwcvoeP4bkY1W2ok_qaI1Mg0Zx2qYgR1k-3CHY,1071
39
+ python_docs_mcp_server-0.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any