codebase-memory-mcp 0.6.0__tar.gz → 0.7.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.
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/PKG-INFO +1 -1
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/pyproject.toml +1 -1
- codebase_memory_mcp-0.7.0/src/codebase_memory_mcp/_cli.py +220 -0
- codebase_memory_mcp-0.6.0/src/codebase_memory_mcp/_cli.py +0 -122
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/.gitignore +0 -0
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/README.md +0 -0
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/src/codebase_memory_mcp/__init__.py +0 -0
- {codebase_memory_mcp-0.6.0 → codebase_memory_mcp-0.7.0}/src/codebase_memory_mcp/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebase-memory-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Fast code intelligence engine for AI coding agents — single static binary MCP server
|
|
5
5
|
Project-URL: Homepage, https://github.com/DeusData/codebase-memory-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/DeusData/codebase-memory-mcp
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codebase-memory-mcp"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Fast code intelligence engine for AI coding agents — single static binary MCP server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Downloads the codebase-memory-mcp binary on first run, then exec's it."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import platform
|
|
7
|
+
import stat
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
import urllib.request
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.parse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
REPO = "DeusData/codebase-memory-mcp"
|
|
16
|
+
|
|
17
|
+
# Security: only permit https fetches. urllib's default handlers accept
|
|
18
|
+
# file://, ftp://, and custom schemes — a redirect or tainted URL source
|
|
19
|
+
# could otherwise turn a download into an arbitrary-local-file read.
|
|
20
|
+
_ALLOWED_SCHEMES = frozenset({"https"})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _validate_url_scheme(url: str) -> None:
|
|
24
|
+
"""Reject non-https URLs before any network fetch."""
|
|
25
|
+
scheme = urllib.parse.urlparse(url).scheme
|
|
26
|
+
if scheme not in _ALLOWED_SCHEMES:
|
|
27
|
+
sys.exit(
|
|
28
|
+
f"codebase-memory-mcp: refusing to fetch non-https URL "
|
|
29
|
+
f"(scheme={scheme!r}): {url}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _safe_extract_tar(tf, dest: str) -> None:
|
|
34
|
+
"""Extract a tarfile to dest, rejecting path-traversal entries.
|
|
35
|
+
|
|
36
|
+
Uses the tarfile data filter on Python >=3.12 (PEP 706), falls back to
|
|
37
|
+
manual per-member path validation on older Pythons. Mitigates the
|
|
38
|
+
classic tar-slip / Zip Slip vulnerability (CWE-22).
|
|
39
|
+
"""
|
|
40
|
+
if hasattr(tf, "extraction_filter") or sys.version_info >= (3, 12):
|
|
41
|
+
tf.extractall(dest, filter="data")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
dest_abs = os.path.abspath(dest)
|
|
45
|
+
for member in tf.getmembers():
|
|
46
|
+
if member.issym() or member.islnk():
|
|
47
|
+
sys.exit(
|
|
48
|
+
f"codebase-memory-mcp: refusing unsafe tar entry "
|
|
49
|
+
f"(link: {member.name!r})"
|
|
50
|
+
)
|
|
51
|
+
member_abs = os.path.abspath(os.path.join(dest_abs, member.name))
|
|
52
|
+
if not (member_abs == dest_abs or member_abs.startswith(dest_abs + os.sep)):
|
|
53
|
+
sys.exit(
|
|
54
|
+
f"codebase-memory-mcp: refusing unsafe tar entry "
|
|
55
|
+
f"(escapes dest: {member.name!r})"
|
|
56
|
+
)
|
|
57
|
+
tf.extractall(dest)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _safe_extract_zip(zf, dest: str) -> None:
|
|
61
|
+
"""Extract a zipfile to dest, rejecting path-traversal entries."""
|
|
62
|
+
dest_abs = os.path.abspath(dest)
|
|
63
|
+
for name in zf.namelist():
|
|
64
|
+
member_abs = os.path.abspath(os.path.join(dest_abs, name))
|
|
65
|
+
if not (member_abs == dest_abs or member_abs.startswith(dest_abs + os.sep)):
|
|
66
|
+
sys.exit(
|
|
67
|
+
f"codebase-memory-mcp: refusing unsafe zip entry "
|
|
68
|
+
f"(escapes dest: {name!r})"
|
|
69
|
+
)
|
|
70
|
+
zf.extractall(dest)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _verify_checksum(archive_path: str, archive_name: str, version: str) -> None:
|
|
74
|
+
"""Verify SHA256 checksum against checksums.txt from the release."""
|
|
75
|
+
url = f"https://github.com/{REPO}/releases/download/v{version}/checksums.txt"
|
|
76
|
+
try:
|
|
77
|
+
_validate_url_scheme(url)
|
|
78
|
+
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
|
|
79
|
+
tmp_path = tmp.name
|
|
80
|
+
urllib.request.urlretrieve(url, tmp_path) # noqa: S310 — scheme validated above
|
|
81
|
+
with open(tmp_path) as f:
|
|
82
|
+
for line in f:
|
|
83
|
+
if archive_name in line:
|
|
84
|
+
expected = line.split()[0]
|
|
85
|
+
h = hashlib.sha256()
|
|
86
|
+
with open(archive_path, "rb") as af:
|
|
87
|
+
for chunk in iter(lambda: af.read(65536), b""):
|
|
88
|
+
h.update(chunk)
|
|
89
|
+
actual = h.hexdigest()
|
|
90
|
+
if expected != actual:
|
|
91
|
+
sys.exit(
|
|
92
|
+
f"codebase-memory-mcp: CHECKSUM MISMATCH for {archive_name}\n"
|
|
93
|
+
f" expected: {expected}\n"
|
|
94
|
+
f" actual: {actual}"
|
|
95
|
+
)
|
|
96
|
+
print("codebase-memory-mcp: checksum verified.", file=sys.stderr)
|
|
97
|
+
break
|
|
98
|
+
except SystemExit:
|
|
99
|
+
raise
|
|
100
|
+
except Exception:
|
|
101
|
+
pass # Non-fatal: checksum unavailable
|
|
102
|
+
finally:
|
|
103
|
+
try:
|
|
104
|
+
os.unlink(tmp_path)
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _version() -> str:
|
|
110
|
+
try:
|
|
111
|
+
from importlib.metadata import version
|
|
112
|
+
return version("codebase-memory-mcp")
|
|
113
|
+
except Exception:
|
|
114
|
+
return "0.6.0"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _os_name() -> str:
|
|
118
|
+
p = sys.platform
|
|
119
|
+
if p == "linux":
|
|
120
|
+
return "linux"
|
|
121
|
+
if p == "darwin":
|
|
122
|
+
return "darwin"
|
|
123
|
+
if p == "win32":
|
|
124
|
+
return "windows"
|
|
125
|
+
sys.exit(f"codebase-memory-mcp: unsupported platform: {p}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _arch() -> str:
|
|
129
|
+
m = platform.machine().lower()
|
|
130
|
+
if m in ("arm64", "aarch64"):
|
|
131
|
+
return "arm64"
|
|
132
|
+
if m in ("x86_64", "amd64"):
|
|
133
|
+
return "amd64"
|
|
134
|
+
sys.exit(f"codebase-memory-mcp: unsupported architecture: {m}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cache_dir() -> Path:
|
|
138
|
+
if sys.platform == "win32":
|
|
139
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
140
|
+
elif sys.platform == "darwin":
|
|
141
|
+
base = Path.home() / "Library" / "Caches"
|
|
142
|
+
else:
|
|
143
|
+
base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
144
|
+
return base / "codebase-memory-mcp"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _bin_path(version: str) -> Path:
|
|
148
|
+
name = "codebase-memory-mcp.exe" if sys.platform == "win32" else "codebase-memory-mcp"
|
|
149
|
+
return _cache_dir() / version / name
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _download(version: str) -> Path:
|
|
153
|
+
os_name = _os_name()
|
|
154
|
+
arch = _arch()
|
|
155
|
+
ext = "zip" if os_name == "windows" else "tar.gz"
|
|
156
|
+
archive = f"codebase-memory-mcp-{os_name}-{arch}.{ext}"
|
|
157
|
+
url = f"https://github.com/{REPO}/releases/download/v{version}/{archive}"
|
|
158
|
+
_validate_url_scheme(url)
|
|
159
|
+
|
|
160
|
+
dest = _bin_path(version)
|
|
161
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
print(
|
|
164
|
+
f"codebase-memory-mcp: downloading v{version} for {os_name}/{arch}...",
|
|
165
|
+
file=sys.stderr,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
169
|
+
tmp_archive = os.path.join(tmp, f"cbm.{ext}")
|
|
170
|
+
try:
|
|
171
|
+
urllib.request.urlretrieve(url, tmp_archive) # noqa: S310 — scheme validated above
|
|
172
|
+
except urllib.error.HTTPError as e:
|
|
173
|
+
sys.exit(
|
|
174
|
+
f"codebase-memory-mcp: download failed ({e})\n"
|
|
175
|
+
f"URL: {url}\n"
|
|
176
|
+
f"See https://github.com/{REPO}/releases for available versions."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
_verify_checksum(tmp_archive, archive, version)
|
|
180
|
+
|
|
181
|
+
if ext == "tar.gz":
|
|
182
|
+
import tarfile
|
|
183
|
+
with tarfile.open(tmp_archive) as tf:
|
|
184
|
+
_safe_extract_tar(tf, tmp)
|
|
185
|
+
else:
|
|
186
|
+
import zipfile
|
|
187
|
+
with zipfile.ZipFile(tmp_archive) as zf:
|
|
188
|
+
_safe_extract_zip(zf, tmp)
|
|
189
|
+
|
|
190
|
+
bin_name = "codebase-memory-mcp.exe" if os_name == "windows" else "codebase-memory-mcp"
|
|
191
|
+
extracted = os.path.join(tmp, bin_name)
|
|
192
|
+
if not os.path.exists(extracted):
|
|
193
|
+
sys.exit("codebase-memory-mcp: binary not found after extraction")
|
|
194
|
+
|
|
195
|
+
shutil.copy2(extracted, dest)
|
|
196
|
+
current = dest.stat().st_mode
|
|
197
|
+
dest.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
198
|
+
|
|
199
|
+
return dest
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def main() -> None:
|
|
203
|
+
version = _version()
|
|
204
|
+
bin_path = _bin_path(version)
|
|
205
|
+
|
|
206
|
+
if not bin_path.exists():
|
|
207
|
+
bin_path = _download(version)
|
|
208
|
+
|
|
209
|
+
# args is a list (not a shell string), so exec/subprocess treat each
|
|
210
|
+
# element as a discrete argv entry — no shell interpretation, no
|
|
211
|
+
# injection vector. sys.argv forwarding is the whole point of this
|
|
212
|
+
# shim, so tainted-input suppression is intentional.
|
|
213
|
+
args = [str(bin_path)] + sys.argv[1:]
|
|
214
|
+
|
|
215
|
+
if sys.platform != "win32":
|
|
216
|
+
os.execv(str(bin_path), args) # noqa: S606 — list form, no shell
|
|
217
|
+
else:
|
|
218
|
+
import subprocess
|
|
219
|
+
result = subprocess.run(args) # noqa: S603 — list form, no shell=True
|
|
220
|
+
sys.exit(result.returncode)
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"""Downloads the codebase-memory-mcp binary on first run, then exec's it."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import platform
|
|
6
|
-
import stat
|
|
7
|
-
import shutil
|
|
8
|
-
import tempfile
|
|
9
|
-
import urllib.request
|
|
10
|
-
import urllib.error
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
REPO = "DeusData/codebase-memory-mcp"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _version() -> str:
|
|
17
|
-
try:
|
|
18
|
-
from importlib.metadata import version
|
|
19
|
-
return version("codebase-memory-mcp")
|
|
20
|
-
except Exception:
|
|
21
|
-
return "0.6.0"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _os_name() -> str:
|
|
25
|
-
p = sys.platform
|
|
26
|
-
if p == "linux":
|
|
27
|
-
return "linux"
|
|
28
|
-
if p == "darwin":
|
|
29
|
-
return "darwin"
|
|
30
|
-
if p == "win32":
|
|
31
|
-
return "windows"
|
|
32
|
-
sys.exit(f"codebase-memory-mcp: unsupported platform: {p}")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _arch() -> str:
|
|
36
|
-
m = platform.machine().lower()
|
|
37
|
-
if m in ("arm64", "aarch64"):
|
|
38
|
-
return "arm64"
|
|
39
|
-
if m in ("x86_64", "amd64"):
|
|
40
|
-
return "amd64"
|
|
41
|
-
sys.exit(f"codebase-memory-mcp: unsupported architecture: {m}")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _cache_dir() -> Path:
|
|
45
|
-
if sys.platform == "win32":
|
|
46
|
-
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
47
|
-
elif sys.platform == "darwin":
|
|
48
|
-
base = Path.home() / "Library" / "Caches"
|
|
49
|
-
else:
|
|
50
|
-
base = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
51
|
-
return base / "codebase-memory-mcp"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _bin_path(version: str) -> Path:
|
|
55
|
-
name = "codebase-memory-mcp.exe" if sys.platform == "win32" else "codebase-memory-mcp"
|
|
56
|
-
return _cache_dir() / version / name
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _download(version: str) -> Path:
|
|
60
|
-
os_name = _os_name()
|
|
61
|
-
arch = _arch()
|
|
62
|
-
ext = "zip" if os_name == "windows" else "tar.gz"
|
|
63
|
-
url = (
|
|
64
|
-
f"https://github.com/{REPO}/releases/download/v{version}"
|
|
65
|
-
f"/codebase-memory-mcp-{os_name}-{arch}.{ext}"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
dest = _bin_path(version)
|
|
69
|
-
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
-
|
|
71
|
-
print(
|
|
72
|
-
f"codebase-memory-mcp: downloading v{version} for {os_name}/{arch}...",
|
|
73
|
-
file=sys.stderr,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
77
|
-
tmp_archive = os.path.join(tmp, f"cbm.{ext}")
|
|
78
|
-
try:
|
|
79
|
-
urllib.request.urlretrieve(url, tmp_archive)
|
|
80
|
-
except urllib.error.HTTPError as e:
|
|
81
|
-
sys.exit(
|
|
82
|
-
f"codebase-memory-mcp: download failed ({e})\n"
|
|
83
|
-
f"URL: {url}\n"
|
|
84
|
-
f"See https://github.com/{REPO}/releases for available versions."
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
if ext == "tar.gz":
|
|
88
|
-
import tarfile
|
|
89
|
-
with tarfile.open(tmp_archive) as tf:
|
|
90
|
-
tf.extractall(tmp)
|
|
91
|
-
else:
|
|
92
|
-
import zipfile
|
|
93
|
-
with zipfile.ZipFile(tmp_archive) as zf:
|
|
94
|
-
zf.extractall(tmp)
|
|
95
|
-
|
|
96
|
-
bin_name = "codebase-memory-mcp.exe" if os_name == "windows" else "codebase-memory-mcp"
|
|
97
|
-
extracted = os.path.join(tmp, bin_name)
|
|
98
|
-
if not os.path.exists(extracted):
|
|
99
|
-
sys.exit(f"codebase-memory-mcp: binary not found after extraction")
|
|
100
|
-
|
|
101
|
-
shutil.copy2(extracted, dest)
|
|
102
|
-
current = dest.stat().st_mode
|
|
103
|
-
dest.chmod(current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
104
|
-
|
|
105
|
-
return dest
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def main() -> None:
|
|
109
|
-
version = _version()
|
|
110
|
-
bin_path = _bin_path(version)
|
|
111
|
-
|
|
112
|
-
if not bin_path.exists():
|
|
113
|
-
bin_path = _download(version)
|
|
114
|
-
|
|
115
|
-
args = [str(bin_path)] + sys.argv[1:]
|
|
116
|
-
|
|
117
|
-
if sys.platform != "win32":
|
|
118
|
-
os.execv(str(bin_path), args)
|
|
119
|
-
else:
|
|
120
|
-
import subprocess
|
|
121
|
-
result = subprocess.run(args)
|
|
122
|
-
sys.exit(result.returncode)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|