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
110 changes: 110 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Release Binaries

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g. v1.0.6)'
required: true

jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: darwin-arm64
runner: macos-14
outfile: code-executor-mcp-darwin-arm64
bun_target: bun-darwin-arm64
- target: darwin-x64
runner: macos-13
outfile: code-executor-mcp-darwin-x64
bun_target: bun-darwin-x64
- target: linux-x64
runner: ubuntu-latest
outfile: code-executor-mcp-linux-x64
bun_target: bun-linux-x64
- target: linux-arm64
runner: ubuntu-22.04-arm
outfile: code-executor-mcp-linux-arm64
bun_target: bun-linux-arm64
- target: windows-x64
runner: windows-latest
outfile: code-executor-mcp-windows-x64.exe
bun_target: bun-windows-x64

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Embed templates
run: bun run embed-templates

- name: Build binary
shell: bash
run: |
mkdir -p bin
bun build --compile --minify --sourcemap --external pyodide \
--target=${{ matrix.bun_target }} \
src/index.ts \
--outfile=bin/${{ matrix.outfile }}

- name: Smoke test (host targets only)
if: matrix.target == 'darwin-arm64' || matrix.target == 'linux-x64'
shell: bash
run: |
./bin/${{ matrix.outfile }} --help 2>&1 | head -5 || true
file ./bin/${{ matrix.outfile }}
ls -lh ./bin/${{ matrix.outfile }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.outfile }}
path: bin/${{ matrix.outfile }}
if-no-files-found: error

release:
name: Create GitHub Release
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts

- name: Flatten artifact directories
run: |
mkdir -p release
find artifacts -type f -exec cp {} release/ \;
ls -lh release/

- name: Create checksums
working-directory: release
run: |
shasum -a 256 * > SHA256SUMS

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: release/*
generate_release_notes: true
fail_on_unmatched_files: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ node_modules/

# Build output
dist/
bin/
.*.bun-build

# Test coverage
coverage/
Expand Down
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,32 @@ Traditional MCP exposes all 47 tools upfront (141k tokens). Code Executor expose

### Option 1: Interactive Setup Wizard (Recommended)

Don't configure manually. Our wizard does everything:
Don't configure manually. Our wizard does everything.

**Via npm (Node.js 22+):**
```bash
npm install -g code-executor-mcp
code-executor-mcp setup
```

**Via bun (Bun 1.1+):**
```bash
bunx code-executor-mcp setup
```

**Via standalone binary (no Node.js or Bun required):**

Download the binary for your platform from the [Releases page](https://github.com/aberemia24/code-executor-MCP/releases) and run it directly:
```bash
# macOS (Apple Silicon)
curl -L -o code-executor-mcp https://github.com/aberemia24/code-executor-MCP/releases/latest/download/code-executor-mcp-darwin-arm64
chmod +x code-executor-mcp
./code-executor-mcp setup
```
Available targets: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `windows-x64`.

The binary still needs **Deno** (for TypeScript sandbox) and **Python 3** (for native Python sandbox) on `PATH`. The setup wizard verifies these and shows install instructions if missing.

**What the wizard does:**
1. 🔍 Scans for existing MCP configs (Claude Code `~/.claude.json`, Cursor `~/.cursor/mcp.json`, project `.mcp.json`)
2. ⚙️ Configures with smart defaults (or customize interactively)
Expand Down Expand Up @@ -719,6 +738,50 @@ Docker deployment automatically generates complete MCP configuration from enviro

See [DOCKER_TESTING.md](DOCKER_TESTING.md) for security details and [docker-compose.example.yml](docker-compose.example.yml) for all available configuration options.

### Build Standalone Binary (from Source)

To produce the single-file binary locally — useful for development, custom patches, or self-contained deployment without npm/Node on `PATH`:

```bash
git clone https://github.com/aberemia24/code-executor-MCP.git
cd code-executor-MCP
npm install

# Host target only (current platform) → ./bin/code-executor-mcp (~63 MB)
npm run build:binary

# Or cross-compile to all five targets:
# bin/code-executor-mcp-darwin-arm64
# bin/code-executor-mcp-darwin-x64
# bin/code-executor-mcp-linux-x64
# bin/code-executor-mcp-linux-arm64
# bin/code-executor-mcp-windows-x64.exe
npm run build:binary:all
```

**Requirements:** Bun 1.1+ on `PATH` (the `npm run` scripts invoke `bun build --compile`). Templates are embedded at build time via `scripts/embed-templates.ts` (auto-run before the compile step). Pyodide is excluded from the binary to keep size down; the npm/`npx` install path keeps Pyodide available if you need it.

**Wire the binary into `mcpServers`** (replace `npx`/`node` with the absolute path):

```json
{
"mcpServers": {
"code-executor": {
"command": "/absolute/path/to/code-executor-MCP/bin/code-executor-mcp",
"args": [],
"env": {
"MCP_CONFIG_PATH": "/full/path/to/.mcp.json",
"DENO_PATH": "/opt/homebrew/bin/deno",
"ENABLE_AUDIT_LOG": "true",
"AUDIT_LOG_PATH": "/Users/you/.code-executor/audit.log"
}
}
}
}
```

Compared to `npx -y code-executor-mcp`: no Node.js needed on `PATH`, faster cold-start (no npm resolution/extraction), and a single artifact you can pin and ship alongside your config.

### Local Development

```bash
Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"embed-templates": "bun run scripts/embed-templates.ts",
"build:binary": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide src/index.ts --outfile=bin/code-executor-mcp",
"build:binary:darwin-arm64": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide --target=bun-darwin-arm64 src/index.ts --outfile=bin/code-executor-mcp-darwin-arm64",
"build:binary:darwin-x64": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide --target=bun-darwin-x64 src/index.ts --outfile=bin/code-executor-mcp-darwin-x64",
"build:binary:linux-x64": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide --target=bun-linux-x64 src/index.ts --outfile=bin/code-executor-mcp-linux-x64",
"build:binary:linux-arm64": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide --target=bun-linux-arm64 src/index.ts --outfile=bin/code-executor-mcp-linux-arm64",
"build:binary:windows-x64": "bun run embed-templates && bun build --compile --minify --sourcemap --external pyodide --target=bun-windows-x64 src/index.ts --outfile=bin/code-executor-mcp-windows-x64.exe",
"build:binary:all": "npm run build:binary:darwin-arm64 && npm run build:binary:darwin-x64 && npm run build:binary:linux-x64 && npm run build:binary:linux-arm64 && npm run build:binary:windows-x64",
"start": "node dist/index.js",
"server": "npm run build && node dist/index.js",
"generate-wrappers": "npm run build && node dist/wrapper-generator.js",
Expand Down
58 changes: 58 additions & 0 deletions scripts/embed-templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Generate src/cli/templates/embedded.ts from templates/* files.
*
* Templates ship two ways:
* 1. As loose files under `templates/` (npm/npx/bunx install) — preferred path
* 2. Embedded as string constants in the binary (bun --compile distribution)
*
* This script regenerates the embedded constants whenever templates change.
* Run via `npm run embed-templates` (wired into prebuild).
*/

