Skip to content
Open
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
7 changes: 5 additions & 2 deletions .github/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ source contracts only; it never builds release binaries or container images.
Release Please maintains the version and changelog PR. Publishing the resulting
stable GitHub release triggers the heavy release workflows:

- `release.yml` builds and smokes Linux and Windows archives, builds and scans
the container, verifies and attaches artifacts, publishes npm and MCP
- `release.yml` builds and smokes Linux, macOS, and Windows archives, builds and
scans the container, verifies and attaches artifacts, publishes npm and MCP
Registry metadata, and signs/attests release outputs.
- `build-incus-image.yml` uses the central hosted Incus image workflow and
publishes checksum-verified image assets plus the rolling Incus alias.
Expand All @@ -82,6 +82,9 @@ ARM64 workflow, installer, and package contracts are explicitly enabled for
Labby through the pinned fleet policy and repository contract. Keep that opt-in
visible when adding ARM64 jobs or artifacts; QEMU and cross-platform emulation
still require a deliberate implementation and verification plan.
The supported binary artifacts are Linux x86_64, macOS arm64, and Windows
x86_64. Keep each target native to its GitHub-hosted runner; do not add
emulation, cross-platform image matrices, or QEMU setup.

## Editing rules

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: release-please
# On every green CI run on main, release-please maintains a "release PR" that
# bumps `.release-please-manifest.json` and updates CHANGELOG.md. Merging that
# PR creates the git tag (vX.Y.Z) and a draft GitHub Release. Publishing that
# release triggers the GitHub-hosted release workflows for Linux/Windows
# release triggers the GitHub-hosted release workflows for Linux/macOS/Windows
# archives, the GHCR image, and the Incus artifact.
#
# release-type is "simple", NOT "rust". release-please's rust/cargo-workspace
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ jobs:
runner: '"windows-latest"'
binary: labby.exe
archive: lab-x86_64-pc-windows-msvc.zip
- target: aarch64-apple-darwin
runner: '"macos-15"'
binary: labby
archive: lab-aarch64-apple-darwin.tar.gz
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
Expand All @@ -111,6 +115,8 @@ jobs:
- name: Install Rust and kache
if: runner.os != 'Windows'
uses: ./.github/actions/setup-rust-kache
with:
targets: ${{ matrix.target }}
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
if: runner.os == 'Windows'
with:
Expand All @@ -133,7 +139,11 @@ jobs:
mkdir -p dist
cp "target/${{ matrix.target }}/release/${{ matrix.binary }}" "dist/${{ matrix.binary }}"
tar -C dist -czf "${{ matrix.archive }}" "${{ matrix.binary }}"
sha256sum "${{ matrix.archive }}" > "${{ matrix.archive }}.sha256"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${{ matrix.archive }}" > "${{ matrix.archive }}.sha256"
else
shasum -a 256 "${{ matrix.archive }}" > "${{ matrix.archive }}.sha256"
fi

- name: Smoke packaged Code Mode runner (Unix)
if: runner.os != 'Windows'
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,13 @@ labby mcp
`labby serve` starts the hosted HTTP runtime: `/v1` product APIs, `/mcp`
streamable HTTP MCP, auth routes, OAuth relay endpoints, and static Labby web
assets when an export is available. `labby mcp` is the stdio MCP entrypoint for
local MCP clients.
local MCP clients. A client configured to launch `labby mcp` does not need an
HTTP URL: when a `labby serve` daemon is reachable, the stdio process becomes a
transparent bridge to that daemon and uses its gateway configuration, upstream
connections, and OAuth state. If no daemon is found and no explicit target is
set, it starts a standalone local gateway instead. See the
[local bridge guide](./docs/surfaces/TRANSPORT.md#local-bridge-to-the-running-daemon)
for client configuration and `LABBY_SERVER_URL` fail-closed behavior.

### Manage Upstream MCP Gateways

Expand Down Expand Up @@ -543,8 +549,8 @@ provider tools. It does not install ACP adapters or mount ACP-specific state.

Release Please maintains the version/changelog pull request and creates the
stable tag plus draft GitHub release when that pull request merges. Publishing
the release triggers the heavy GitHub-hosted x86_64 workflows. They build Linux
and Windows archives with checksums, build and scan the GHCR image, build and
the release triggers the heavy GitHub-hosted workflows. They build Linux,
macOS, and Windows archives with checksums, build and scan the GHCR image, build and
smoke the Incus image, publish the npm launcher, and publish Labby's
`server.json` metadata to the official MCP Registry.

Expand Down
2 changes: 1 addition & 1 deletion crates/labby-codemode/src/runner_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ fn invalid_param(message: impl Into<String>) -> ToolError {
}
}

