Skip to content

feat: publish standalone GitHub release bundle - #5

Open
emrberk wants to merge 5 commits into
masterfrom
feat/standalone-github-release
Open

feat: publish standalone GitHub release bundle#5
emrberk wants to merge 5 commits into
masterfrom
feat/standalone-github-release

Conversation

@emrberk

@emrberk emrberk commented Aug 26, 2026

Copy link
Copy Markdown
Member

Summary

  • build a self-contained .mjs bridge and third-party notices for GitHub Releases
  • make setup, upgrade, CLI help, and version-mismatch guidance distribution-aware
  • make the release workflow idempotently publish GitHub assets, skip existing npm packages, and publish the missing MCP Registry version
  • run CI on Node 22 and 24 and add isolated standalone/runtime coverage

The standalone bundle and the npm build use the same MCP server implementation. The currently published npm 0.4.0 packages remain unchanged; this workflow will detect them and complete the npm stage as a no-op.

Validation

  • yarn install --immutable
  • yarn typecheck
  • yarn lint
  • yarn build
  • yarn test — 309 tests passed
  • yarn bundle
  • workflow YAML parsing and actionlint
  • isolated Node 22 bundle smoke test
  • staged npm build, standalone bundle, and published npx package all expose the same 35 tools with identical names and input schemas
  • current server.json passes the live MCP Registry publisher validation

Expected first run

  • GitHub: create v0.4.0, build and publish the standalone bundle plus notices
  • npm: both 0.4.0 package names already exist, so both publishes are skipped successfully
  • MCP Registry: currently at 0.3.0, so publish 0.4.0 via GitHub OIDC

Known non-blocking limitation

On a minimal Linux desktop without a system xdg-open, automatic browser opening from the single-file bundle can fail; the pairing response still provides the deep link and credentials, so manual pairing continues to work.

@emrberk

emrberk commented Aug 26, 2026

Copy link
Copy Markdown
Member Author

PR #5 Review: feat: publish standalone GitHub release bundle

Reviewing PR #5 at level 3 — the full mission-critical pass with all seven review agents and per-finding verification.

  • Target: PR feat: publish standalone GitHub release bundle #5 (feat/standalone-github-releasemaster)
  • State: Open
  • Title check: Pass. The title follows Conventional Commits (feat: description) and accurately names the user-visible release capability.
  • Description check: Pass. The description identifies the standalone distribution behavior, release-workflow impact, validation performed, required repository setup, expected first-run behavior, and the known non-blocking browser-opening limitation.

Issues

Issue ID Issue name Category Severity Location Description Steps to reproduce Suggested fix
#1 Historical bundle URLs fail Trust boundaries & security Moderate in-diff — src/pairingTools.ts:157, src/distribution.ts:112 PR #5 introduces the first standalone asset at 0.4.0, but every canonical version is treated as having one. Version-mismatch responses can mandate downloading nonexistent 0.2/0.3 assets. Tests explicitly preserve this incorrect assumption. Run standalone 0.4.0 and pair with a console expecting 0.2.0. Pairing succeeds because both use major 0, but the returned upgrade URL targets v0.2.0/mcp-server-questdb-0.2.0.mjs, which does not exist. Model standalone availability from 0.4.0 onward. For earlier versions, use the appropriate npm package or return honest unsupported/offline guidance. Correct the tests.
#2 Windows Codex path breaks Tests & coverage Moderate out-of-diff — src/setup/codexCli.ts:13; arbitrary standalone-path contract introduced by src/distribution.ts:28 Standalone paths now reach the existing Windows cmd.exe quoting layer. winQuote does not quote ( or ), so legal no-space paths containing parentheses are parsed as shell syntax and Codex setup or upgrade fails. Run the bundle from C:\QuestDB(test)\mcp-server-questdb-0.4.0.mjs, then configure Codex. The path remains unquoted in codex mcp add ... -- node <path>. Avoid shell:true and custom quoting if possible. Otherwise quote every cmd.exe metacharacter, including parentheses, and add an injected Windows-path test.
#3 Reconnect cancels foreign grace Disconnect, reconnect & races Moderate out-of-diff — src/bridgeSession.ts:599; per-call reconnect-grace ownership contract A reconnect cancels grace for every in-flight call, including calls belonging to an earlier console. A lost long-running call then waits up to five minutes instead of returning the 30-second unverified-disconnect warning. Console A starts run_query and disconnects. Fresh console B pairs, disconnects, then reconnects with B’s ID. The reconnect loop cancels A’s grace timer, leaving A’s call pending until its 300-second deadline. Store the owning session ID on each in-flight call and cancel grace only for calls owned by the reconnecting session.
#4 Failed reconnect erases identity Disconnect, reconnect & races Moderate out-of-diff — src/bridgeSession.ts:743; detached-session preservation contract An S0 handshake failure overwrites detachedSessionId with null. A subsequent valid reconnect is no longer recognized, so its call falsely returns browser_disconnected even though the original console returned. Console A starts a long query and disconnects. Its first reconnect drops before hello, overwriting the detached ID with null. Its second reconnect succeeds, but grace is not canceled; at 30 seconds the call fails and a later legitimate result is dropped. Do not overwrite the detached identity on S0→S0 failures. Prefer per-call session/grace ownership and add a fake-timer regression test.
#5 Hidden tools bypass surface MCP server contract & tool surface Moderate out-of-diff — src/mcpServer.ts:162; static/live tool allowlist contract The SDK accepts any string as a tool name, and the bridge forwards every non-pairing name without checking BUNDLED_TOOL_NAMES. Calling AI-only suggest_query bypasses the MCP surface and live schema validation; the Console returns success without displaying a suggestion. Pair normally, then send a raw tools/call for suggest_query. It is absent from tools/list, but reaches the browser’s generic dispatcher and returns a false success. Enforce BUNDLED_TOOL_NAMES before forwarding and return UNKNOWN_TOOL with isError:true. Optionally require membership in the live advertised set too.
#6 Lifecycle comment is false Session state machine & protocol Minor in-diff — src/bridgeSession.ts:749 The new comment says incompatibleConsole is cleared only by a successful hello, but beginPairingAttempt and fresh attachBrowser also clear it. This documents a false lifecycle invariant in the highest-risk state machine. Cache a major mismatch, let its socket close, then call get_pairing_credentials or attach a fresh authenticated socket. The cache clears without a successful hello. Update the comment to enumerate successful hello, explicit retry, and fresh attach.