import { readFile, writeFile, readdir } from 'fs/promises';
import { fileURLToPath } from 'url';
import * as path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
const OUTPUT_PATH = path.resolve(__dirname, '..', 'src', 'cli', 'templates', 'embedded.ts');

async function main(): Promise<void> {
const files = await readdir(TEMPLATES_DIR);
const entries: Array<[string, string]> = [];

for (const filename of files.sort()) {
const filePath = path.join(TEMPLATES_DIR, filename);
const content = await readFile(filePath, 'utf-8');
entries.push([filename, content]);
}

const lines: string[] = [
'/**',
' * Auto-generated by scripts/embed-templates.ts. Do not edit by hand.',
' *',
' * Templates embedded as string constants for the bun --compile single-binary',
' * distribution where templates/ is not on disk. The filesystem path is',
' * preferred when available (npm install).',
' */',
'',
'export const EMBEDDED_TEMPLATES = {',
];

for (const [name, content] of entries) {
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(content)},`);
}

lines.push('} as const;');
lines.push('');
lines.push('export type EmbeddedTemplateName = keyof typeof EMBEDDED_TEMPLATES;');
lines.push('');

await writeFile(OUTPUT_PATH, lines.join('\n'), 'utf-8');
console.log(`Embedded ${entries.length} templates → ${path.relative(process.cwd(), OUTPUT_PATH)}`);
}

main().catch((err: unknown) => {
console.error(err);
process.exit(1);
});
23 changes: 23 additions & 0 deletions src/cli/runtime-detect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Runtime detection helpers.
*
* Used to gate features that don't make sense in a compiled single-binary
* distribution (e.g., self-installation via `npm install -g`).
*/

declare const Bun: { version?: string } | undefined;

/**
* True when running as a `bun --compile` compiled binary.
*
* Heuristic: Bun is defined and `process.execPath` is not the `bun` runtime
* itself — in compiled mode it points to the produced binary instead.
*
* Override with `CODE_EXECUTOR_FORCE_COMPILED=1` for testing.
*/
export function isCompiledBinary(): boolean {
if (process.env.CODE_EXECUTOR_FORCE_COMPILED === '1') return true;
if (typeof Bun === 'undefined') return false;
const exec = process.execPath.toLowerCase();
return !exec.endsWith('/bun') && !exec.endsWith('\\bun.exe') && !exec.endsWith('bun');
}
8 changes: 7 additions & 1 deletion src/cli/self-installer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawn } from 'child_process';
import { isCompiledBinary } from './runtime-detect.js';

/**
* SelfInstaller - Detects and installs code-executor-mcp globally
Expand Down Expand Up @@ -108,8 +109,13 @@ Or configure npm to use a user-writable directory: https://docs.npmjs.com/resolv
* @throws Error with remediation message if installation fails
*/
async runBootstrap(): Promise<void> {
if (isCompiledBinary()) {
console.log('📦 Running as compiled binary — skipping npm self-install.');
return;
}

console.log('🔍 Checking if code-executor-mcp is globally installed...');

const isInstalled = await this.detectGlobalInstall();

if (isInstalled) {
Expand Down
Loading
Loading