Skip to content

ci: zod MCP schema gate + codex-desktop release reminder#2

Merged
avifenesh merged 4 commits into
mainfrom
ci/zod-schema-and-release-reminder
May 21, 2026
Merged

ci: zod MCP schema gate + codex-desktop release reminder#2
avifenesh merged 4 commits into
mainfrom
ci/zod-schema-and-release-reminder

Conversation

@avifenesh
Copy link
Copy Markdown
Collaborator

Why

Follow-up to #1. The boolean outputSchema regression was only catchable by the in-house mcp_safety_check.py heuristics, not by the actual validation strict clients run. This adds a real zod gate using @modelcontextprotocol/sdk so the exact failure can never ship again, plus a release-time reminder to keep codex-desktop-linux in sync.

What

zod schema validation (both pre- and post-release)

  • scripts/zod-check/ — self-contained Node ESM check that does the MCP initialize handshake, calls tools/list, and parses the result with ListToolsResultSchema (the same AssertObjectSchema path that rejected received: true in Bug: outputSchema.properties.received is true (boolean) causing Zod validation failure in MCP clients #1). CI-only; excluded from the published npm files.
  • zod-schema job — pre-release gate against the locally built binary; added to release-binary.needs, so a broken schema blocks publish.
  • validate-published job — runs after publish-npm, installs the published @agent-sh/computer-use-linux@<version> and re-runs the zod check against it.

release reminder

  • notify-codex-desktop job — on a v* tag, opens an issue in this repo reminding to sync codex-desktop-linux with the release content (extracts the matching CHANGELOG section). Uses the default GITHUB_TOKEN (issues: write); dedupes by title.

Verification (local)

Guard against regressions of #1 (boolean outputSchema nodes that strict
MCP clients reject) by validating tools/list with the actual
@modelcontextprotocol/sdk zod schema:

- scripts/zod-check: self-contained Node check that parses tools/list
  with ListToolsResultSchema, the same validation mcphub/Claude Desktop
  run. CI-only; not part of the published npm package.
- zod-schema job: pre-release gate against the locally built binary,
  added to release-binary's needs so a broken schema can never publish.
- validate-published job: post-publish, installs the published npm
  package and re-runs the zod check against it.

Also add notify-codex-desktop: on a release tag, open a reminder issue
in this repo to sync codex-desktop-linux with the release content.
Comment thread .github/workflows/ci.yml Fixed
Comment thread .github/workflows/ci.yml Fixed
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a regression guard for MCP tool list responses by adding a Node.js script that validates server output against Zod schemas. The script performs an initialization handshake and parses the tool list to ensure compatibility with strict clients. Review feedback suggested improving the robustness of the script by refining command-line argument parsing to handle missing values and ensuring safe UTF-8 processing of the server's stdout stream by explicitly setting the encoding.

Comment on lines +18 to +27
function parseArgs(argv) {
const args = { command: 'target/debug/computer-use-linux' };
for (let i = 0; i < argv.length; i += 1) {
if (argv[i] === '--command') {
args.command = argv[i + 1];
i += 1;
}
}
return args;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parseArgs function does not handle cases where --command is provided without a following value (e.g., as the last argument). This results in args.command being set to undefined, which causes spawn to throw a TypeError synchronously. Adding a check for the existence of the next argument makes the script more robust.

function parseArgs(argv) {
  const args = { command: 'target/debug/computer-use-linux' };
  for (let i = 0; i < argv.length; i += 1) {
    if (argv[i] === '--command' && argv[i + 1]) {
      args.command = argv[i + 1];
      i += 1;
    }
  }
  return args;
}


const { command } = parseArgs(process.argv.slice(2));

const child = spawn(command, ['mcp'], { stdio: ['pipe', 'pipe', 'inherit'] });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It is safer to set the encoding on the stdout stream to 'utf8'. This ensures that the 'data' event emits strings and correctly handles multi-byte UTF-8 characters that might be split across chunk boundaries. Using chunk.toString('utf8') on raw buffers can occasionally lead to corrupted characters if the split happens in the middle of a multi-byte sequence.

const child = spawn(command, ['mcp'], { stdio: ['pipe', 'pipe', 'inherit'] });
child.stdout.setEncoding('utf8');

Comment thread scripts/zod-check/check.mjs Outdated
Comment on lines +48 to +49
child.stdout.on('data', (chunk) => {
buffer += chunk.toString('utf8');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With stdout encoding set to 'utf8', the chunk is already a string. You can append it directly to the buffer, which is cleaner and avoids redundant toString() calls.

child.stdout.on('data', (chunk) => {
  buffer += chunk;

avifenesh added 2 commits May 21, 2026 10:41
- Use `npm ci --ignore-scripts` for the MCP SDK install in both the
  pre-release gate and the post-publish job: reproducible from the
  committed lockfile and no third-party install scripts run. The
  published-package install keeps scripts so its postinstall can fetch
  the release binary.
- check.mjs: don't fall through after reporting a ZodError.
- parseArgs: ignore a trailing --command with no value instead of
  setting command to undefined (would throw in spawn).
- set stdout encoding to utf8 and append chunks directly, so multi-byte
  characters split across stream chunks are decoded correctly.
@avifenesh
Copy link
Copy Markdown
Collaborator Author

Addressed the gemini-code-assist review in 0f4aec9's follow-up:

  • parseArgs now ignores a trailing --command with no value (falls back to the default) instead of passing undefined to spawn.
  • child.stdout.setEncoding('utf8') + direct chunk append, so multi-byte UTF-8 split across chunks decodes correctly.

CodeQL flagged dtolnay/rust-toolchain@stable and Swatinem/rust-cache@v2 as unpinned — these match the existing convention across every job in ci.yml and predate this PR, so pinning is left to a separate repo-wide pass.

CodeQL flagged dtolnay/rust-toolchain@stable and Swatinem/rust-cache@v2
as unpinned non-immutable actions. Pin both to commit SHAs repo-wide
(version kept in a trailing comment) so the whole workflow is consistent,
not just the new zod-schema job. dtolnay/rust-toolchain's `toolchain`
input defaults to `stable`, so dropping the @stable ref keeps the stable
channel; no job overrides it.
@avifenesh avifenesh merged commit ea11854 into main May 21, 2026
20 checks passed
@avifenesh avifenesh deleted the ci/zod-schema-and-release-reminder branch May 21, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants