diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 2a23445a..739b3185 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -62,8 +62,8 @@ workflows: - 'actions/checkout@v6.0.2' '.github/workflows/publish.yml': - 'actions/checkout@v6.0.2' - - 'actions/setup-node@v4.4.0' - 'denoland/setup-deno@v2.0.4' + - 'oven-sh/setup-bun@v2.2.0' '.github/workflows/push-email-notify.yml': - 'hyperpolymath/smtp-notify-action@v0.2.0' '.github/workflows/readme-derive.yml': [] diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ba9c3741..d34e9f76 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -209,18 +209,18 @@ jobs: fail-fast: false matrix: include: - - runtime: node - unit: node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js mcp-bridge/tests/routing_args_test.js - boot: node mcp-bridge/main.js + - runtime: bun + unit: bun test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js mcp-bridge/tests/routing_args_test.js + boot: bun mcp-bridge/main.js - runtime: deno unit: deno test --allow-read --allow-env --allow-run --allow-net mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js mcp-bridge/tests/routing_args_test.js # Scoped perms matching main.js's shebang, NOT -A: the boot smoke # must exercise the exact permission set a real install uses, or it # would mask a missing-grant bug that scoped users would hit. boot: deno run --allow-net --allow-env --allow-read mcp-bridge/main.js - - runtime: bun - unit: bun test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js mcp-bridge/tests/routing_args_test.js - boot: bun mcp-bridge/main.js + - runtime: node + unit: node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js mcp-bridge/tests/routing_args_test.js + boot: node mcp-bridge/main.js steps: - name: Checkout diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dd461eb5..47cb4322 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,8 @@ # This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Publish workflow — publishes to npm, the official MCP Server Registry, and JSR on version tag push (v*). +# Publish workflow — publishes via Bun, the official MCP Server Registry, and Deno/JSR on version tag push (v*). +# Runtime hierarchy: Bun first, followed by Deno/pnpm, and finally npm/yarn. # Requires NPM_TOKEN and JSR_TOKEN repository secrets; MCP Registry uses GitHub OIDC (zero secrets). name: Publish @@ -18,8 +19,8 @@ permissions: contents: read jobs: - publish-npm: - name: Publish to npm & MCP Registry + publish-bun: + name: Publish via Bun & MCP Registry runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -28,22 +29,22 @@ jobs: steps: - uses: actions/checkout@v6.0.2 - - name: Setup Node.js - uses: actions/setup-node@v4.4.0 + - name: Setup Bun + uses: oven-sh/setup-bun@v2.2.0 with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' + bun-version: '1.x' - name: Verify package and server.json versions match tag run: | TAG_VERSION="${GITHUB_REF_NAME#v}" - PKG_NAME=$(node -p "require('./package.json').name") - PKG_VERSION=$(node -p "require('./package.json').version") - MCP_NAME=$(node -p "require('./package.json').mcpName") - SRV_VERSION=$(node -p "require('./server.json').version") - SRV_NAME=$(node -p "require('./server.json').name") - SRV_PKG_ID=$(node -p "require('./server.json').packages?.[0]?.identifier ?? ''") - SRV_PKG_VERSION=$(node -p "require('./server.json').packages?.[0]?.version ?? ''") + PKG_NAME=$(bun -e "console.log(require('./package.json').name)") + PKG_VERSION=$(bun -e "console.log(require('./package.json').version)") + MCP_NAME=$(bun -e "console.log(require('./package.json').mcpName)") + SRV_VERSION=$(bun -e "console.log(require('./server.json').version)") + SRV_NAME=$(bun -e "console.log(require('./server.json').name)") + SRV_PKG_ID=$(bun -e "console.log(require('./server.json').packages?.[0]?.identifier ?? '')") + SRV_PKG_VERSION=$(bun -e "console.log(require('./server.json').packages?.[0]?.version ?? '')") + SRV_RUNTIME_HINT=$(bun -e "console.log(require('./server.json').packages?.[0]?.runtimeHint ?? '')") if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)" exit 1 @@ -64,6 +65,10 @@ jobs: echo "::error::Tag version ($TAG_VERSION) does not match server.json packages[0].version ($SRV_PKG_VERSION)" exit 1 fi + if [ "$SRV_RUNTIME_HINT" != "bunx" ]; then + echo "::error::server.json packages[0].runtimeHint ($SRV_RUNTIME_HINT) must be 'bunx'" + exit 1 + fi - name: Install mcp-publisher run: | @@ -77,8 +82,8 @@ jobs: - name: Validate MCP manifest run: /tmp/mcp-publisher validate server.json - - name: Publish to npm - run: npm publish --provenance --access public + - name: Publish package via Bun + run: NPM_CONFIG_TOKEN="$NODE_AUTH_TOKEN" bun publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.mcp.json b/.mcp.json index be880815..6712865c 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,8 +1,8 @@ { "mcpServers": { "boj-server": { - "command": "npx", - "args": ["-y", "@hyperpolymath/boj-server@latest"], + "command": "bunx", + "args": ["@hyperpolymath/boj-server@latest"], "env": { "BOJ_URL": "http://localhost:7700" } diff --git a/Containerfile b/Containerfile index 4b8ded98..888495cc 100644 --- a/Containerfile +++ b/Containerfile @@ -32,7 +32,7 @@ WORKDIR /app COPY package.json /app/ COPY mcp-bridge/ /app/mcp-bridge/ -# No npm install needed — zero dependencies +# No package install needed — zero dependencies USER node -ENTRYPOINT ["node", "mcp-bridge/main.js"] +ENTRYPOINT ["/usr/local/bin/bun", "mcp-bridge/main.js"] diff --git a/container/Containerfile.mcp-bridge b/container/Containerfile.mcp-bridge index 0368fb1c..63753021 100644 --- a/container/Containerfile.mcp-bridge +++ b/container/Containerfile.mcp-bridge @@ -45,4 +45,4 @@ USER appuser # Health: just check process alive (stdin bridge has no HTTP endpoint) ENV BOJ_URL=http://localhost:7700 -ENTRYPOINT ["deno", "run", "--allow-net", "/app/mcp-bridge/main.js"] +ENTRYPOINT ["bun", "/app/mcp-bridge/main.js"] diff --git a/mcp-bridge/lib/runtime.js b/mcp-bridge/lib/runtime.js index af8ac8bc..31740827 100644 --- a/mcp-bridge/lib/runtime.js +++ b/mcp-bridge/lib/runtime.js @@ -3,12 +3,13 @@ // // BoJ Server — Runtime abstraction layer // -// Provides a unified interface for Deno and Node.js environments. -// Supports the "Deno > Bun > NPM" hierarchy while maintaining +// Provides a unified interface for Bun, Deno, and Node.js environments. +// Supports the "Bun > Deno/pnpm > npm/yarn" hierarchy while maintaining // compatibility with Node-only MCP clients like Glama. import { writeSync as writeFdSync } from "node:fs"; +const isBun = typeof globalThis.Bun !== "undefined"; const isDeno = typeof globalThis.Deno !== "undefined"; /** @type {{ get: (name: string) => string|undefined }} */ @@ -51,4 +52,4 @@ export const stderr = { } }; -export { isDeno }; +export { isBun, isDeno }; diff --git a/mcp-bridge/main.js b/mcp-bridge/main.js index 7e8c260c..31db0ff9 100755 --- a/mcp-bridge/main.js +++ b/mcp-bridge/main.js @@ -12,8 +12,10 @@ // BOJ_TRANSPORT=http listen on BOJ_HTTP_PORT (default 7780) // BOJ_TRANSPORT=both run both simultaneously // -// Usage: +// Usage (preferred order: Bun > Deno/pnpm > Node/npm): +// bun mcp-bridge/main.js // deno run --allow-net --allow-env --allow-read mcp-bridge/main.js +// node mcp-bridge/main.js import { env, stdout } from "./lib/runtime.js"; import { sanitizeErrorMessage } from "./lib/security.js"; diff --git a/mcp-bridge/tests/dispatch_test.js b/mcp-bridge/tests/dispatch_test.js index 2a5e40b5..c19fe98e 100644 --- a/mcp-bridge/tests/dispatch_test.js +++ b/mcp-bridge/tests/dispatch_test.js @@ -416,6 +416,7 @@ test("server.json and package.json satisfy MCP Registry constraints", () => { assert.ok(npmPkg, "server.json must declare an npm package entry"); assert.equal(npmPkg.identifier, pkg.name); assert.equal(npmPkg.version, pkg.version); + assert.equal(npmPkg.runtimeHint, "bunx", "server.json package entry must prefer bunx as runtimeHint"); assert.equal(npmPkg.transport?.type, "stdio"); }); diff --git a/package.json b/package.json index ae823e26..f7306ad8 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,17 @@ "boj-server": "mcp-bridge/main.js" }, "scripts": { - "start": "deno run -A mcp-bridge/main.js", - "test": "node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js" + "start": "if command -v bun >/dev/null 2>&1; then exec bun mcp-bridge/main.js; elif command -v deno >/dev/null 2>&1; then exec deno run --allow-net --allow-env --allow-read mcp-bridge/main.js; else exec node mcp-bridge/main.js; fi", + "start:bun": "bun mcp-bridge/main.js", + "start:deno": "deno run --allow-net --allow-env --allow-read mcp-bridge/main.js", + "start:node": "node mcp-bridge/main.js", + "test": "if command -v bun >/dev/null 2>&1; then bun test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js; elif command -v deno >/dev/null 2>&1; then deno test --allow-read --allow-env --allow-run --allow-net mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js; else node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js; fi", + "test:bun": "bun test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js", + "test:deno": "deno test --allow-read --allow-env --allow-run --allow-net mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js", + "test:node": "node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/routing_args_test.js mcp-bridge/tests/declared_args_test.js" }, "engines": { + "bun": ">=1.0.0", "node": ">=18.0.0" } } diff --git a/server.json b/server.json index 105daa5e..f5b30aca 100644 --- a/server.json +++ b/server.json @@ -15,7 +15,7 @@ "registryBaseUrl": "https://registry.npmjs.org", "identifier": "@hyperpolymath/boj-server", "version": "0.5.0", - "runtimeHint": "npx", + "runtimeHint": "bunx", "transport": { "type": "stdio" }, @@ -99,5 +99,19 @@ } ] } - ] + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.hyperpolymath": { + "runtimePreference": [ + "bunx", + "deno", + "pnpm", + "npx", + "yarn" + ], + "jsrPackage": "jsr:@hyperpolymath/boj-server" + } + } + } }