False-positives

Category Description Explanation
Timers, in-flight calls & cancellation A throwing send(cancel) leaves deadline or abort promises unsettled. Production’s adapter skips non-open sockets, uses a fixed JSON-safe frame, and ws.send cannot synchronously throw on the reachable open-state path. The sequence requires an artificial throwing adapter.
Session state machine & protocol A malformed expectedBridgeVersion strands waiters and causes reconnect loops. Supported Console builds send a hard-coded canonical version. Protocol violations intentionally leave waiters available for a later valid connection; only actionable major mismatches terminate and drain them.
Timers, in-flight calls & cancellation A fresh authenticated socket incorrectly erases a cached version diagnosis. This is intentional new-attempt behavior. Retaining the predecessor’s mismatch would immediately reject waiters while a potentially compatible replacement is handshaking.

Summary

Approve.

There are no Critical findings. The in-diff findings are one Moderate standalone-recovery defect and one Minor lifecycle-comment defect, so they do not block this PR under the requested approval policy. The disconnect-grace/no-duplicate-DML review also identified two pre-existing out-of-diff ownership problems.

Eight remaining unique draft findings were verified: five confirmed and three dropped as false positives. Verification additionally found one Minor in-diff comment defect. The final split is two in-diff issues and four out-of-diff issues.

Typecheck, build, lint, and all 313 tests pass.

@sklarsa

sklarsa commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Review findings

Important — standalone mismatch guidance links to nonexistent historical bundles

Locations: src/pairingTools.ts:157-180, src/distribution.ts:74-82,112-121

Standalone release assets begin with v0.4.0, but the implementation treats every canonical version—including 0.2.x and 0.3.x—as downloadable. A v0.4 standalone bridge paired with a console expecting v0.2/v0.3 directs the user to a GitHub asset that returns 404.

Model standalone availability from v0.4.0 onward (or use an explicit manifest), and provide npm/console-upgrade guidance when no historical standalone asset exists.

Important — MCP Registry recovery is not target-version idempotent

Location: .github/workflows/mcp-registry.yml:54-64

The existence check compares the target only with .servers[0] (effectively the latest result). Once v0.5.0 exists, a recovery run for already-published v0.4.0 sees v0.5.0 and attempts to republish v0.4.0.

Use the version-specific endpoint /v0.1/servers/io.github.questdb%2Fmcp-server-questdb/versions/$VERSION, and use curl --fail --show-error --silent --retry 3 so lookup failures fail closed.

Important — legal Windows standalone paths can break Codex configuration

Locations: src/setup/codexCli.ts:17-18, src/distribution.ts:64-68

Standalone bundle paths now pass through the Windows cmd.exe quoting layer. winQuote() does not quote parentheses, so a path such as C:\QuestDB(test)\mcp-server-questdb-0.4.0.mjs remains unquoted even though parentheses are shell metacharacters. Expand quoting coverage or avoid shell:true, and add a regression test.

Minor — bundle E2E environment is not fully isolated

Location: src/test/bundle.e2e.test.ts:34-41

PATH: dirname(process.execPath) can expose unrelated executables installed beside Node. On my host this exposed codex, causing both upgrade E2E tests to fail against the synthetic CODEX_HOME. Use a private empty/bin directory rather than Node's installation directory.

Validation

  • PR CI passes on Node 22 and 24.
  • Local typecheck, lint, and build passed.
  • Local tests: 311 passed, 2 failed due to the PATH isolation issue above.
  • The standalone bundle built and ran manually.
  • Current GitHub Actions documentation confirms queue: max is valid.
  • actions/create-github-app-token@v3 supports and prefers client-id; actionlint warnings claiming otherwise are stale false positives.

Assessment: Not ready to merge until the Important findings are addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants