Skip to content

feat: codex-shell-tool-mcp#7005

Merged
bolinfest merged 1 commit intomainfrom
pr7005
Nov 21, 2025
Merged

feat: codex-shell-tool-mcp#7005
bolinfest merged 1 commit intomainfrom
pr7005

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Nov 20, 2025

This adds a GitHub workflow for building a new npm module we are experimenting with that contains an MCP server for running Bash commands. The new workflow, shell-tool-mcp, is a dependency of the general release workflow so that we continue to use one version number for all artifacts across the project in one GitHub release.

.github/workflows/shell-tool-mcp.yml is the primary workflow introduced by this PR, which does the following:

  • builds the codex-exec-mcp-server and codex-execve-wrapper executables for both arm64 and x64 versions of Mac and Linux (preferring the MUSL version for Linux)
  • builds Bash (dynamically linked) for a [comically] large number of platforms (both x64 and arm64 for most) with a small patch specified by shell-tool-mcp/patches/bash-exec-wrapper.patch:
    • debian-11
    • debian-12
    • ubuntu-20.04
    • ubuntu-22.04
    • ubuntu-24.04
    • centos-9
    • macos-13 (x64 only)
    • macos-14 (arm64 only)
    • macos-15 (arm64 only)
  • builds the TypeScript for the [new] Node module declared in the shell-tool-mcp/ folder, which creates bin/mcp-server.js
  • adds all of the native binaries to shell-tool-mcp/vendor/ folder; bin/mcp-server.js does a runtime check to determine which ones to execute
  • uses npm pack to create the .tgz for the module
  • if publish: true is set, invokes the npm publish call with the .tgz

The justification for building Bash for so many different operating systems is because, since it is dynamically linked, we want to increase our confidence that the version we build is compatible with the glibc whatever OS we end up running on. (Note this is less of a concern with codex-exec-mcp-server and codex-execve-wrapper on Linux, as they are statically linked.)

This PR also introduces the code for the npm module in shell-tool-mcp/ (the proposed module name is @openai/codex-shell-tool-mcp). Initially, I intended the module to be a single file of vanilla JavaScript (like codex-cli/bin/codex.js), but some of the logic seemed a bit tricky, so I decided to port it to TypeScript and add unit tests.

shell-tool-mcp/src/index.ts defines the main() function for the module, which performs runtime checks to determine the clang triple to find the path to the Rust executables within the vendor/ folder (resolveTargetTriple()). It uses a combination of readOsRelease() and resolveBashPath() to determine the correct Bash executable to run in the environment. Ultimately, it spawns a command like the following:

codex-exec-mcp-server \
    --execve codex-execve-wrapper \
    --bash custom-bash "$@"

Note .github/workflows/shell-tool-mcp-ci.yml defines a fairly standard CI job for the module (format/build/test).

To test this PR, I pushed this branch to my personal fork of Codex and ran the CI job there:

https://github.com/bolinfest/codex/actions/runs/19564311320

Admittedly, the graph looks a bit wild now:

Screenshot 2025-11-20 at 11 44 58 PM

But when it finished, I was able to download codex-shell-tool-mcp-npm from the Artifacts for the workflow in an empty temp directory, unzip the .zip and then the .tgz inside it, followed by xattr -rc . to remove the quarantine bits. Then I ran:

npx @modelcontextprotocol/inspector node /private/tmp/foobar4/package/bin/mcp-server.js

which launched the MCP Inspector and I was able to use it as expected! This bodes well that this should work once the package is published to npm:

npx @modelcontextprotocol/inspector npx @openai/codex-shell-tool-mcp

Also, to verify the package contains what I expect:

/tmp/foobar4/package$ tree
.
├── bin
│   └── mcp-server.js
├── package.json
├── README.md
└── vendor
    ├── aarch64-apple-darwin
    │   ├── bash
    │   │   ├── macos-14
    │   │   │   └── bash
    │   │   └── macos-15
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    ├── aarch64-unknown-linux-musl
    │   ├── bash
    │   │   ├── centos-9
    │   │   │   └── bash
    │   │   ├── debian-11
    │   │   │   └── bash
    │   │   ├── debian-12
    │   │   │   └── bash
    │   │   ├── ubuntu-20.04
    │   │   │   └── bash
    │   │   ├── ubuntu-22.04
    │   │   │   └── bash
    │   │   └── ubuntu-24.04
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    ├── x86_64-apple-darwin
    │   ├── bash
    │   │   └── macos-13
    │   │       └── bash
    │   ├── codex-exec-mcp-server
    │   └── codex-execve-wrapper
    └── x86_64-unknown-linux-musl
        ├── bash
        │   ├── centos-9
        │   │   └── bash
        │   ├── debian-11
        │   │   └── bash
        │   ├── debian-12
        │   │   └── bash
        │   ├── ubuntu-20.04
        │   │   └── bash
        │   ├── ubuntu-22.04
        │   │   └── bash
        │   └── ubuntu-24.04
        │       └── bash
        ├── codex-exec-mcp-server
        └── codex-execve-wrapper

