Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/notify-catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ on:
workflow_dispatch:
workflow_call:
secrets:
CATALOG_DISPATCH_TOKEN:
description: Existing receiver-scoped Actions write authority
CATALOG_APP_PRIVATE_KEY:
description: Private key of the receiver-scoped catalog GitHub App
required: true
outputs:
run_url:
Expand All @@ -23,6 +23,7 @@ jobs:
steps:
- name: Request complete catalog reconciliation
id: catalog
uses: computer-mcp/computer-mcp.github.io/.github/actions/notify-catalog@fc27dd0f370d028a3e5021e3585274891f696578
uses: computer-mcp/computer-mcp.github.io/.github/actions/notify-catalog@50298a84b08afdbc2642f4c765cce7c7dccf81e0
with:
token: ${{ secrets.CATALOG_DISPATCH_TOKEN }}
client-id: ${{ vars.CATALOG_APP_CLIENT_ID }}
private-key: ${{ secrets.CATALOG_APP_PRIVATE_KEY }}
17 changes: 14 additions & 3 deletions Documentation/Reference/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
On macOS the package owns `bin/codex-mcp-adapter` and its adjacent
`codex-plugin_CodexAdapter.bundle`. On Windows it owns
`bin/codex-mcp-adapter.exe`, `codex-plugin_CodexAdapter.resources` and the required
runtime DLLs in `bin/`. Codex itself remains an external dependency.
Swift/open-source runtime DLLs in `bin/`. Codex itself remains an external dependency.

Before launching on Windows x86_64, install the latest supported
[Microsoft Visual C++ v14 x64 Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170)
from Microsoft. The plugin does not include Microsoft runtime DLLs or install
the redistributable. Its release receipt records the minimum tested version for
each imported Microsoft DLL under `windows_runtime.external_prerequisites`.
If Windows reports a missing `VCRUNTIME140.dll`, `VCRUNTIME140_1.dll` or
`MSVCP140.dll`, install or update that official x64 package before retrying.
Installing this package never installs, updates or removes Codex, changes global
PATH, or grants a profile access to tools.

Expand Down Expand Up @@ -38,7 +46,8 @@ builds SQLite from the checksummed source in `Scripts/windows-sqlite.json`,
verifies its required features, and passes the resulting headers and static
library to SwiftPM. It copies the recursively inspected Swift runtime DLLs
beside the adapter and verifies their architecture and notice coverage. The
Windows receipt records each DLL's source digest and the SQLite build identity.
Windows receipt records each bundled DLL's source digest, the imported Microsoft
runtime version floors and the SQLite build identity.
See [third-party components](../../THIRD_PARTY_NOTICES.md) for distribution terms.

Package inputs must be regular files and directories. Symbolic links and special
Expand All @@ -53,7 +62,9 @@ and manual dispatch. It checks formatting, tests and the dependency lock, then
retains both native ZIPs and receipts as downloadable workflow artifacts. A
separate Windows job installs no Swift toolchain, relocates the exact ZIP and
runs the adapter with system-only child PATH. It records actual loaded module
paths and digests, MCP discovery, reconnect and joined native process cleanup.
paths and digests, verifies the system-installed Microsoft runtime architecture
and versions against the receipt, and checks MCP discovery, reconnect and joined
native process cleanup.
Hosted runners can contain preinstalled software; this gate does not claim a
pristine Windows installation or authenticated model execution. These outputs
are validation builds, not published or verified official releases.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ On macOS, building requires macOS 14 or newer and Swift 6.2 or newer.
Run `swift build` and `swift test`. Windows x86_64 packaging uses Swift 6.2.3,
PowerShell and the pinned SQLite build described in
[Installation](Documentation/Reference/Installation.md). The Windows adapter
serves standard MCP over stdio; it does not provide a Windows host GUI. Launch
serves standard MCP over stdio and requires the user-installed official
Microsoft Visual C++ v14 x64 runtime. It does not provide a Windows host GUI. Launch
`.build/debug/codex-mcp-adapter` through an MCP stdio client, never as an
unbounded unattended shell command. `--help` prints usage without serving.

