ao-mcp-bridge
v1.0.4
Published
stdio ↔ HTTP bridge for Claude Desktop ↔ ao-mcp-server with OAuth 2.1 + PKCE + Dynamic Client Registration. Single-file, zero runtime dependencies.
Downloads
740
Maintainers
Readme
ao-mcp-bridge
Single-file stdio ↔ HTTP bridge for connecting Claude Desktop to ao-mcp-server. Supports
API-key mode (default) and OAuth 2.1 + PKCE + Dynamic Client Registration (opt-in
via --oauth).
┌─────────────────────────────────────┐
Claude Desktop ──stdio──▶│ ao-mcp-bridge (this package) │──HTTP──▶ ao-mcp-server (:8080)
(JSON-RPC) │ • Bearer / API-key forwarding │ │
│ • OAuth flow on 401 │ ▼
│ • Server→client SSE notifs │ ao-mcp-security-service (:9000)
│ • DELETE /mcp on shutdown │ │
└─────────────────────────────────────┘ ▼
AO Platform (:8090)Table of contents
- What's new in 1.0.3
- Why this bridge exists
- Features
- Requirements
- Distribution options
- Platform paths reference
- Claude Desktop configuration
- Updating to a new version
- CLI flag reference
- Persisted state
- Logs & debugging
- For maintainers — Publishing to npm
- For maintainers — Building the standalone executable
- Known limitations
What's new in 1.0.3
Production-hardening release. Fully backwards-compatible defaults — no flag changes required to upgrade from 1.0.2. New behavior is opt-in via new flags; bug fixes trigger only in failure paths 1.0.2 already handled less gracefully. See CHANGELOG.md for full detail.
Fixed
tools/callfailures now reach the Claude Desktop tool-call UI (returned asresult.isError = trueper MCP spec instead of a top-level JSON-RPCerrorthat the client renders as blank). Non-tools/callfailures still use top-level JSON-RPC errors — that's the correct channel for those.- Non-JSON / non-SSE 200 responses (e.g., HTML maintenance page) no longer silently drop and hang the client; the bridge surfaces a clear error.
--debugbody logging now redacts JSON / form-urlencoded fields matchingtoken / secret / password / credential / authorization / api_key / code / *_token / client_secret. Headers were already redacted in 1.0.2.
Added
- HTTPS hardening:
--ca-file,--client-cert+--client-key(mTLS),--require-https(fail-closed). - Cancellation:
notifications/cancelledaborts the matching upstream POST immediately so the platform stops working on a result the client no longer wants. - Concurrent forwards: a long Semantic SQL / NLQ tool call no longer blocks other tool calls or notifications.
- Bounded retry on 502/503/504 (3 attempts, 500 ms / 1000 ms backoff).
--max-body-bytes(default 50 MB) — OOM guard against pathological responses.--log-format json— one structured event per stderr line for fleet log aggregation. Events:startup,request,response,auth_401,retry_5xx,cancel_in_flight,sse_open,sse_error,sse_giveup,teardown_abort, each with stable fields (mcp_method,mcp_id,latency_ms, etc.).
Changed
- License:
UNLICENSED→Apache-2.0. No code change; clarifies usage rights for the public-registry distribution.
Why this bridge exists
Existing bridges fail against ao-mcp-security-service's ephemeral proxy-{uuid} client
pattern, or simply lack OAuth support:
| Bridge | Failure mode |
| --- | --- |
| [email protected] | Stale lockfile wedges callback port 38351 across sessions → "site can't be reached" |
| mcp-remote@latest | DCR client-info persistence bug → Existing OAuth client information is required when exchanging an authorization code |
| supergateway | No OAuth — exits on first 401 |
| Claude Desktop native remote MCP | Gated off in current enterprise build |
This bridge is ~600 lines of Node with zero runtime dependencies, engineered specifically to avoid those failure modes.
Features
- Fresh random callback port every OAuth flow — no lockfile, no cross-session coordination
- Persisted DCR client info keyed by auth-server issuer (survives restarts)
- Auto re-auth on 401 — initial and mid-session, including platform 401s propagated by
PlatformUnauthorizedPropagationFilter - Refresh-token support — silent token rotation, no browser prompt when access token expires
- Streamable HTTP — POST /mcp with JSON or SSE responses
- Long-lived GET /mcp SSE — server→client
tools/list_changed, etc. reach Claude Desktop - DELETE /mcp on shutdown — clean server-side session teardown
- Header injection —
--header(repeatable) forX-Platform-Api-Key, tracing IDs, tenant routing, etc. OAuth'sAuthorizationalways wins over user-supplied auth headers - stderr-only logging — keeps stdout clean for JSON-RPC framing;
--debugadds verbose request/response with secrets redacted
Requirements
| For… | Need |
| --- | --- |
| Running the bridge from source | Node ≥ 20.6 |
| Running via npx from npm | Node ≥ 20.6 (npx ships with npm) |
| Running the standalone .exe | Nothing — Node runtime is embedded |
| Building the standalone .exe | Node ≥ 20.6 (host OS == target OS — no cross-compile) |
| Publishing to npm | Node ≥ 20.6, npm account, write access to the package name |
Distribution options
Three ways to ship the bridge to end users — pick the one that fits your environment:
| Option | End user needs | Install effort | Best for |
| --- | --- | --- | --- |
| A. npx + npm | Node | Zero — fetches on first run | Most end users; auto-updates if pinned to a range |
| B. Local checkout | Node + git clone | Manual | Developers / debugging |
| C. Standalone .exe | Nothing | Copy the file | Locked-down machines without Node |
Each option corresponds to a Claude Desktop configuration mode below.
Platform paths reference
The Modes below use placeholders like <bridge-path>. Substitute the right value for your
operating system:
Claude Desktop config file
| OS | Path |
| --- | --- |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
<bridge-path> — absolute path to mcp-bridge.mjs (Modes 1–4 only)
| OS | Example |
| --- | --- |
| Windows | C:\\path\\to\\ao-mcp-server\\scripts\\mcp-bridge.mjs (note: backslashes must be escaped in JSON) |
| macOS / Linux | /path/to/ao-mcp-server/scripts/mcp-bridge.mjs |
<exe-path> — absolute path to the built executable (Mode 6 only)
| OS | Example |
| --- | --- |
| Windows | C:\\Tools\\mcp-bridge.exe |
| macOS / Linux | /usr/local/bin/mcp-bridge |
Bridge state directory
| OS | Path |
| --- | --- |
| Windows | %USERPROFILE%\.ao-mcp-bridge\ |
| macOS / Linux | ~/.ao-mcp-bridge/ |
Claude Desktop logs
| OS | Path |
| --- | --- |
| Windows | %APPDATA%\Claude\logs\mcp-server-ao-mcp-server.log |
| macOS | ~/Library/Logs/Claude/mcp-server-ao-mcp-server.log |
| Linux | ~/.config/Claude/logs/mcp-server-ao-mcp-server.log |
Claude Desktop configuration
Edit the config file at the path for your OS. Restart Claude Desktop after editing.
Always pass
--timeout 600(10 minutes) so long-running Semantic SQL / NLQ tool calls don't hit the bridge's default 60 s POST timeout. Every example includes it.
Quick reference
| Mode | OAuth | Headers | Distribution | Use case |
|---|---|---|---|---|
| 1 | ✓ | — | Local checkout | Pure SSO. First launch opens browser; tokens cached. |
| 2 | — | X-Platform-Api-Key | Local checkout | Static credential, no browser. |
| 3 | ✓ | tracing / tenant / region | Local checkout | SSO with operational headers. |
| 4 | ✓ | X-Platform-Api-Key | Local checkout | API-key by default; OAuth as backstop. |
| 5 | any | any | npm registry | End users — no clone, no PATH setup. |
| 6 | any | any | Pre-built .exe | Machines without Node. |
Mode 1 — OAuth-only (SSO)
{
"mcpServers": {
"ao-mcp-server": {
"command": "node",
"args": [
"<bridge-path>",
"http://127.0.0.1:8080/mcp",
"--oauth",
"--timeout", "600"
]
}
}
}First launch runs discovery → DCR → PKCE, opens your browser. Tokens are cached in the bridge state directory; subsequent launches skip the prompt. Mid-session 401 (token expired and refresh failed) re-runs the browser flow automatically.
Mode 2 — API-key only
{
"mcpServers": {
"ao-mcp-server": {
"command": "node",
"args": [
"<bridge-path>",
"http://127.0.0.1:8080/mcp",
"--header", "X-Platform-Api-Key: <YOUR_API_KEY>",
"--timeout", "600"
]
}
}
}API key on every request. No browser ever opens. On 401, surfaces an error to Claude Desktop:
tools/callfailures arrive as a successful result withisError: trueand the message incontent[0].text(so the tool-call UI displays the reason).- Non-
tools/callfailures (e.g.,tools/list) arrive as a top-level JSON-RPC error.
Mode 3 — OAuth + custom headers
{
"mcpServers": {
"ao-mcp-server": {
"command": "node",
"args": [
"<bridge-path>",
"http://127.0.0.1:8080/mcp",
"--oauth",
"--header", "X-Request-Id: claude-desktop",
"--header", "X-Tenant-Id: acme",
"--header", "X-Region: us-east-1",
"--timeout", "600"
]
}
}
}OAuth provides the Authorization: Bearer <jwt>; --header adds non-auth headers (tracing,
tenant routing, region pinning). The fresh OAuth Bearer is applied after --header, so a
stale or accidental --header "Authorization: ..." cannot stomp the live token.
--header is repeatable. Names are logged at startup; values are never logged.
Mode 4 — Mixed (API-key with OAuth fallback)
{
"mcpServers": {
"ao-mcp-server": {
"command": "node",
"args": [
"<bridge-path>",
"http://127.0.0.1:8080/mcp",
"--header", "X-Platform-Api-Key: <YOUR_API_KEY>",
"--oauth",
"--timeout", "600"
]
}
}
}Sends the API key by default. If the platform rejects it (revoked, expired entitlement, etc.) falls back to OAuth and opens the browser.
Mode 5 — npx (zero-install end-user setup)
End users don't need to clone the repo. npx fetches the package from the npm public registry
on first run and caches it.
{
"mcpServers": {
"ao-mcp-server": {
"command": "npx",
"args": [
"-y", "ao-mcp-bridge@latest",
"http://127.0.0.1:8080/mcp",
"--oauth",
"--timeout", "600"
]
}
}
}@latest resolves to the newest published bridge on each npx run (subject to npm cache). For a fixed install, use [email protected] instead. The npx cache location varies by OS:
| OS | npx cache |
| --- | --- |
| Windows | %LOCALAPPDATA%\npm-cache\_npx\ |
| macOS / Linux | ~/.npm/_npx/ |
If Claude Desktop can't find npx on PATH, expand command to the full path:
| OS | Typical full path |
| --- | --- |
| Windows | C:\\Program Files\\nodejs\\npx.cmd |
| macOS (Homebrew) | /opt/homebrew/bin/npx (Apple Silicon) or /usr/local/bin/npx (Intel) |
| Linux | /usr/bin/npx or /usr/local/bin/npx |
All --header, --oauth, etc. flags work identically — flags pass through npx to the bridge.
Mode 6 — Standalone .exe (no Node on target machine)
After building the executable, distribute the binary and point the config directly at it:
{
"mcpServers": {
"ao-mcp-server": {
"command": "<exe-path>",
"args": [
"http://127.0.0.1:8080/mcp",
"--oauth",
"--timeout", "600"
]
}
}
}The binary embeds the Node runtime — no install needed on the target machine. ~80 MB on disk.
Updating to a new version
When you change the bridge spec in your config (e.g. switch from a pinned version to @latest, or bump 1.0.2 → 1.0.3), npx
may serve the previously-cached package and Claude Desktop may keep a stale child process
running. To force a clean re-fetch:
| What you're clearing | Why | When you need it |
| --- | --- | --- |
| Claude Desktop process | Avoid an old child bridge process surviving a bridge version or spec change | After every bridge upgrade (Mode 5 + Mode 6) |
| npx package cache | Forces re-download of the requested bridge tag from the npm registry | After changing @latest / pinned version in Mode 5 when cache serves stale code |
| Bridge state dir (~/.ao-mcp-bridge/) | Wipes DCR client + tokens — next launch re-runs OAuth from scratch | Only when troubleshooting auth (401, expired refresh token, auth-server changed). Skip otherwise — the bridge handles re-auth automatically |
| Legacy ~/.mcp-auth/ | Cleanup if migrating from mcp-remote | Once during initial migration; safe to skip thereafter |
Windows (PowerShell)
# Stop Claude Desktop (including any background/tray processes)
Get-Process "Claude*" -ErrorAction SilentlyContinue | Stop-Process -Force
# Required after a Mode 5 (npx) version bump:
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\npm-cache\_npx\*" -ErrorAction SilentlyContinue
# Optional — only if re-auth is also needed:
Remove-Item -Recurse -Force "$env:USERPROFILE\.ao-mcp-bridge\*" -ErrorAction SilentlyContinue
# Legacy mcp-remote cache — only if migrating from mcp-remote:
Remove-Item -Recurse -Force "$env:USERPROFILE\.mcp-auth\*" -ErrorAction SilentlyContinue
# Re-launch Claude Desktop manuallymacOS
# Stop Claude Desktop
pkill -f "Claude" 2>/dev/null || true
# Required after a Mode 5 (npx) version bump:
rm -rf ~/.npm/_npx/*
# Optional — only if re-auth is also needed:
rm -rf ~/.ao-mcp-bridge/*
# Legacy mcp-remote cache — only if migrating from mcp-remote:
rm -rf ~/.mcp-auth/*
# Re-open from Applications or:
open -a "Claude"Linux
# Stop Claude Desktop
pkill -f "claude-desktop\|Claude" 2>/dev/null || true
# Required after a Mode 5 (npx) version bump:
rm -rf ~/.npm/_npx/*
# Optional — only if re-auth is also needed:
rm -rf ~/.ao-mcp-bridge/*
# Legacy mcp-remote cache — only if migrating from mcp-remote:
rm -rf ~/.mcp-auth/*
# Re-launch Claude DesktopQuick verification
After cleanup, force a fresh fetch and confirm the new version resolves:
# All platforms
npx -y ao-mcp-bridge@<new-version> --helpThe first line of stderr should print ao-mcp-bridge <new-version> starting → ... confirming
the new tarball is in use.
CLI flag reference
| Flag | Purpose |
| --- | --- |
| --header "Name: Value" (repeatable) | Adds header to every outgoing HTTP request. OAuth Bearer always overrides any --header "Authorization: ...". |
| --oauth | Enables OAuth/PKCE flow on 401. Default: off. Without it, 401 surfaces as a JSON-RPC error. |
| --timeout <seconds> | Per-request POST timeout. Default: 60. Pass --timeout 600 for long-running Semantic SQL / NLQ. SSE streams are exempt. |
| --allow-http | Allow non-loopback HTTP URLs. Default: only 127.0.0.1 / localhost HTTP allowed; HTTPS always allowed. Mutually exclusive with --require-https. |
| --require-https | Fail-closed: refuse any http:// URL (even loopback). For production deployments where a misconfigured URL must not leak credentials in plaintext. |
| --ca-file <path> | PEM bundle for custom / private-CA truststore. Used for HTTPS upstreams whose cert isn't anchored in the system trust store. |
| --client-cert <path> --client-key <path> | mTLS client certificate + private key (PEM). Must be supplied together. |
| --max-body-bytes <n> | Cap on buffered POST response body size. Default: 52428800 (50 MB). Exceeded responses surface as a clear error instead of OOMing the bridge. SSE streams are exempt (not buffered). |
| --log-format text\|json | text (default — human-readable lines, identical to 1.0.2). json emits one structured event per stderr line for fleet log aggregation. |
| --transport <flavor> | http-only (default) / http-first (alias) / sse-first (warns + falls through) / sse-only (errors — legacy SSE not implemented). |
| --static-oauth-client-info '{...}' | Skip DCR; use a pre-registered client_id ({"client_id": "...", "client_secret": "..."}). |
| --static-oauth-client-metadata '{...}' | Override DCR registration body fields. redirect_uris is always re-applied to the bound port. |
| --auth-only | Run discovery + OAuth + token exchange, then exit. Implies --oauth. Useful for debugging the auth pipeline. |
| --debug | Verbose request/response logging on stderr. Headers AND body fields matching token / secret / password / credential / authorization / api_key / code / *_token / client_secret are redacted. |
| --clean | Delete the bridge state directory and exit 0. |
Persisted state
All under the bridge state directory:
| File | Contents | When to delete |
| --- | --- | --- |
| client.json | DCR registration: client_id, client_secret, auth_server, redirect_uri | Auth server regenerated its client DB |
| tokens.json | access_token, refresh_token, expires_at | Tokens stale/bad (the bridge clears these on 401 automatically) |
Both are rewritten as needed. Manual cleanup: run the bridge with --clean.
Logs & debugging
Logs go to stderr → captured by Claude Desktop into the
Claude Desktop log file. Add --debug for full request/response
visibility (headers AND body fields redacted — see CLI flag reference).
Text mode (default)
Key lines to look for:
ao-mcp-bridge 1.0.3 starting → http://127.0.0.1:8080/mcp
OAuth: enabled (--oauth) | OAuth: disabled (default; pass --oauth to enable)
Custom headers: X-Platform-Api-Key
Fetching resource metadata: ... (RFC 9728 discovery, OAuth path)
DCR: registering client at ... for redirect_uri = ... (or "Using --static-oauth-client-info")
Callback listener bound on http://127.0.0.1:<port> (fresh port per flow)
Access token obtained (expires_in = ... sec)
Opening notifications SSE stream (attempt 1)
MCP returned 401 (attempt 1) ... (re-auth trigger)
Retrying after 500 ms (5xx attempt 2 of 3) (transient gateway retry)
Cancelled in-flight request id = 7 (notifications/cancelled propagation)
Sent DELETE /mcp for session <id> (clean shutdown)JSON mode (--log-format json)
Each line is one structured event suitable for fleet log aggregation. Stable shape:
{ "ts": ISO-8601, "level": "info", "event": "...", ...fields }. Example trace
of a single tool call returning 401:
{"ts":"2026-05-01T21:30:14Z","level":"info","event":"startup","version":"1.0.3","mcp_url":"http://127.0.0.1:8080/mcp","oauth":false,"transport":"http-only","timeout_ms":600000,"max_body_bytes":52428800,"log_format":"json","custom_ca":false,"mtls":false,"require_https":false}
{"ts":"2026-05-01T21:30:15Z","level":"info","event":"request","mcp_method":"tools/call","mcp_id":4,"body_bytes":83}
{"ts":"2026-05-01T21:30:15Z","level":"info","event":"auth_401","mcp_method":"tools/call","mcp_id":4,"attempt":1,"oauth_enabled":false}
{"ts":"2026-05-01T21:30:15Z","level":"info","event":"response","mcp_method":"tools/call","mcp_id":4,"status":401,"latency_ms":108,"body_bytes":89}Other event names: response_5xx, response_non_json, retry_5xx,
cancel_in_flight, sse_open, sse_error, sse_giveup, sse_ended_cleanly,
teardown_abort. All carry mcp_method and mcp_id where relevant.
Standalone debugging — drive OAuth without launching Claude Desktop:
# macOS / Linux
node mcp-bridge.mjs http://127.0.0.1:8080/mcp --oauth --auth-only --debug# Windows
node mcp-bridge.mjs http://127.0.0.1:8080/mcp --oauth --auth-only --debugFor maintainers — Publishing to npm
The package is configured in scripts/package.json:
| Field | Value |
| --- | --- |
| name | ao-mcp-bridge (unscoped) |
| bin | ao-mcp-bridge → mcp-bridge.mjs |
| files | mcp-bridge.mjs, README.md, LICENSE, CHANGELOG.md (4 files) |
| engines.node | >=20.6.0 |
| scripts.test | node --test mcp-bridge.test.mjs |
| scripts.check-version-sync | Asserts the VERSION constant in mcp-bridge.mjs matches package.json's version. Fails publish if drifted. |
| scripts.prepublishOnly | npm run check-version-sync && node --check ./mcp-bridge.mjs && npm test |
Two version fields, must stay in sync. The bridge advertises its version on stderr at startup (
ao-mcp-bridge 1.0.4 starting → ...), which means the literal string is duplicated inmcp-bridge.mjs(const VERSION = '...') alongsidepackage.json'sversionfield.npm versiononly editspackage.json— you must hand-edit theVERSIONconstant or thecheck-version-syncscript fails the publish.
One-time setup
cd ao-mcp-server/scripts
# Confirm name is available (skip if you already publish this package)
npm view ao-mcp-bridge
# If taken, switch to a scoped name in package.json (requires the org on npm):
# "name": "@your-org/mcp-bridge"
# Authenticate. Two options:
# A. Interactive: npm login (stores token in ~/.npmrc on macOS/Linux,
# or %USERPROFILE%\.npmrc on Windows)
# B. CI / scripted: set NPM_TOKEN env var. The repo already ships a project-local
# .npmrc at scripts/.npmrc containing:
# //registry.npmjs.org/:_authToken=${NPM_TOKEN}
# so `npm publish` picks it up automatically. Keep .npmrc gitignored if you ever
# put a literal token in it (the templated form above is safe to commit).Publish (per-release)
cd ao-mcp-server/scripts
# 1. Bump version in package.json. npm version refuses to run if the working tree
# is dirty — commit or stash unrelated changes first, or pass --allow-same-version
# only on intentional re-tags.
npm version patch --no-git-tag-version # 1.0.3 → 1.0.4 (bug fix / recovery)
# or npm version minor --no-git-tag-version # 1.0.3 → 1.1.0 (additive feature)
# or npm version major --no-git-tag-version # 1.0.3 → 2.0.0 (breaking change)
# 2. Hand-edit the matching VERSION constant in mcp-bridge.mjs (line ~66):
# const VERSION = 'X.Y.Z';
# Skipping this step makes prepublishOnly fail with "Version mismatch".
# 3. Update CHANGELOG.md — add a new section above the previous one, following
# the Keep a Changelog format already used in the file (Fixed / Added / Changed).
# 4. Smoke-test locally. These three commands run automatically inside prepublishOnly,
# but running them by hand catches issues without leaving the registry in a half
# state on failure.
npm run check-version-sync # VERSION constant matches package.json
node --check ./mcp-bridge.mjs # parse syntax
npm test # node --test mcp-bridge.test.mjs
# 5. Inspect the tarball file list — should be exactly 4 files.
npm pack --dry-run
# 6. Publish. publishConfig.access:public in package.json handles the unscoped public
# registry case. With NPM_TOKEN set, no interactive 2FA prompt for an Automation token.
npm publish
# Scoped packages (@scope/name) are private by default — force public:
# npm publish --access public
# 7. Commit + tag. Use the ao-mcp-bridge- prefix on the tag so bridge releases are
# distinguishable from any future ao-mcp-server release tags in the same monorepo.
cd ../..
git add ao-mcp-server/scripts/package.json ao-mcp-server/scripts/mcp-bridge.mjs ao-mcp-server/scripts/CHANGELOG.md
git commit -m "ao-mcp-bridge X.Y.Z: <one-line summary>"
git tag ao-mcp-bridge-vX.Y.Z
git push origin <branch> --follow-tagsRequired token type: Automation (or Granular with publish + write permission for the
package). Read-only tokens fail with 403 You may not perform that action with these credentials.
Post-publish verification
# The new version is visible on the registry within a few seconds.
npm view [email protected] version dist.tarball
# End-to-end: fresh fetch via npx prints the new version on the first stderr line.
npx -y [email protected] --helpVersioning hygiene
- Version spec: Docs use
ao-mcp-bridge@latestfor convenience. For strict reproducibility in production, pin[email protected]and bump deliberately;@latestcan change when the package is published. unpublishwindow: 72 hours from publish. Note that the same version cannot be republished for 24 hours after unpublishing — bump to the next patch and republish instead. After 72 hours, unpublish is no longer permitted; deprecate instead:npm deprecate ao-mcp-bridge@<bad-version> 'use <good-version>'.- CI: a one-line GitHub Action on tag push (
npm publish) keeps releases consistent. Out of scope here.
Common pitfalls
| Symptom | Cause | Fix |
| --- | --- | --- |
| prepublishOnly fails with Version mismatch: package.json=X.Y.Z, mcp-bridge.mjs=A.B.C | Forgot Step 2 — npm version updates package.json only | Edit the VERSION constant in mcp-bridge.mjs to match, then re-run npm publish. |
| npm publish exits with 403 You do not have permission to publish "ao-mcp-bridge" | Token lacks publish scope, or you're not a maintainer of that package name | Use an Automation/Granular token with publish permission, or coordinate with the npm org owner. |
| npm publish exits with 403 Cannot publish over previously-published version X.Y.Z | Version not bumped, or you republished after a same-version unpublish within the 24 h embargo | Bump to the next patch (Step 1) and republish. |
| npx -y ao-mcp-bridge@latest keeps serving the previous version after publish | npx cache | Clear ~/.npm/_npx/ (macOS / Linux) or %LOCALAPPDATA%\npm-cache\_npx\ (Windows). See Updating to a new version. |
For maintainers — Building the standalone executable
For machines that can't install Node, build a self-contained binary using Node's official Single Executable Application (SEA) feature:
# From the ao-mcp-server directory
node scripts/build-exe.mjsOutput:
| Build host | Output path |
| --- | --- |
| Windows | dist\mcp-bridge.exe |
| macOS | dist/mcp-bridge |
| Linux | dist/mcp-bridge |
~80 MB — embeds the build host's Node runtime.
Pipeline
scripts/build-exe.mjs runs four steps:
esbuild— bundles ESM (mcp-bridge.mjs) → CommonJS. Required because Node SEA only supports ESM directly on Node 22.5+; CJS keeps the build portable down to Node 20.- Node SEA — generates a binary blob from the CJS bundle.
- Copy the host's
nodebinary todist/mcp-bridge[.exe]as the runtime base. postjectinjects the SEA blob into the copied executable, replacing the sentinel fuse the Node runtime looks for at startup.
esbuild and postject are auto-fetched via npx --yes — no npm install needed.
Cross-compilation
Not supported by Node SEA. Build on the platform you target:
| Build host | Output | Post-build |
| --- | --- | --- |
| Windows | mcp-bridge.exe | Optional: signtool sign /fd SHA256 dist\mcp-bridge.exe to avoid SmartScreen warnings. |
| macOS | mcp-bridge | Required: codesign --sign - dist/mcp-bridge (ad-hoc) or with a Developer ID. |
| Linux | mcp-bridge | None. chmod +x already applied by build script. |
Smoke test
# macOS / Linux
dist/mcp-bridge --help
dist/mcp-bridge http://127.0.0.1:8080/mcp --oauth --auth-only --debug# Windows
dist\mcp-bridge.exe --help
dist\mcp-bridge.exe http://127.0.0.1:8080/mcp --oauth --auth-only --debugCaveats
- Output size: ~80 MB (embedded Node runtime).
postjectwarning "The signature seems corrupted!" — benign on Windows; refers to the Authenticode signature on the Node binary, invalidated by blob injection. Re-sign withsigntoolif shipping to end users.- Node version match: the embedded runtime is whichever Node you ran the build with. Build with the version you've validated.
Known limitations
- No legacy SSE transport (separate
/sse+/messagesendpoints).ao-mcp-serveruses STREAMABLE HTTP. If ever needed, see the comment near--transportvalidation inmcp-bridge.mjs. - No env-var / config-file based settings. CLI flags only, by design.
- No lockfile-based port coordination. Explicitly avoided — that's the bug in
[email protected]that motivated this bridge. - OAuth user-login timeout: 5 minutes. After that the bridge emits a JSON-RPC error and the next request retries.
- Single in-flight OAuth: concurrent stdio messages during an OAuth flow queue behind the same promise — one browser, one callback server, done.
