hanzo-mcp 1.1.30 for every platform the CLI ships, and 2.4.20 takes --project-dir
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 2m14s
Hanzo CI/CD / test-amd64 (push) Successful in 6m17s
Hanzo CI/CD / cicd (push) Successful in 4m15s
CI/CD / cicd (push) Successful in 10m38s

The release lane built Linux alone, so hanzo.sh offered macOS and
Windows a server nobody had built, and `hanzo dev` there fell back to
nothing. It now cross-builds linux, windows and darwin for both
architectures from the one Linux pool with Zig and a pinned macOS SDK,
the way the CLI's release does, one target at a time. Zig comes from its
own checksummed tarball, not pip.

`hanzo dev` hands its server `--project-dir`, the native spelling. The
npm build read it as stray arguments and exited, so a machine whose
PATH found the npm `hanzo-mcp` first ran every session without the
toolset. It now takes the flag as an allowed root.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
This commit is contained in:
2026-10-14 20:51:41 -07:00
parent 4da4b72cd9
commit 41533d452a
7 changed files with 109 additions and 64 deletions
+50 -30
View File
@@ -4,8 +4,8 @@
# There was no such lane. hanzo.yml declares a TypeScript gate and publish.yml # There was no such lane. hanzo.yml declares a TypeScript gate and publish.yml
# ships the npm package; neither produces a binary, so the tarballs on the # ships the npm package; neither produces a binary, so the tarballs on the
# rust-v1.1.23 release arrived some other way and only ever covered Linux. # rust-v1.1.23 release arrived some other way and only ever covered Linux.
# hanzo.sh offers `mcp` as a native download, so on macOS it asked for a file # hanzo.sh offers `mcp` as a native download on every platform the CLI ships
# nobody had built. # for, so every one of them is built here.
# #
# Tagged rust-v*, because the binary is the Rust crate in rust/ — its # Tagged rust-v*, because the binary is the Rust crate in rust/ — its
# Cargo.toml is the one that calls itself hanzo-mcp. # Cargo.toml is the one that calls itself hanzo-mcp.
@@ -22,9 +22,16 @@ on:
permissions: permissions:
contents: write contents: write
env:
ZIG_VERSION: '0.13.0'
ZIG_SHA256: 'd45312e61ebcc48032b77bc4cf7fd6915c11fa16e4aad116b66c9468211230ea'
ZIGBUILD_VERSION: '0.23.0'
MACOS_SDK_URL: 'https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz'
MACOS_SDK_SHA256: 'cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4'
jobs: jobs:
# The release has to exist before anything can be attached to it, and four # The release has to exist before anything can be attached to it, and five
# matrix jobs racing to create it is three failures and a coin toss. One job, # matrix jobs racing to create it is four failures and a coin toss. One job,
# first, and the builds wait on it. # first, and the builds wait on it.
open: open:
runs-on: linux-amd64 runs-on: linux-amd64
@@ -42,35 +49,52 @@ jobs:
# empty. A missing target is a missing file somebody can chase; a missing # empty. A missing target is a missing file somebody can chase; a missing
# release is an install that breaks for everyone. # release is an install that breaks for everyone.
fail-fast: false fail-fast: false
# One at a time: five cross-builds on one runner host's disk at once is
# what the CLI's matrix measured taking the host down.
max-parallel: 1
matrix: matrix:
include: include:
- target: linux-amd64 - { target: linux-amd64, triple: x86_64-unknown-linux-gnu, bin: hanzo-mcp }
triple: x86_64-unknown-linux-gnu - { target: linux-arm64, triple: aarch64-unknown-linux-gnu, bin: hanzo-mcp }
# The org's runners are linux-amd64 only, so arm64 is cross-built here: - { target: windows-amd64, triple: x86_64-pc-windows-gnu, bin: hanzo-mcp.exe }
# zig is the C toolchain ring needs. darwin waits for a macOS runner. - { target: darwin-amd64, triple: x86_64-apple-darwin, bin: hanzo-mcp, apple: true }
- target: linux-arm64 - { target: darwin-arm64, triple: aarch64-apple-darwin, bin: hanzo-mcp, apple: true }
triple: aarch64-unknown-linux-gnu
zig: true
runs-on: linux-amd64 runs-on: linux-amd64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable
targets: ${{ matrix.triple }} targets: ${{ matrix.triple }}
# Every target is cross-linked by Zig from this one Linux pool, as the CLI's
# release is: no macOS or Windows runner exists, and none is needed.
- name: Zig (the cross-linker)
run: |
set -eu
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" -o zig.tar.xz
echo "${ZIG_SHA256} zig.tar.xz" | sha256sum -c -
mkdir -p "$HOME/zig"
tar -xf zig.tar.xz -C "$HOME/zig" --strip-components=1
echo "$HOME/zig" >> "$GITHUB_PATH"
- name: cargo-zigbuild
run: cargo install cargo-zigbuild --version "${ZIGBUILD_VERSION}" --locked
# Zig ships no macOS frameworks (cocoa and objc link AppKit), so a pinned
# SDK is vendored and SDKROOT names it.
- name: Vendor macOS SDK
if: matrix.apple
run: |
set -eu
curl -fsSL "${MACOS_SDK_URL}" -o macsdk.tar.xz
echo "${MACOS_SDK_SHA256} macsdk.tar.xz" | sha256sum -c -
tar -xf macsdk.tar.xz -C "$HOME"
echo "SDKROOT=$HOME/MacOSX11.3.sdk" >> "$GITHUB_ENV"
- name: Build - name: Build
working-directory: rust working-directory: rust
run: | run: cargo zigbuild --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
set -eu
rustup target add "${{ matrix.triple }}" 2>/dev/null || true
if [ "${{ matrix.zig || false }}" = true ]; then
python3 -m venv "$RUNNER_TEMP/zig"
"$RUNNER_TEMP/zig/bin/pip" install --quiet ziglang cargo-zigbuild
export PATH="$RUNNER_TEMP/zig/bin:$PATH"
cargo zigbuild --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
else
cargo build --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
fi
# The name the installer asks for, and the checksum it refuses to install # The name the installer asks for, and the checksum it refuses to install
# without. Both are part of the contract in hanzo.sh, not decoration. # without. Both are part of the contract in hanzo.sh, not decoration.
@@ -78,14 +102,10 @@ jobs:
run: | run: |
set -eu set -eu
asset="hanzo-mcp-${{ matrix.target }}.tar.gz" asset="hanzo-mcp-${{ matrix.target }}.tar.gz"
tar -C "rust/target/${{ matrix.triple }}/release" -czf "$asset" hanzo-mcp tar -C "rust/target/${{ matrix.triple }}/release" -czf "$asset" "${{ matrix.bin }}"
if command -v sha256sum >/dev/null 2>&1; then # "<hash> <name>", the line `sha256sum -c` reads: install.sh verifies
# "<hash> <name>", the line `sha256sum -c` reads: install.sh verifies # the download with exactly that, and a bare hash fails it.
# the download with exactly that, and a bare hash fails it. sha256sum "$asset" > "$asset.sha256"
sha256sum "$asset" > "$asset.sha256"
else
shasum -a 256 "$asset" > "$asset.sha256"
fi
ls -l "$asset" "$asset.sha256" ls -l "$asset" "$asset.sha256"
- name: Attach - name: Attach
+50 -30
View File
@@ -4,8 +4,8 @@
# There was no such lane. hanzo.yml declares a TypeScript gate and publish.yml # There was no such lane. hanzo.yml declares a TypeScript gate and publish.yml
# ships the npm package; neither produces a binary, so the tarballs on the # ships the npm package; neither produces a binary, so the tarballs on the
# rust-v1.1.23 release arrived some other way and only ever covered Linux. # rust-v1.1.23 release arrived some other way and only ever covered Linux.
# hanzo.sh offers `mcp` as a native download, so on macOS it asked for a file # hanzo.sh offers `mcp` as a native download on every platform the CLI ships
# nobody had built. # for, so every one of them is built here.
# #
# Tagged rust-v*, because the binary is the Rust crate in rust/ — its # Tagged rust-v*, because the binary is the Rust crate in rust/ — its
# Cargo.toml is the one that calls itself hanzo-mcp. # Cargo.toml is the one that calls itself hanzo-mcp.
@@ -23,9 +23,16 @@ on:
permissions: permissions:
contents: write contents: write
env:
ZIG_VERSION: '0.13.0'
ZIG_SHA256: 'd45312e61ebcc48032b77bc4cf7fd6915c11fa16e4aad116b66c9468211230ea'
ZIGBUILD_VERSION: '0.23.0'
MACOS_SDK_URL: 'https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz'
MACOS_SDK_SHA256: 'cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4'
jobs: jobs:
# The release has to exist before anything can be attached to it, and four # The release has to exist before anything can be attached to it, and five
# matrix jobs racing to create it is three failures and a coin toss. One job, # matrix jobs racing to create it is four failures and a coin toss. One job,
# first, and the builds wait on it. # first, and the builds wait on it.
open: open:
runs-on: linux-amd64 runs-on: linux-amd64
@@ -43,35 +50,52 @@ jobs:
# empty. A missing target is a missing file somebody can chase; a missing # empty. A missing target is a missing file somebody can chase; a missing
# release is an install that breaks for everyone. # release is an install that breaks for everyone.
fail-fast: false fail-fast: false
# One at a time: five cross-builds on one runner host's disk at once is
# what the CLI's matrix measured taking the host down.
max-parallel: 1
matrix: matrix:
include: include:
- target: linux-amd64 - { target: linux-amd64, triple: x86_64-unknown-linux-gnu, bin: hanzo-mcp }
triple: x86_64-unknown-linux-gnu - { target: linux-arm64, triple: aarch64-unknown-linux-gnu, bin: hanzo-mcp }
# The org's runners are linux-amd64 only, so arm64 is cross-built here: - { target: windows-amd64, triple: x86_64-pc-windows-gnu, bin: hanzo-mcp.exe }
# zig is the C toolchain ring needs. darwin waits for a macOS runner. - { target: darwin-amd64, triple: x86_64-apple-darwin, bin: hanzo-mcp, apple: true }
- target: linux-arm64 - { target: darwin-arm64, triple: aarch64-apple-darwin, bin: hanzo-mcp, apple: true }
triple: aarch64-unknown-linux-gnu
zig: true
runs-on: linux-amd64 runs-on: linux-amd64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable
targets: ${{ matrix.triple }} targets: ${{ matrix.triple }}
# Every target is cross-linked by Zig from this one Linux pool, as the CLI's
# release is: no macOS or Windows runner exists, and none is needed.
- name: Zig (the cross-linker)
run: |
set -eu
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" -o zig.tar.xz
echo "${ZIG_SHA256} zig.tar.xz" | sha256sum -c -
mkdir -p "$HOME/zig"
tar -xf zig.tar.xz -C "$HOME/zig" --strip-components=1
echo "$HOME/zig" >> "$GITHUB_PATH"
- name: cargo-zigbuild
run: cargo install cargo-zigbuild --version "${ZIGBUILD_VERSION}" --locked
# Zig ships no macOS frameworks (cocoa and objc link AppKit), so a pinned
# SDK is vendored and SDKROOT names it.
- name: Vendor macOS SDK
if: matrix.apple
run: |
set -eu
curl -fsSL "${MACOS_SDK_URL}" -o macsdk.tar.xz
echo "${MACOS_SDK_SHA256} macsdk.tar.xz" | sha256sum -c -
tar -xf macsdk.tar.xz -C "$HOME"
echo "SDKROOT=$HOME/MacOSX11.3.sdk" >> "$GITHUB_ENV"
- name: Build - name: Build
working-directory: rust working-directory: rust
run: | run: cargo zigbuild --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
set -eu
rustup target add "${{ matrix.triple }}" 2>/dev/null || true
if [ "${{ matrix.zig || false }}" = true ]; then
python3 -m venv "$RUNNER_TEMP/zig"
"$RUNNER_TEMP/zig/bin/pip" install --quiet ziglang cargo-zigbuild
export PATH="$RUNNER_TEMP/zig/bin:$PATH"
cargo zigbuild --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
else
cargo build --release --locked --bin hanzo-mcp --target "${{ matrix.triple }}"
fi
# The name the installer asks for, and the checksum it refuses to install # The name the installer asks for, and the checksum it refuses to install
# without. Both are part of the contract in hanzo.sh, not decoration. # without. Both are part of the contract in hanzo.sh, not decoration.
@@ -79,14 +103,10 @@ jobs:
run: | run: |
set -eu set -eu
asset="hanzo-mcp-${{ matrix.target }}.tar.gz" asset="hanzo-mcp-${{ matrix.target }}.tar.gz"
tar -C "rust/target/${{ matrix.triple }}/release" -czf "$asset" hanzo-mcp tar -C "rust/target/${{ matrix.triple }}/release" -czf "$asset" "${{ matrix.bin }}"
if command -v sha256sum >/dev/null 2>&1; then # "<hash> <name>", the line `sha256sum -c` reads: install.sh verifies
# "<hash> <name>", the line `sha256sum -c` reads: install.sh verifies # the download with exactly that, and a bare hash fails it.
# the download with exactly that, and a bare hash fails it. sha256sum "$asset" > "$asset.sha256"
sha256sum "$asset" > "$asset.sha256"
else
shasum -a 256 "$asset" > "$asset.sha256"
fi
ls -l "$asset" "$asset.sha256" ls -l "$asset" "$asset.sha256"
- name: Attach - name: Attach
+1 -1
View File
@@ -21,7 +21,7 @@ export interface RecipeResult {
*/ */
export async function createRecipeClient() { export async function createRecipeClient() {
// Kai is reached as every client reaches it: api.hanzo.ai with HANZO_API_KEY, or LOCAL_KAI_URL for a local `kai serve`. // Kai is reached as every client reaches it: api.hanzo.ai with HANZO_API_KEY, or LOCAL_KAI_URL for a local `kai serve`.
const server = await createMCPServer({ name: "kai-cookbook-server", version: "2.4.19" }); const server = await createMCPServer({ name: "kai-cookbook-server", version: "2.4.20" });
// Call tools directly via server handlers or in-memory transport // Call tools directly via server handlers or in-memory transport
return { return {
server, server,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@hanzo/mcp", "name": "@hanzo/mcp",
"version": "2.4.19", "version": "2.4.20",
"packageManager": "[email protected]", "packageManager": "[email protected]",
"description": "Hanzo MCP Server - Unified tool surface with AI, cloud control, vector search, and multi-framework UI", "description": "Hanzo MCP Server - Unified tool surface with AI, cloud control, vector search, and multi-framework UI",
"main": "dist/index.js", "main": "dist/index.js",
+1 -1
View File
@@ -1265,7 +1265,7 @@ dependencies = [
[[package]] [[package]]
name = "hanzo-mcp" name = "hanzo-mcp"
version = "1.1.29" version = "1.1.30"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
+1 -1
View File
@@ -3,7 +3,7 @@ members = ["trigram", "actions", "shell"]
[package] [package]
name = "hanzo-mcp" name = "hanzo-mcp"
version = "1.1.29" version = "1.1.30"
repository = "https://github.com/hanzoai/mcp" repository = "https://github.com/hanzoai/mcp"
homepage = "https://hanzo.ai" homepage = "https://hanzo.ai"
keywords = ["mcp", "ai", "tools", "hanzo", "llm"] keywords = ["mcp", "ai", "tools", "hanzo", "llm"]
+5
View File
@@ -56,6 +56,7 @@ program
.option('-p, --port <port>', 'Port for HTTP transport', '3000') .option('-p, --port <port>', 'Port for HTTP transport', '3000')
.option('--project <path>', 'Project path for context', process.cwd()) .option('--project <path>', 'Project path for context', process.cwd())
.option('--allow-path <path...>', 'Allowed paths for client operations') .option('--allow-path <path...>', 'Allowed paths for client operations')
.option('--project-dir <path...>', 'Filesystem root the server may operate under; the native hanzo-mcp spelling, which `hanzo dev` passes')
.option('--beta-safe', 'Offer only the safe validated beta tools (core + tool resolution, next action, command guard, loop breaker)') .option('--beta-safe', 'Offer only the safe validated beta tools (core + tool resolution, next action, command guard, loop breaker)')
.option('--full-surface', 'Enable full legacy tool surface (multi-word aliases and optional categories)') .option('--full-surface', 'Enable full legacy tool surface (multi-word aliases and optional categories)')
.option('--enable-ui', 'Enable UI tools') .option('--enable-ui', 'Enable UI tools')
@@ -72,6 +73,10 @@ program
.option('--disable-tools <tools>', 'Comma-separated list of tools to disable') .option('--disable-tools <tools>', 'Comma-separated list of tools to disable')
.option('--enable-categories <categories>', 'Comma-separated list of categories to enable (files,search,shell,edit)') .option('--enable-categories <categories>', 'Comma-separated list of categories to enable (files,search,shell,edit)')
.action(async (options) => { .action(async (options) => {
if (options.projectDir?.length) {
options.allowPath = [...(options.allowPath ?? []), ...options.projectDir];
options.project = options.projectDir[0];
}
const toolConfig = surface(options); const toolConfig = surface(options);
const tools = getConfiguredTools(toolConfig); const tools = getConfiguredTools(toolConfig);