Expand Down
4 changes: 4 additions & 0 deletions Scripts/check-package.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def verify(archive, receipt_path, manifest, destination=None):
raise ValueError("Archive receipt must declare its file inventory")
for name, value in inventory.items():
safe_path(name)
if receipt.get("platform") == "windows":
from windows_runtime import is_msvc_runtime
if is_msvc_runtime(PurePosixPath(name).name):
raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled")
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value):
raise ValueError("Archive inventory requires exact SHA-256 digests")
expected_name = validate_architectures(manifest, receipt["architectures"], receipt["platform"])
Expand Down
3 changes: 2 additions & 1 deletion Scripts/check-windows-package.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ if ($LASTEXITCODE -ne 0) { throw 'Cannot identify acceptance source' }
runnerArchitecture = $env:RUNNER_ARCH
existingSwiftCommands = @((Get-Command swift -All -ErrorAction SilentlyContinue).Source)
swiftToolchainInstalledByThisJob = $false
microsoftRuntime = 'system-installed prerequisite; native versions and module paths verified against the package receipt'
pristineWindowsImage = $false
sourceRebuilt = $false
authenticatedModel = $false
Expand Down Expand Up @@ -52,7 +53,7 @@ if ((Get-FileHash $codexBinary -Algorithm SHA256).Hash.ToLowerInvariant() -ne $m
throw 'Native Codex executable checksum mismatch'
}
python (Join-Path $fixture 'ProtocolCheck.py') --adapter (Join-Path $relocated 'bin/codex-mcp-adapter.exe') `
--codex $codexBinary --evidence-directory (Join-Path $evidence 'protocol') --app-local-runtime `
--codex $codexBinary --evidence-directory (Join-Path $evidence 'protocol') --app-local-runtime --package-receipt $receiptPath `
*> (Join-Path $evidence 'protocol.log')
$code = $LASTEXITCODE
Get-Content (Join-Path $evidence 'protocol.log') -Tail 80
Expand Down
18 changes: 15 additions & 3 deletions Scripts/windows_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,23 @@ def stage_runtime(repo, binary_directory, notices, sqlite, swift, command, copy_
raise ValueError("Selected Swift runtime directories and llvm-readobj are required")
report = windows_runtime.audit(binary_directory / "codex-mcp-adapter.exe", runtime_directories,
Path(os.environ["SystemRoot"]) / "System32", Path(inspector))
if any(row["role"] == "external-msvc-runtime" for row in report["libraries"]):
raise ValueError("Runtime inputs must come from the selected toolchain, not System32 redistributables")
coverage = metadata["runtime_libraries"]
copied = []
prerequisites = []
for row in report["libraries"]:
if row["role"] == "external-msvc-runtime":
windows_runtime.version_tuple(row["version"])
prerequisites.append({"name": row["name"], "minimum_version": row["version"],
"build_input_sha256": row["sha256"], "architecture": row["architecture"]})
continue
if row["role"] != "runtime":
continue
name = row["name"].casefold()
declaration = coverage.get(name)
if declaration is None:
raise ValueError(f"Runtime library lacks reviewed notice coverage: {row['name']}")
if declaration["license_scope"] != "open-source" or windows_runtime.is_msvc_runtime(name):
raise ValueError(f"Runtime library is an external prerequisite: {row['name']}")
source = Path(row["path"])
target = binary_directory / source.name
if os.path.lexists(target):
Expand All @@ -82,6 +88,10 @@ def stage_runtime(repo, binary_directory, notices, sqlite, swift, command, copy_
"notice_coverage": declaration})
if not any(row["file"].casefold() == "swiftcore.dll" for row in copied):
raise ValueError("The native adapter must include its Swift runtime")
if any(windows_runtime.is_msvc_runtime(name) for name in windows_runtime.directory_files(binary_directory)):
raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled")
if not prerequisites:
raise ValueError("Native runtime closure must declare its Microsoft prerequisites")
copy_tree(notice_root, notices / "windows-runtime")
# The original SQLite header retains its public-domain statement verbatim.
sqlite_notices = notices / "sqlite"
Expand All @@ -93,7 +103,9 @@ def stage_runtime(repo, binary_directory, notices, sqlite, swift, command, copy_
copy_file(repo / "Scripts/windows-sqlite.json", sqlite_notices / "source.json")
return {"architecture": {"aarch64": "arm64", "x86_64": "x86_64"}[report["architecture"]],
"swift_version": version[1], "libraries": copied,
"external_prerequisites": prerequisites,
"sqlite_source": sqlite["receipt"]["source"],
"sqlite_library_sha256": sqlite["receipt"]["librarySHA256"],
"notice_metadata_sha256": windows_runtime.digest(notice_root / "sources.json"),
"system_imports": [row["name"] for row in report["libraries"] if row["role"] != "runtime"]}
"system_imports": [row["name"] for row in report["libraries"]
if row["role"] in {"windows-system", "windows-api-set"}]}
105 changes: 101 additions & 4 deletions Scripts/windows_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import re
import shutil
import stat
import struct
import subprocess


Expand All @@ -25,6 +26,63 @@ def regular_file(path):
raise ValueError(f"Runtime input is a link or special file: {path}")


def is_msvc_runtime(name):
return re.fullmatch(r"(?:vcruntime|msvcp|concrt|vccorlib)\d[a-z0-9_]*\.dll", name.casefold()) is not None


def pe_architecture(path):
regular_file(path)
with path.open("rb") as source:
header = source.read(64)
if len(header) != 64 or header[:2] != b"MZ":
raise ValueError(f"Missing PE header: {path}")
source.seek(struct.unpack_from("<I", header, 60)[0])
coff = source.read(6)
if len(coff) != 6 or coff[:4] != b"PE\0\0":
raise ValueError(f"Invalid PE header: {path}")
architecture = {0x8664: "x86_64", 0xAA64: "aarch64"}.get(struct.unpack_from("<H", coff, 4)[0])
if architecture is None:
raise ValueError(f"Unsupported PE architecture: {path}")
return architecture


def file_version(path):
"""Read the fixed native version resource without executing the DLL."""
if os.name != "nt":
raise ValueError("Native file version observation requires Windows")
from ctypes import wintypes
version = ctypes.WinDLL("version", use_last_error=True)
version.GetFileVersionInfoSizeW.argtypes = [wintypes.LPCWSTR, ctypes.POINTER(wintypes.DWORD)]
version.GetFileVersionInfoSizeW.restype = wintypes.DWORD
version.GetFileVersionInfoW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPVOID]
version.GetFileVersionInfoW.restype = wintypes.BOOL
version.VerQueryValueW.argtypes = [wintypes.LPCVOID, wintypes.LPCWSTR,
ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.UINT)]
version.VerQueryValueW.restype = wintypes.BOOL
size = version.GetFileVersionInfoSizeW(str(path), None)
if not size or size > 1024 * 1024:
raise ValueError(f"Missing or oversized file version resource: {path}")
buffer = ctypes.create_string_buffer(size)
if not version.GetFileVersionInfoW(str(path), 0, size, buffer):
raise ctypes.WinError(ctypes.get_last_error())
pointer, length = wintypes.LPVOID(), wintypes.UINT()
if not version.VerQueryValueW(buffer, "\\", ctypes.byref(pointer), ctypes.byref(length)) or length.value < 52:
raise ValueError(f"Missing fixed file version: {path}")
fields = ctypes.cast(pointer, ctypes.POINTER(wintypes.DWORD))
if fields[0] != 0xFEEF04BD:
raise ValueError(f"Invalid fixed file version signature: {path}")
return ".".join(map(str, (fields[2] >> 16, fields[2] & 0xFFFF, fields[3] >> 16, fields[3] & 0xFFFF)))


def version_tuple(value):
if not isinstance(value, str) or not re.fullmatch(r"\d{1,5}(?:\.\d{1,5}){3}", value):
raise ValueError("Runtime version must have four numeric components")
result = tuple(map(int, value.split(".")))
if max(result) > 65535:
raise ValueError("Runtime version component exceeds its native bound")
return result


def imports(path, inspector):
regular_file(path)
result = subprocess.run([str(inspector), "--file-headers", "--coff-imports", str(path)],
Expand Down Expand Up @@ -65,10 +123,10 @@ def resolve(name, runtime_files, system_files):
regular_file(path)
if len({digest(path) for path in candidates}) != 1:
raise ValueError(f"Conflicting runtime DLL sources for {name}: {candidates}")
return "runtime", candidates[0]
return ("external-msvc-runtime" if is_msvc_runtime(name) else "runtime"), candidates[0]
if key in system_files:
# Visual C++ redistributables are not Windows OS components, even in System32.
role = ("external-msvc-runtime" if re.match(r"(?:vcruntime|msvcp|concrt)\d", key)
role = ("external-msvc-runtime" if is_msvc_runtime(name)
else "windows-system")
regular_file(system_files[key])
return role, system_files[key]
Expand Down Expand Up @@ -98,6 +156,8 @@ def audit(executable, runtime_directories, system_directory, inspector):
if native_arch != architecture:
raise ValueError(f"Runtime architecture mismatch: {path}: {native_arch} != {architecture}")
entry.update(architecture=native_arch, imports=dependencies)
if role == "external-msvc-runtime":
entry["version"] = file_version(path)
queue.extend((dependency, name) for dependency in dependencies)
return {"executable": str(executable), "sha256": digest(executable),
"architecture": architecture, "runtime_directories": list(map(str, runtime_directories)),
Expand Down Expand Up @@ -155,14 +215,51 @@ def loaded_modules(pid):
kernel.CloseHandle(handle)


def verify_app_local_modules(modules, executable, system_directory):
def verify_prerequisites(executable, system_directory, requirements):
local = directory_files(executable.parent)
if any(is_msvc_runtime(name) for name in local):
raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled")
architecture = pe_architecture(executable)
system = directory_files(system_directory)
result, seen = [], set()
if not requirements:
raise ValueError("Missing Microsoft runtime prerequisite declarations")
for requirement in requirements:
name = requirement["name"].casefold()
if not is_msvc_runtime(name) or name in seen:
raise ValueError("Invalid or duplicate Microsoft runtime prerequisite")
seen.add(name)
minimum = version_tuple(requirement["minimum_version"])
path = system.get(name)
if path is None:
raise ValueError(f"Install the official Microsoft Visual C++ runtime: missing {name}")
if pe_architecture(path) != architecture:
raise ValueError(f"Microsoft runtime architecture mismatch: {path}")
current = file_version(path)
if version_tuple(current) < minimum:
raise ValueError(f"Update the official Microsoft Visual C++ runtime: {name} {current} < {requirement['minimum_version']}")
result.append({"name": name, "path": str(path), "version": current,
"minimum_version": requirement["minimum_version"], "architecture": architecture,
"sha256": digest(path), "role": "external-msvc-runtime"})
return result


def verify_app_local_modules(modules, executable, system_directory, prerequisites=()):
local = directory_files(executable.parent)
if any(is_msvc_runtime(name) for name in local):
raise ValueError("Microsoft runtime DLLs must be installed separately, not bundled")
external = {row["name"].casefold(): row for row in prerequisites}
observed = set()
result = []
for path in modules:
name = path.name.casefold()
regular_file(path)
if name in local:
if is_msvc_runtime(name):
expected = external.get(name)
if expected is None or not path.samefile(Path(expected["path"])) or digest(path) != expected["sha256"]:
raise ValueError(f"Process loaded an unverified Microsoft runtime: {path}")
role = "external-msvc-runtime"
elif name in local:
if not path.samefile(local[name]):
raise ValueError(f"Packaged runtime was loaded from outside the package: {path}")
role = "app-local-runtime"
Expand Down
10 changes: 6 additions & 4 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ binds those texts to upstream commits and maps runtime libraries to notices.
public-domain statement and the pinned source identity. The package receipt
separately binds the selected runtime DLLs and compiled SQLite library.

Microsoft Visual C++ runtime files retain their separate Microsoft redistribution
terms. The applicable publisher license must permit redistribution; their
presence in the Swift toolchain does not establish that permission. See the
[Microsoft redistribution guidance](https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170).
The Microsoft Visual C++ runtime is a user-installed prerequisite, supplied by
Microsoft's official installer. The plugin archive carries the Swift/open-source
runtime closure and records external Microsoft runtime requirements in its receipt.
The upstream provenance mapping includes Microsoft entries for identification;
those entries are not a bundled-file inventory. See the
[official runtime downloads](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170).

An ad-hoc signature and checksum support local integrity checks. They do not
establish official publisher provenance, Developer ID signing, notarization, or
Expand Down
Loading
Loading