mcp-bin
v0.3.0
Published
Run a prebuilt MCP server binary straight from its GitHub releases — npx fetches the arch-matched asset, caches it in ~/.mcp-bin, and execs it. Zero config.
Maintainers
Readme
mcp-bin
Run a prebuilt MCP server binary straight from its GitHub or GitLab releases — no global install, no build step, no config.
npx mcp-bin <release url | owner/repo[@tag]>npx fetches this tiny launcher; the launcher downloads the release asset matching your computer's OS/arch, caches it under ~/.mcp-bin, and runs it so it speaks MCP directly to the client.
Once cached, a server is reused forever — no update checks, no network. This is deliberate: an MCP server must start instantly. Moving to a newer release is an explicit action (--ttl or mcp-bin expire), never a surprise.
Single process
On macOS/Linux the launcher does not stay resident: it uses process.execve (Node ≥ 22) to replace its own image with the server binary — same PID, no idle Node parent. Windows has no execve equivalent, so there the launcher stays as a thin supervisor — one extra Node process that just passes stdio straight through (unavoidable on Windows). With npx there is also npm's own npx supervisor; to drop that, install the launcher (npm i -g mcp-bin) and use "command": "mcp-bin" instead of npx.
Use in an MCP client
Point the MCP command at mcp-bin and pass the server's release as the first arg. The server is resolved & cached on first launch; env is passed through to it unchanged.
Claude Code — ~/.claude.json (global) or project .mcp.json. Cursor — ~/.cursor/mcp.json (global) or project .cursor/mcp.json. Same shape for both:
{
"mcpServers": {
"youtrack": {
"command": "npx",
"args": ["-y", "mcp-bin", "https://github.com/sensiarion/youtrack-mcp/releases/latest"],
"env": {
"YOUTRACK_URL": "https://youtrack.example.com",
"YOUTRACK_TOKEN": "perm-xxxx"
}
}
}
}Pin a known-good version (recommended for teams — reproducible, still zero-maintenance):
{
"mcpServers": {
"youtrack": {
"command": "npx",
"args": ["-y", "mcp-bin", "sensiarion/[email protected]"],
"env": { "YOUTRACK_URL": "https://youtrack.example.com", "YOUTRACK_TOKEN": "perm-xxxx" }
}
}
}The cached binary lands at ~/.mcp-bin/<repo>/<tag>/<binary> (override the root with MCP_BIN_DIR in env). No global install, nothing on PATH.
Source argument
owner/repo and an optional pinned tag are extracted from any of these:
https://github.com/OWNER/REPO/releases/latest— newest releasehttps://github.com/OWNER/REPO/releases/tag/v1.2.3— pinnedhttps://gitlab.example.com/OWNER/REPO/-/releases/v1.2.3— pinned (GitLab, incl. self-hosted)[email protected]:OWNER/[email protected]— SSH formOWNER/REPO— newest release (GitHub)OWNER/[email protected]— pinned
Anything after the source is forwarded to the binary as arguments (a leading -- is stripped):
npx mcp-bin OWNER/REPO -- --flag valueFull CLI:
mcp-bin [--ttl <dur>] <source> [-- server-args...]
mcp-bin expire <source> [--all]Public releases only
mcp-bin fetches releases anonymously — public repos only. Earlier
versions tried to reuse git credential fill for private repos, but the
GitHub/GitLab Releases API needs a PAT/OAuth scope (not basic auth), so it
never actually authenticated; it only managed to surface password prompts
for users with saved git credentials. Removed in v0.3.0. For private tools
use cargo install / npm i -g <pkg> directly.
Updating
The default is a no-update policy: a cached server is reused forever. Move forward explicitly:
mcp-bin expire <source>— clears the cached latest pointer so the next launch re-resolves and downloads the current newest release. Add--allto also delete every cached binary version for that repo.npx mcp-bin expire sensiarion/youtrack-mcp # next 'latest' launch re-resolves npx mcp-bin expire sensiarion/youtrack-mcp --all # purge all cached versions--ttl <dur>(orMCP_BIN_TTL) — time-based refresh oflatest: re-check only when the recorded resolution is older than the duration. A pinned@tagis immutable and ignores ttl.Duration values:
| Value | Meaning | |---|---| |
30s| 30 seconds | |10m| 10 minutes | |6h| 6 hours | |1d/7d| 1 / 7 days | |90| bare number = seconds | |0| check on every launch | | unset | never re-check (default) |Use it on the CLI or in the client config:
npx mcp-bin --ttl 7d sensiarion/youtrack-mcp"args": ["-y", "mcp-bin", "--ttl", "7d", "sensiarion/youtrack-mcp"]
Caching
~/.mcp-bin/<repo>/<tag>/<binary>
~/.mcp-bin/<repo>/latest.json # last resolved "latest" tag + timestamp(Non-GitHub hosts get a <host>-- prefix on the repo folder so caches never collide.)
- Pinned
@tag: immutable — once cached, runs with zero network calls, ever. latest(default): if any version is cached, the newest cached one runs with zero network calls. The release API is contacted only when nothing is cached yet, when--ttlsays the record is stale, or aftermcp-bin expire.- If the release API is unreachable, the newest already-cached version is used (graceful offline fallback).
Override the cache root with MCP_BIN_DIR.
CLI shims — ~/.mcp-bin/bin
Every launch also links the resolved binary into a flat ~/.mcp-bin/bin/
directory under the name the asset ships (e.g. thin-worktree,
thin-worktree.exe). Only the newest cached version of each binary is
exposed — re-launching a newer release replaces the entry, so the name always
points at the latest. It is a symlink where the OS allows one, otherwise a
copy (Windows without Developer Mode).
This lets a cached MCP server double as a normal CLI — useful for plugin
hooks and scripts that must invoke the tool directly, not over MCP. Add the
directory to your PATH (the binary appears there after the server has been
launched at least once):
# bash/zsh — ~/.zshrc or ~/.bashrc
export PATH="$HOME/.mcp-bin/bin:$PATH"# Windows PowerShell profile ($PROFILE)
$env:Path = "$HOME\.mcp-bin\bin;$env:Path"Honors MCP_BIN_DIR (the shim dir is $MCP_BIN_DIR/bin). Linking is
best-effort: a failure never aborts the server launch.
Integrity
If the release publishes a checksum for the asset, the download is verified before it is cached or run. A mismatch aborts the launch — nothing is cached. Supported, in priority order:
- a per-asset sidecar —
<asset>.sha256or<asset>.sha512(the cargo-dist default); - a combined list —
SHA256SUMS,SHA512SUMS,checksums.txt.
Both the bare-digest and the sha256sum <hex> <file> forms are accepted. If no checksum asset is published, the download proceeds unverified (no failure) — so this is automatic protection when authors ship sums, and a no-op otherwise.
Environment
| Var | Purpose |
|---|---|
| MCP_BIN_DIR | Cache directory (default ~/.mcp-bin) |
| MCP_BIN_TTL | Default --ttl for latest refresh (e.g. 7d); unset = never re-check |
All other env vars are passed through to the launched binary untouched.
Requirements
- Node.js ≥ 22 — hard requirement. macOS/Linux run as a single process via
process.execve; Windows (noexecve) keeps one thin Node supervisor that passes stdio through. Only Node built-ins are used; zero dependencies. tar(and/orunzip) onPATHfor archive assets
For server authors
mcp-bin imposes no special packaging — it just needs release assets whose names carry an arch token and an OS token for the host:
| Host | Arch tokens | OS tokens |
|---|---|---|
| macOS | aarch64/arm64, x86_64/amd64 | apple-darwin, darwin, macos |
| Linux | x86_64/amd64, aarch64/arm64 | unknown-linux, linux |
| Windows | x86_64/amd64, aarch64 | pc-windows, windows, win |
Archives (.tar.xz, .tar.gz, .tgz, .tar.bz2, .zip) and raw binaries are supported. Signatures, installers and source.* are ignored as binary candidates; a .sha256/.sha512 (or SHA256SUMS) asset is not ignored — it is used to verify the download (see Integrity). Extraction uses the system tar (bsdtar on macOS/Windows reads .zip and .tar.*; GNU tar reads .tar.*), falling back to unzip for .zip.
Easiest path: cargo-dist. Its default asset naming is exactly this layout and it publishes per-asset .sha256 files, so a cargo-dist-released MCP server works with mcp-bin out of the box — arch/OS-matched and checksum-verified, no extra config on either side. Tag a release, let cargo-dist build the per-target archives, and users can npx mcp-bin OWNER/REPO immediately. Non-cargo-dist pipelines work too, as long as asset names include the arch/OS tokens above; ship a .sha256 alongside each archive to get verification.
Development
Single file, no deps, no build. Run it straight from source:
node bin/mcp-bin.js sensiarion/youtrack-mcp
node -c bin/mcp-bin.js # syntax check
node --test # test suite (Node built-in runner, no deps)
MCP_BIN_DIR=$(mktemp -d) node bin/mcp-bin.js OWNER/REPO # isolated cache for testingTests (test/*.test.js) are unit (pure helpers) + offline CLI integration — no network. CI (.github/workflows/ci.yml) runs them on Node 22/24 × Linux/macOS/Windows for every push & PR; the publish workflow re-runs them before npm publish.
Release (publish to npm)
.github/workflows/publish.yml publishes to npm on every v* tag (with provenance). One-time: add an NPM_TOKEN repo secret (npm automation token, publish scope).
To cut a release:
# bump version in package.json (must equal the tag, sans leading v)
npm version patch # or: minor / major — creates the commit + vX.Y.Z tag
git push origin main --follow-tagsThe workflow verifies package.json version == tag, runs a CLI sanity check, then npm publish --provenance --access public.
License
MIT