#[cfg(test)]
#[cfg(all(test, target_os = "linux"))]
mod tests {
use super::*;

Expand Down
9 changes: 9 additions & 0 deletions crates/labby/tests/ci_changed_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,15 @@ fn draft_releases_are_surfaced_without_being_auto_published() {
fn release_tool_downloads_are_version_and_digest_pinned() {
let release = fs::read_to_string(repo_root().join(".github/workflows/release.yml"))
.expect("read release workflow");
for target in [
"target: x86_64-unknown-linux-gnu",
"target: aarch64-apple-darwin",
"target: x86_64-pc-windows-msvc",
] {
assert!(release.contains(target), "release matrix missing {target}");
}
assert!(release.contains("runner: '\"macos-15\"'"));
assert!(!release.contains("x86_64-apple-darwin"));
assert!(!release.contains("/latest/download/"));
assert!(!release.contains("mcp-publisher"));
assert!(!release.contains("registry.modelcontextprotocol.io"));
Expand Down
2 changes: 1 addition & 1 deletion docs/TECH.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The workspace metadata in the root `Cargo.toml` is authoritative:
- Cargo resolver 3
- workspace version shared by the Rust crates
- AGPL-3.0-only license
- release targets: Linux x86_64 GNU and Windows x86_64 MSVC
- release targets: Linux x86_64 GNU, macOS arm64, and Windows x86_64 MSVC

`rust-toolchain.toml` pins the toolchain used locally and in CI. The matching
`rust-version` in `Cargo.toml` is the minimum version Cargo will accept.
Expand Down
12 changes: 7 additions & 5 deletions docs/runtime/CICD.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ land the required code/tests and the baseline update together.
- Required fast jobs run only when their category is enabled; `ci-gate` is the stable required check for branch protection
- Native Windows workspace and Palette jobs use GitHub-hosted runners, bounded timeouts, and keyed Cargo caches; they report portability regressions without blocking `ci-gate`
- Heavy release work starts only from a published stable GitHub release
- Release Linux jobs use GitHub-hosted x86_64 runners; native Windows artifacts use GitHub-hosted Windows
- Release Linux jobs use GitHub-hosted x86_64 runners; native macOS and Windows artifacts use GitHub-hosted runners

The pinned fleet policy and repository contract set `allow-arm64: true` for
Labby. This removes the former fleet-wide ARM64 token rejection while keeping
Expand All @@ -203,11 +203,13 @@ documented in [Actions runner setup](./ACTIONS_RUNNER.md).
| Platform | Target |
|----------|--------|
| Linux x86_64 | `x86_64-unknown-linux-gnu` |
| macOS arm64 | `aarch64-apple-darwin` |
| Windows x86_64 | `x86_64-pc-windows-msvc` |

Windows is a supported platform. Official Windows release artifacts are built
on native GitHub-hosted Windows runners using the MSVC target. Linux-to-Windows
GNU cross-compilation may be useful experimentally, but it is not the release
macOS and Windows are supported platforms. Official macOS artifacts are built
on a native GitHub-hosted Apple Silicon runner. Official Windows artifacts are
built on native GitHub-hosted Windows runners using the MSVC target.
Cross-compilation may be useful experimentally, but it is not the release
support contract.

## Integration Tests
Expand Down Expand Up @@ -252,7 +254,7 @@ partially published release.

- **Surface:** GitHub Releases
- **Container surface:** GitHub Container Registry (`ghcr.io/dinglebear-ai/labby`)
- **Artifacts per release:** one binary archive per supported target (Linux x86_64 and Windows x86_64)
- **Artifacts per release:** one binary archive per supported target (Linux x86_64, macOS arm64, and Windows x86_64)
- **Checksums:** every binary archive has a SHA-256 checksum file
- **Package registries:** the `@dinglebear/labby` npm launcher and `server.json` MCP Registry metadata publish from the same validated version.

Expand Down
14 changes: 10 additions & 4 deletions docs/services/UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ Add one or more `[[upstream]]` entries to `~/.config/labby/config.toml`.

Set bearer-token env vars named by `bearer_token_env` in `~/.labby/.env` or the process environment.

### 3. Start `lab`
### 3. Start `labby`

For local stdio clients:

Expand All @@ -652,21 +652,27 @@ For network MCP clients:
labby serve
```

### 4. Point the client at `lab`, not the upstreams
### 4. Point the client at `labby`, not the upstreams

Example `.mcp.json` for stdio:

```json
{
"mcpServers": {
"lab": {
"labby": {
"command": "labby",
"args": ["serve"]
"args": ["mcp"]
}
}
}
```

This is the local stdio bridge: the client does not need an HTTP URL. If a
`labby serve` daemon is already running, `labby mcp` forwards the session to
that daemon; otherwise it starts a standalone local gateway. See the
[transport guide](../surfaces/TRANSPORT.md#local-bridge-to-the-running-daemon)
for explicit-target and fallback behavior.

Example HTTP MCP endpoint:

```text
Expand Down
46 changes: 46 additions & 0 deletions docs/surfaces/TRANSPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,52 @@ labby mcp
Stdio is intended for local editor and desktop clients. Protocol messages use
stdin/stdout; logs must never be written to stdout.

### Local bridge to the running daemon

Use `labby mcp` when the MCP client can launch a local command but should use
the same gateway state as a long-running `labby serve` daemon. The client talks
to `labby mcp` over stdin/stdout, so the client configuration does not need an
HTTP URL:

```json
{
"mcpServers": {
"labby": {
"command": "labby",
"args": ["mcp"]
}
}
}
```

Start the daemon once, either in a terminal or as the installed user service:

```bash
labby serve --host 127.0.0.1 --port 8765
```

When `labby mcp` starts, it probes the local daemon. If one is reachable, it
becomes a transparent stdio-to-MCP bridge: tools, resources, prompts,
notifications, cancellation, and task responses are forwarded to the daemon.
The bridge does not create a second gateway manager, upstream pool, or OAuth
state, so the local client sees the same configuration and connections as the
web UI and HTTP clients.

If no explicit remote target is configured and no daemon is found, `labby mcp`
starts a standalone local gateway instead. This is useful for a fully local
setup, but it has its own configuration and runtime state. To require one
specific daemon and prevent that standalone fallback, set `LABBY_SERVER_URL`:

```bash
LABBY_SERVER_URL=http://127.0.0.1:8765 labby mcp
```

Explicit targets fail closed when they are invalid, unreachable, unauthorized,
or not a compatible Labby daemon. For a daemon on another host, use an HTTPS
`LABBY_SERVER_URL` and the daemon's matching `LABBY_MCP_HTTP_TOKEN`; use the
HTTP MCP endpoint directly when the client cannot launch local commands. The
full target and token rules are in [Environment](../runtime/ENV.md#remote-gateway-cli-usage).

## Streamable HTTP

Run:
Expand Down
12 changes: 9 additions & 3 deletions packages/labby-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,13 @@ labby mcp
`labby serve` starts the hosted HTTP runtime: `/v1` product APIs, `/mcp`
streamable HTTP MCP, auth routes, OAuth relay endpoints, and static Labby web
assets when an export is available. `labby mcp` is the stdio MCP entrypoint for
local MCP clients.
local MCP clients. A client configured to launch `labby mcp` does not need an
HTTP URL: when a `labby serve` daemon is reachable, the stdio process becomes a
transparent bridge to that daemon and uses its gateway configuration, upstream
connections, and OAuth state. If no daemon is found and no explicit target is
set, it starts a standalone local gateway instead. See the
[local bridge guide](./docs/surfaces/TRANSPORT.md#local-bridge-to-the-running-daemon)
for client configuration and `LABBY_SERVER_URL` fail-closed behavior.

### Manage Upstream MCP Gateways

Expand Down Expand Up @@ -543,8 +549,8 @@ provider tools. It does not install ACP adapters or mount ACP-specific state.

Release Please maintains the version/changelog pull request and creates the
stable tag plus draft GitHub release when that pull request merges. Publishing
the release triggers the heavy GitHub-hosted x86_64 workflows. They build Linux
and Windows archives with checksums, build and scan the GHCR image, build and
the release triggers the heavy GitHub-hosted workflows. They build Linux,
macOS, and Windows archives with checksums, build and scan the GHCR image, build and
smoke the Incus image, publish the npm launcher, and publish Labby's
`server.json` metadata to the official MCP Registry.

Expand Down
12 changes: 11 additions & 1 deletion packages/labby-mcp/lib/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ function targetFor(platform = process.platform, arch = process.arch) {
};
}

throw new Error(`Unsupported platform ${platform}/${arch}. Supported targets: linux/x64, win32/x64.`);
if (platform === "darwin" && arch === "arm64") {
return {
asset: "lab-aarch64-apple-darwin.tar.gz",
binary: "labby",
archiveType: "tar.gz",
};
}

throw new Error(
`Unsupported platform ${platform}/${arch}. Supported targets: linux/x64, darwin/arm64, win32/x64.`,
);
}

function releaseVersion(env = process.env) {
Expand Down
2 changes: 1 addition & 1 deletion packages/labby-mcp/scripts/check-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ function supportedTargets(platform) {
const tuples = [
["linux", "x64"],
["win32", "x64"],
["darwin", "x64"],
["darwin", "arm64"],
["linux", "ppc64"],
];
const targets = [];
Expand Down
5 changes: 5 additions & 0 deletions packages/labby-mcp/test/platform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ test("maps supported platforms to release assets", () => {
binary: "labby.exe",
archiveType: "zip",
});
assert.deepEqual(targetFor("darwin", "arm64"), {
asset: "lab-aarch64-apple-darwin.tar.gz",
binary: "labby",
archiveType: "tar.gz",
});
});

test("rejects unsupported platforms", () => {
Expand Down
8 changes: 7 additions & 1 deletion scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ target_triple() {
*) fail "unsupported platform ${os}/${arch}; supported: Linux/x86_64" ;;
esac
;;
*) fail "unsupported platform ${os}/${arch}; supported: Linux/x86_64" ;;
Darwin)
case "$arch" in
arm64) echo "aarch64-apple-darwin" ;;
*) fail "unsupported platform ${os}/${arch}; supported: macOS/arm64" ;;
esac
;;
*) fail "unsupported platform ${os}/${arch}; supported: Linux/x86_64 and macOS/arm64" ;;
esac
}

Expand Down