fix: enforce the declared bash and jq floors when the file is sourced - #22
Merged
Martin Bens (SpiGAndromeda) merged 1 commit intoSep 13, 2026
Merged
Conversation
The header line and README §Requirements have declared bash 4.1+ and jq 1.7+ since 3.0.0, and nothing read either one. Both failed in ways an operator could not diagnose from what reached them. Below bash 4.1 the file still parses, so the first sign of trouble was `run_mcp_server` dying on its `exec {_MCP_LIFELINE_FD}<>` redirection with `{_MCP_LIFELINE_FD}: not found` and status 127, which an MCP host reports as nothing more than a server that failed to start — and macOS ships 3.2.57 as /bin/bash, which both usual launch paths resolve through PATH. Below jq 1.7 nothing failed at all: the server answered every request while `validate_tool_arguments` returned the pre-3.0.0 verdict for a declared `integer`, because reading a number back through `tojson` to catch a fraction the double rounded away depends on the literal preservation jq added in 1.7.
Sourcing now checks bash, then jq's presence, then jq's version, and refuses with a message naming the requirement, the version found where there is one, and remediation for the platform. The block sits above the file's own `set -euo pipefail` so a refusal leaves the calling shell's options as it set them, and uses no construct newer than bash 3.2 — a guard that cannot run on the version it rejects never gets to speak. Diagnostics go to stderr: stdout carries the JSON-RPC stream, and no protocol error is constructible there, since every envelope is built with `jq -n`.
A jq that is present but cannot run is named as such, with the path it resolved to, rather than reported as outdated; `command -v jq` succeeds for a binary of the wrong architecture or with a missing library, and the advice to upgrade an already-current package sent the operator nowhere.
The floors are read at source time, so PATH has to select the intended jq and bash at that point. A server script that repaired PATH after sourcing moves that line above the `source`; an operator who cannot edit the script sets PATH in the host manifest's launch command, which also selects the bash the server runs under. README §Requirements carries both forms.
Co-Authored-By: Claude <noreply@anthropic.com>
Martin Bens (SpiGAndromeda)
deleted the
fix/preflight-dependency-guards
branch
September 13, 2026 17:48
Martin Bens (SpiGAndromeda)
added a commit
that referenced
this pull request
Sep 13, 2026
Classifies the dependency guards from #22 as major. A consumer running below either declared floor could source the file before; the guards now refuse to source it. A server script that repairs PATH after sourcing the file no longer starts. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lib/mcpserver_core.shhas declared bash 4.1+ and jq 1.7+ in its header line and inREADME.md§Requirements since 3.0.0. Nothing read either one.What that cost
bash below 4.1 — the file still parses, so there was no early error.
run_mcp_serverdied on itsexec {_MCP_LIFELINE_FD}<>redirection with{_MCP_LIFELINE_FD}: not foundand status 127, which an MCP host reports as nothing more than a server that failed to start. macOS ships 3.2.57 as/bin/bash, and both usual launch paths — a#!/usr/bin/env bashshebang, and a host manifest whose command isbash— resolve throughPATH.jq below 1.7 — nothing failed at all. The server answered every request while
validate_tool_argumentsreturned the pre-3.0.0 verdict for a declaredinteger: reading a number back throughtojsonto catch a fraction the double rounded away depends on the literal preservation jq added in 1.7.What the guard does
Sourcing checks bash, then jq's presence, then jq's version, and refuses with the requirement, the version found where there is one, and remediation for the platform — the package manager's command where one can be determined, a generic line otherwise.
The block sits above the file's own
set -euo pipefail, so a refusal leaves the calling shell's options as it set them. It uses no construct newer than bash 3.2: a guard that cannot run on the version it rejects never gets the chance to speak. Diagnostics go to stderr — stdout carries the JSON-RPC stream, and no protocol error is constructible at that point anyway, since every envelope is built withjq -n.A jq that is present but cannot run — wrong architecture, missing shared library — is named as such, with the path it resolved to, rather than reported as an outdated one.
For consumers
The floors are read at source time, so
PATHhas to select the intended jq and bash at that point. A server script that repairedPATHafter sourcing moves that line above thesource. An operator who cannot edit the script setsPATHin the host manifest's launch command, which also selects the bash the server runs under:Closes #21