26 directories, 26 files

@bolinfest bolinfest changed the base branch from main to pr6973 November 20, 2025 16:33
@bolinfest bolinfest force-pushed the pr7005 branch 2 times, most recently from 5f81440 to 9fb1355 Compare November 20, 2025 23:05
@bolinfest bolinfest force-pushed the pr7005 branch 2 times, most recently from f0cc39b to 0bb0c48 Compare November 21, 2025 00:07
@bolinfest bolinfest force-pushed the pr6973 branch 2 times, most recently from 0401532 to 50ab543 Compare November 21, 2025 00:14
@bolinfest bolinfest force-pushed the pr7005 branch 2 times, most recently from 3c3bedc to a3821f1 Compare November 21, 2025 00:22
bolinfest added a commit that referenced this pull request Nov 21, 2025
…6972)

This updates `ExecParams` so that instead of taking `timeout_ms:
Option<u64>`, it now takes a more general cancellation mechanism,
`ExecExpiration`, which is an enum that includes a
`Cancellation(tokio_util::sync::CancellationToken)` variant.

If the cancellation token is fired, then `process_exec_tool_call()`
returns in the same way as if a timeout was exceeded.

This is necessary so that in #6973, we can manage the timeout logic
external to the `process_exec_tool_call()` because we want to "suspend"
the timeout when an elicitation from a human user is pending.








---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/6972).
* #7005
* #6973
* __->__ #6972
bolinfest added a commit that referenced this pull request Nov 21, 2025
…l timeout (#6973)

Previously, we were running into an issue where we would run the `shell`
tool call with a timeout of 10s, but it fired an elicitation asking for
user approval, the time the user took to respond to the elicitation was
counted agains the 10s timeout, so the `shell` tool call would fail with
a timeout error unless the user is very fast!

This PR addresses this issue by introducing a "stopwatch" abstraction
that is used to manage the timeout. The idea is:

- `Stopwatch::new()` is called with the _real_ timeout of the `shell`
tool call.
- `process_exec_tool_call()` is called with the `Cancellation` variant
of `ExecExpiration` because it should not manage its own timeout in this
case
- the `Stopwatch` expiration is wired up to the `cancel_rx` passed to
`process_exec_tool_call()`
- when an elicitation for the `shell` tool call is received, the
`Stopwatch` pauses
- because it is possible for multiple elicitations to arrive
concurrently, it keeps track of the number of "active pauses" and does
not resume until that counter goes down to zero

I verified that I can test the MCP server using
`@modelcontextprotocol/inspector` and specify `git status` as the
`command` with a timeout of 500ms and that the elicitation pops up and I
have all the time in the world to respond whereas previous to this PR,
that would not have been possible.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/6973).
* #7005
* __->__ #6973
* #6972
Base automatically changed from pr6973 to main November 21, 2025 00:45
@bolinfest bolinfest force-pushed the pr7005 branch 5 times, most recently from 596b330 to 8c0fb09 Compare November 21, 2025 01:54
@bolinfest bolinfest force-pushed the pr7005 branch 6 times, most recently from 336da53 to 9df5baf Compare November 21, 2025 07:58
@bolinfest bolinfest marked this pull request as ready for review November 21, 2025 08:03
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +239 to +241
- runner: macos-13
target: x86_64-apple-darwin
variant: macos-13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge macOS 13 arm64 Bash artifact missing

The reusable build only produces a macOS 13 Bash binary for x86_64-apple-darwin (matrix lines 239-241). However the launcher uses selectDarwinBash to pick the macos-13 variant whenever os.release() reports Darwin 22 (macOS 13) regardless of CPU, and resolveTargetTriple returns aarch64-apple-darwin on Apple Silicon. On macOS 13 arm64 hosts this workflow never builds vendor/aarch64-apple-darwin/bash/macos-13/bash, so mcp-server.js will throw “Required binary missing” at startup on that platform. Please add an arm64 macOS 13 build or adjust selection to avoid targeting an absent binary.

Useful? React with 👍 / 👎.

@bolinfest bolinfest merged commit d363a09 into main Nov 21, 2025
53 checks passed
@bolinfest bolinfest deleted the pr7005 branch November 21, 2025 16:16
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants