getcodexy 1.2.2__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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: getcodexy
3
+ Version: 1.2.2
4
+ Summary: Version-pinned MCP runtime bootstrap tools for Codexy
5
+ Author: Eunsoo Lee
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Project-URL: Repository, https://github.com/eunsoogi/codexy
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["uv_build==0.9.26"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "getcodexy"
7
+ version = "1.2.2"
8
+ description = "Version-pinned MCP runtime bootstrap tools for Codexy"
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ authors = [{ name = "Eunsoo Lee" }]
12
+
13
+ [project.urls]
14
+ Repository = "https://github.com/eunsoogi/codexy"
15
+
16
+ [project.scripts]
17
+ codexy-mcp-runtime = "codexy_runtime_tools.runtime:main"
18
+
19
+ [tool.uv.build-backend]
20
+ module-name = "codexy_runtime_tools"
21
+ module-root = "src"
@@ -0,0 +1 @@
1
+ """Codexy runtime bootstrap package."""
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+
9
+ SEMVER = re.compile(
10
+ r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)"
11
+ r"(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)"
12
+ r"(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?"
13
+ r"(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$"
14
+ )
15
+
16
+
17
+ def plugin_release(manifest_path: Path, package_override: bool = False) -> str:
18
+ if not manifest_path.is_file():
19
+ if package_override:
20
+ return "package-override"
21
+ raise ValueError("plugin manifest is missing")
22
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
23
+ release = manifest.get("version") if isinstance(manifest, dict) else None
24
+ if not isinstance(release, str) or not SEMVER.fullmatch(release):
25
+ raise ValueError("plugin manifest version is invalid")
26
+ return release
27
+
28
+
29
+ def releases_match(expected_manifest: Path, observed_manifest: Path) -> tuple[bool, str]:
30
+ try:
31
+ expected = plugin_release(expected_manifest)
32
+ except (OSError, ValueError, json.JSONDecodeError):
33
+ return False, "runtime package release mismatch: expected valid plugin release, observed missing or invalid"
34
+ try:
35
+ observed = plugin_release(observed_manifest)
36
+ except (OSError, ValueError, json.JSONDecodeError):
37
+ return False, f"runtime package release mismatch: expected {expected}, observed missing or invalid"
38
+ if expected != observed:
39
+ return False, f"runtime package release mismatch: expected {expected}, observed {observed}"
40
+ return True, ""
41
+
42
+
43
+ def runtime_cache_key(*, manifest: Path, package_override: bool, identity: list[str]) -> str:
44
+ release = plugin_release(manifest, package_override)
45
+ digest_input = "\0".join(("codexy.runtime-cache/v2", *identity[:-1], release, identity[-1]))
46
+ return f"v2-{hashlib.sha256(digest_input.encode()).hexdigest()}"
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import stat
6
+ import subprocess
7
+ import tempfile
8
+ from pathlib import Path
9
+ from typing import NoReturn, Protocol
10
+
11
+ from .cache import releases_match
12
+ from .package import acquire_package, unpack_runtime
13
+
14
+
15
+ class InstallConfig(Protocol):
16
+ server: str
17
+ manifest: Path
18
+ runtime_name: str
19
+ package_path: str
20
+ package_url: str
21
+ artifacts_api: str
22
+ package_override: bool
23
+ package_sha256: str
24
+ git_repository: str
25
+ git_ref: str
26
+
27
+
28
+ def executable(path: Path) -> bool:
29
+ try:
30
+ metadata = os.lstat(path)
31
+ except FileNotFoundError:
32
+ return False
33
+ reparse = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0)
34
+ return (
35
+ stat.S_ISREG(metadata.st_mode)
36
+ and not stat.S_ISLNK(metadata.st_mode)
37
+ and not bool(getattr(metadata, "st_file_attributes", 0) & reparse)
38
+ and os.access(path, os.X_OK)
39
+ )
40
+
41
+
42
+ def execute(
43
+ path: Path | str, arguments: list[str], environment: dict[str, str] | None = None
44
+ ) -> NoReturn:
45
+ command = str(path)
46
+ runtime_environment = os.environ.copy()
47
+ runtime_environment.update(environment or {})
48
+ os.execvpe(command, [command, *arguments], runtime_environment)
49
+ raise AssertionError("exec returned unexpectedly")
50
+
51
+
52
+ def install_package(config: InstallConfig, install_root: Path, installed: Path) -> None:
53
+ install_root.mkdir(parents=True, exist_ok=True)
54
+ with tempfile.TemporaryDirectory(prefix="package-", dir=install_root) as temporary:
55
+ work = Path(temporary)
56
+ archive = acquire_package(
57
+ path=config.package_path,
58
+ url=config.package_url,
59
+ artifacts_api=config.artifacts_api,
60
+ expected_sha256=config.package_sha256,
61
+ work=work,
62
+ )
63
+ packaged_runtime, package_manifest = unpack_runtime(
64
+ archive=archive, work=work, runtime_name=config.runtime_name
65
+ )
66
+ if not config.package_override:
67
+ matches, message = releases_match(config.manifest, package_manifest)
68
+ if not matches:
69
+ raise RuntimeError(message)
70
+ installed.parent.mkdir(parents=True, exist_ok=True)
71
+ temporary_runtime = installed.with_name(f".{installed.name}.{os.getpid()}.tmp")
72
+ shutil.copyfile(packaged_runtime, temporary_runtime)
73
+ temporary_runtime.chmod(
74
+ temporary_runtime.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
75
+ )
76
+ os.replace(temporary_runtime, installed)
77
+ if not config.package_override:
78
+ shutil.copyfile(package_manifest, install_root / "plugin.json")
79
+
80
+
81
+ def install_git(config: InstallConfig, install_root: Path, installed: Path) -> None:
82
+ cargo = shutil.which("cargo")
83
+ if not cargo:
84
+ raise RuntimeError("cargo is unavailable for the configured Git runtime source")
85
+ revision = len(config.git_ref) == 40 and all(
86
+ character in "0123456789abcdefABCDEF" for character in config.git_ref
87
+ )
88
+ if not revision:
89
+ raise RuntimeError("CODEXY_RUNTIME_GIT_REF must be an exact 40-hex commit")
90
+ command = [
91
+ cargo,
92
+ "install",
93
+ "--force",
94
+ "--locked",
95
+ "--git",
96
+ config.git_repository,
97
+ "--rev",
98
+ config.git_ref,
99
+ "--root",
100
+ str(install_root),
101
+ "--bin",
102
+ f"codexy-mcp-{config.server}",
103
+ ]
104
+ completed = subprocess.run(command, check=False)
105
+ if completed.returncode or not executable(installed):
106
+ raise RuntimeError(f"cargo install exited with status {completed.returncode}")
107
+ shutil.copyfile(config.manifest, install_root / "plugin.json")
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import shutil
7
+ import stat
8
+ import subprocess
9
+ import tarfile
10
+ import urllib.request
11
+ import zipfile
12
+ import zlib
13
+ from pathlib import Path
14
+ from urllib.parse import urlparse
15
+
16
+
17
+ MAX_ARCHIVE_FILES = 2_048
18
+ MAX_UNPACKED_BYTES = 512 * 1024 * 1024
19
+ CANONICAL_REPOSITORY_ID = 1_269_350_143
20
+
21
+
22
+ class _GithubRedirectHandler(urllib.request.HTTPRedirectHandler):
23
+ def redirect_request(self, request, file_pointer, status, message, headers, new_url):
24
+ redirected = super().redirect_request(
25
+ request, file_pointer, status, message, headers, new_url
26
+ )
27
+ if redirected and _origin(request.full_url) != _origin(new_url):
28
+ for redirect_headers in (redirected.headers, redirected.unredirected_hdrs):
29
+ for name in list(redirect_headers):
30
+ if name.lower() == "authorization":
31
+ del redirect_headers[name]
32
+ return redirected
33
+
34
+
35
+ def _download(url: str, destination: Path, token: str = "") -> None:
36
+ headers = {"Accept": "application/vnd.github+json"}
37
+ if token:
38
+ headers["Authorization"] = f"Bearer {token}"
39
+ request = urllib.request.Request(url, headers=headers)
40
+ open_request = (
41
+ urllib.request.build_opener(_GithubRedirectHandler()).open if token else urllib.request.urlopen
42
+ )
43
+ with open_request(request, timeout=30) as response, destination.open("wb") as output:
44
+ shutil.copyfileobj(response, output)
45
+
46
+
47
+ def _origin(url: str) -> tuple[str, str, int | None]:
48
+ parsed = urlparse(url)
49
+ return parsed.scheme, parsed.hostname or "", parsed.port
50
+
51
+
52
+ def _trusted_github_api(url: str) -> bool:
53
+ return _origin(url) == ("https", "api.github.com", None)
54
+
55
+
56
+ def _safe_extract_tar(archive: Path, destination: Path) -> None:
57
+ try:
58
+ _extract_tar(archive, destination)
59
+ except (tarfile.TarError, EOFError) as error:
60
+ raise ValueError(f"invalid runtime package archive: {error}") from error
61
+
62
+
63
+ def _extract_tar(archive: Path, destination: Path) -> None:
64
+ destination_resolved = destination.resolve()
65
+ with tarfile.open(archive, "r:gz") as package:
66
+ members = package.getmembers()
67
+ if len(members) > MAX_ARCHIVE_FILES:
68
+ raise ValueError("runtime package contains too many members")
69
+ if sum(member.size for member in members) > MAX_UNPACKED_BYTES:
70
+ raise ValueError("runtime package exceeds the unpacked size limit")
71
+ destinations: set[Path] = set()
72
+ for member in members:
73
+ if not (member.isdir() or member.isfile()):
74
+ raise ValueError(f"runtime package contains unsafe link or device: {member.name}")
75
+ member_path = (destination / member.name).resolve()
76
+ if destination_resolved not in member_path.parents and member_path != destination_resolved:
77
+ raise ValueError(f"runtime package contains unsafe path: {member.name}")
78
+ if member_path in destinations:
79
+ raise ValueError(f"runtime package contains duplicate path: {member.name}")
80
+ destinations.add(member_path)
81
+ package.extractall(destination)
82
+
83
+
84
+ def _safe_extract_zip(archive: Path, destination: Path) -> None:
85
+ try:
86
+ _extract_zip(archive, destination)
87
+ except (zipfile.BadZipFile, zlib.error) as error:
88
+ raise ValueError(f"invalid artifact archive: {error}") from error
89
+
90
+
91
+ def _extract_zip(archive: Path, destination: Path) -> None:
92
+ destination_resolved = destination.resolve()
93
+ with zipfile.ZipFile(archive) as zipped:
94
+ members = zipped.infolist()
95
+ if len(members) > MAX_ARCHIVE_FILES:
96
+ raise ValueError("artifact archive contains too many members")
97
+ if sum(member.file_size for member in members) > MAX_UNPACKED_BYTES:
98
+ raise ValueError("artifact archive exceeds the unpacked size limit")
99
+ destinations: set[Path] = set()
100
+ for member in members:
101
+ member_path = (destination / member.filename).resolve()
102
+ unix_mode = member.external_attr >> 16
103
+ if stat.S_ISLNK(unix_mode):
104
+ raise ValueError(f"artifact archive contains unsafe link: {member.filename}")
105
+ if destination_resolved not in member_path.parents and member_path != destination_resolved:
106
+ raise ValueError(f"artifact archive contains unsafe path: {member.filename}")
107
+ if member_path in destinations:
108
+ raise ValueError(f"artifact archive contains duplicate path: {member.filename}")
109
+ destinations.add(member_path)
110
+ zipped.extractall(destination)
111
+
112
+
113
+ def _github_token_for(url: str) -> str:
114
+ if not _trusted_github_api(url):
115
+ return ""
116
+ token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
117
+ if token:
118
+ return token
119
+ try:
120
+ return subprocess.run(
121
+ ["gh", "auth", "token"],
122
+ check=True,
123
+ stdout=subprocess.PIPE,
124
+ stderr=subprocess.DEVNULL,
125
+ text=True,
126
+ ).stdout.strip()
127
+ except (OSError, subprocess.CalledProcessError):
128
+ return ""
129
+
130
+
131
+ def _artifact_package(api_url: str, work: Path) -> Path:
132
+ token = _github_token_for(api_url)
133
+ metadata = work / "artifacts.json"
134
+ _download(api_url, metadata, token)
135
+ payload = json.loads(metadata.read_text(encoding="utf-8"))
136
+ artifacts = payload.get("artifacts") if isinstance(payload, dict) else None
137
+ if not isinstance(artifacts, list):
138
+ raise RuntimeError("artifact source has invalid artifacts listing")
139
+ selected = next(
140
+ (
141
+ item
142
+ for item in artifacts
143
+ if isinstance(item, dict)
144
+ and not item.get("expired", True)
145
+ and isinstance(item.get("workflow_run"), dict)
146
+ and item["workflow_run"].get("head_branch") == "main"
147
+ and item["workflow_run"].get("head_repository_id") == CANONICAL_REPOSITORY_ID
148
+ and isinstance(item.get("archive_download_url"), str)
149
+ ),
150
+ None,
151
+ )
152
+ if selected is None:
153
+ raise RuntimeError("artifact source has no unexpired main-branch package")
154
+ archive = work / "artifact.zip"
155
+ download_url = selected["archive_download_url"]
156
+ if token and not (_trusted_github_api(download_url) or _origin(download_url) == ("https", "github.com", None)):
157
+ raise RuntimeError("artifact download URL is not a trusted GitHub host")
158
+ _download(download_url, archive, token)
159
+ artifact_root = work / "artifact"
160
+ _safe_extract_zip(archive, artifact_root)
161
+ matches = list(artifact_root.rglob("codexy-marketplace-plugin.tar.gz"))
162
+ if len(matches) != 1:
163
+ raise RuntimeError("artifact must contain exactly one marketplace package")
164
+ return matches[0]
165
+
166
+
167
+ def acquire_package(
168
+ *, path: str, url: str, artifacts_api: str, expected_sha256: str, work: Path
169
+ ) -> Path:
170
+ work.mkdir(parents=True, exist_ok=True)
171
+ archive = work / "codexy-marketplace-plugin.tar.gz"
172
+ if path:
173
+ source = Path(path)
174
+ if not source.is_absolute():
175
+ raise ValueError(f"runtime package path must be absolute: {source}")
176
+ shutil.copyfile(source, archive)
177
+ elif url:
178
+ _download(url, archive)
179
+ elif artifacts_api:
180
+ shutil.copyfile(_artifact_package(artifacts_api, work), archive)
181
+ else:
182
+ raise RuntimeError("no runtime package source was configured")
183
+ if expected_sha256:
184
+ observed = hashlib.sha256(archive.read_bytes()).hexdigest()
185
+ if observed != expected_sha256.lower():
186
+ raise ValueError(
187
+ f"runtime package SHA-256 mismatch: expected {expected_sha256.lower()}, observed {observed}"
188
+ )
189
+ return archive
190
+
191
+
192
+ def unpack_runtime(*, archive: Path, work: Path, runtime_name: str) -> tuple[Path, Path]:
193
+ extracted = work / "package"
194
+ extracted.mkdir()
195
+ _safe_extract_tar(archive, extracted)
196
+ runtime = extracted / "plugins" / "codexy" / "runtime" / runtime_name
197
+ manifest = extracted / "plugins" / "codexy" / ".codex-plugin" / "plugin.json"
198
+ if not runtime.is_file() or runtime.is_symlink() or not manifest.is_file() or manifest.is_symlink():
199
+ raise RuntimeError("runtime package is missing its exact runtime binary or plugin manifest")
200
+ return runtime, manifest
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import os
5
+ import platform as host_platform
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import NoReturn
10
+
11
+ from .cache import plugin_release, releases_match, runtime_cache_key
12
+ from .installer import executable, execute, install_git, install_package
13
+
14
+
15
+ SUPPORTED_PLATFORMS = ("darwin-arm64", "linux-x86_64")
16
+ PROTOCOL = "stdio-newline-v1"
17
+ REPOSITORY = "https://github.com/eunsoogi/codexy"
18
+
19
+
20
+ def _fail(message: str) -> NoReturn:
21
+ print(message, file=sys.stderr)
22
+ raise SystemExit(127)
23
+
24
+
25
+ def _notice(message: str) -> None:
26
+ print(f"codexy runtime: {message}", file=sys.stderr)
27
+
28
+
29
+ def _host_platform() -> str:
30
+ override = os.environ.get("CODEXY_RUNTIME_PLATFORM")
31
+ if override:
32
+ return override
33
+ os_name = {"Darwin": "darwin", "Linux": "linux", "Windows": "windows"}.get(
34
+ host_platform.system(), "unknown"
35
+ )
36
+ architecture = {
37
+ "arm64": "arm64", "aarch64": "arm64", "x86_64": "x86_64",
38
+ "amd64": "x86_64", "AMD64": "x86_64",
39
+ }.get(host_platform.machine(), "unknown")
40
+ return f"{os_name}-{architecture}"
41
+
42
+
43
+ def _absolute_env_path(name: str) -> Path | None:
44
+ value = os.environ.get(name)
45
+ if not value:
46
+ return None
47
+ path = Path(value)
48
+ if not path.is_absolute():
49
+ _fail(f"{name} must be absolute: {path}")
50
+ return path
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Configuration:
55
+ server: str
56
+ plugin_root: Path
57
+ arguments: list[str]
58
+ platform: str
59
+ manifest: Path
60
+ release: str
61
+ runtime_name: str
62
+ package_path: str
63
+ package_url: str
64
+ artifacts_api: str
65
+ package_override: bool
66
+ package_sha256: str
67
+ git_repository: str
68
+ git_ref: str
69
+ offline: bool
70
+ git_fallback: bool
71
+
72
+ @classmethod
73
+ def load(cls, server: str, plugin_root: Path, arguments: list[str]) -> "Configuration":
74
+ manifest = plugin_root / ".codex-plugin/plugin.json"
75
+ try:
76
+ release = plugin_release(manifest)
77
+ except (OSError, ValueError) as error:
78
+ _fail(f"codexy-mcp-{server} cannot read plugin release: {error}")
79
+ package_path_was_set = "CODEXY_RUNTIME_PACKAGE_PATH" in os.environ
80
+ package_path = os.environ.get("CODEXY_RUNTIME_PACKAGE_PATH", "")
81
+ package_url_was_set = "CODEXY_RUNTIME_PACKAGE_URL" in os.environ
82
+ artifacts_was_set = "CODEXY_RUNTIME_ARTIFACTS_API_URL" in os.environ
83
+ package_url = os.environ.get("CODEXY_RUNTIME_PACKAGE_URL", "")
84
+ artifacts_api = os.environ.get("CODEXY_RUNTIME_ARTIFACTS_API_URL", "")
85
+ package_override = bool(package_path_was_set or package_url_was_set or artifacts_was_set)
86
+ package_sha256 = os.environ.get("CODEXY_RUNTIME_PACKAGE_SHA256", "").lower()
87
+ if package_override and (
88
+ len(package_sha256) != 64
89
+ or any(character not in "0123456789abcdefABCDEF" for character in package_sha256)
90
+ ):
91
+ _fail("explicit runtime package source requires CODEXY_RUNTIME_PACKAGE_SHA256")
92
+ if not package_override:
93
+ package_url = f"{REPOSITORY}/releases/download/v{release}/codexy-marketplace-plugin.tar.gz"
94
+ return cls(
95
+ server=server, plugin_root=plugin_root, arguments=arguments,
96
+ platform=_host_platform(), manifest=manifest, release=release,
97
+ runtime_name=f"codexy-mcp-{server}-{_host_platform()}.bin",
98
+ package_path=package_path, package_url=package_url, artifacts_api=artifacts_api,
99
+ package_override=package_override, package_sha256=package_sha256,
100
+ git_repository=os.environ.get("CODEXY_RUNTIME_GIT_REPOSITORY", REPOSITORY),
101
+ git_ref=os.environ.get("CODEXY_RUNTIME_GIT_REF", ""),
102
+ offline=os.environ.get("UV_OFFLINE", "").lower() in {"1", "true", "yes"},
103
+ git_fallback=os.environ.get("CODEXY_RUNTIME_GIT_FALLBACK") == "1",
104
+ )
105
+
106
+
107
+ def _cache_root(server: str) -> Path:
108
+ explicit = _absolute_env_path("CODEXY_RUNTIME_CACHE_DIR")
109
+ if explicit:
110
+ return explicit
111
+ xdg, home = os.environ.get("XDG_CACHE_HOME"), os.environ.get("HOME")
112
+ if not xdg and not home:
113
+ _fail(f"codexy-mcp-{server} cannot bootstrap runtime without HOME, XDG_CACHE_HOME, or CODEXY_RUNTIME_CACHE_DIR")
114
+ root = Path(xdg) if xdg else Path(home or "") / ".cache"
115
+ if not root.is_absolute():
116
+ _fail(f"codexy-mcp-{server} runtime cache dir must be absolute: {root}")
117
+ return root / "codexy" / "runtime"
118
+
119
+
120
+ def _execute(config: Configuration, path: Path) -> NoReturn:
121
+ execute(path, config.arguments, {"CODEXY_PLUGIN_ROOT": str(config.plugin_root)})
122
+
123
+
124
+ def run(config: Configuration) -> NoReturn:
125
+ runtime_dir = _absolute_env_path("CODEXY_RUNTIME_DIR")
126
+ if runtime_dir:
127
+ runtime = runtime_dir / config.runtime_name
128
+ if not executable(runtime):
129
+ _fail(f"codexy-mcp-{config.server} runtime not found in CODEXY_RUNTIME_DIR: {runtime}")
130
+ _execute(config, runtime)
131
+ if config.platform not in SUPPORTED_PLATFORMS:
132
+ _fail(f"codexy-mcp-{config.server} bundled runtime supports: {' '.join(SUPPORTED_PLATFORMS)}; set CODEXY_RUNTIME_DIR for {config.platform}")
133
+ bundled = config.plugin_root / "runtime" / config.runtime_name
134
+ if executable(bundled):
135
+ _execute(config, bundled)
136
+ source = "\n".join(("package-override", config.package_path, config.package_url, config.artifacts_api, config.package_sha256)) if config.package_override else "\n".join(("package-default", config.package_sha256))
137
+ key = runtime_cache_key(manifest=config.manifest, package_override=config.package_override, identity=[config.git_repository, config.git_ref, config.platform, PROTOCOL, source, f"codexy-mcp-{config.server}"])
138
+ install_root = _cache_root(config.server) / key
139
+ installed, marker = install_root / "bin" / f"codexy-mcp-{config.server}", install_root / "plugin.json"
140
+ if executable(installed) and config.package_override:
141
+ _execute(config, installed)
142
+ if executable(installed) and marker.is_file() and releases_match(config.manifest, marker)[0]:
143
+ _execute(config, installed)
144
+ if config.offline:
145
+ _fail(f"codexy-mcp-{config.server} offline mode has no cached or bundled runtime for {config.platform}")
146
+ try:
147
+ _notice(f"acquiring exact release package v{config.release} for {config.server}")
148
+ install_package(config, install_root, installed)
149
+ _execute(config, installed)
150
+ except (OSError, RuntimeError, ValueError) as package_error:
151
+ if config.package_override:
152
+ _fail(f"codexy-mcp-{config.server} explicit package source failed: {package_error}")
153
+ if not config.git_fallback:
154
+ _fail(f"codexy-mcp-{config.server} exact release package failed: {package_error}")
155
+ _notice(f"release package failed ({package_error}); explicit Git fallback uses {config.git_ref}")
156
+ try:
157
+ install_git(config, install_root, installed)
158
+ _execute(config, installed)
159
+ except (OSError, RuntimeError) as git_error:
160
+ _fail(f"codexy-mcp-{config.server} pinned Git runtime failed: {git_error}")
161
+
162
+
163
+ def main() -> None:
164
+ parser = argparse.ArgumentParser(prog="codexy-mcp-runtime")
165
+ parser.add_argument("server", choices=("lsp", "codegraph"))
166
+ parser.add_argument("--plugin-root", type=Path, required=True)
167
+ parsed, arguments = parser.parse_known_args()
168
+ arguments = arguments[1:] if arguments[:1] == ["--"] else arguments
169
+ run(Configuration.load(parsed.server, parsed.plugin_root.resolve(), arguments))