refactor(github-mcp)!: vendor the protocol handler from bash-mcp-sdk v1.0.0 - #4
Merged
Martin Bens (SpiGAndromeda) merged 3 commits intoSep 2, 2026
Merged
Conversation
shared/mcpserver_core.sh was maintained here after the split from ai-coding-tools and drifted three commits behind it. It is now vendored byte-identical from shopwareLabs/bash-mcp-sdk lib/mcpserver_core.sh at v1.0.0, with .mcp-sdk.lock recording the release and renovate.json watching it for new ones. Protocol changes go to the SDK repository and arrive here as a lock bump; a local edit is overwritten by the next update. The upgrade carries the validator work this repo never picked up. Argument validation now enforces enum, a declared type, a pattern on string values, and items.type / items.enum per array element, on top of required and additionalProperties. It also closes a hole where a trailing || true masked a jq failure: an arguments value that was present but not an object, such as null or false, made the pipeline error and every check was skipped while the call dispatched. project_view.number and the issue_number / sub_issue_number pairs on sub_issue_add and sub_issue_remove were the only numeric identifiers still declared integer-only, so the stricter type check would have refused the string form clients send. They now declare ["integer", "string"], matching every other identifier. The tool functions already read both forms through jq -r and check the result with _gh_validate_number; only the schemas were narrower. The SDK tests its own surface, so the suites covering validate_tool_arguments, handle_tools_call, log and _configure_extra_log_file are removed. plugin-tests/github-mcp/tool_schemas.bats replaces them with what only this repository can check: that the shipped tool schemas describe the calls clients make, verified against the vendored validator itself. BREAKING CHANGE: an argument whose type does not match its schema now returns an isError result instead of being passed through to gh. This affects the integer-typed paging and output parameters — limit, max_lines, tail_lines, grep_context_before, grep_context_after, line_start, line_end — where a quoted number such as "20" is refused. Send them as JSON numbers. Identifier parameters accept both forms and are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.github/scripts/vendor-mcp-sdk.sh reads the pinned release from .mcp-sdk.lock and writes shopwareLabs/bash-mcp-sdk lib/mcpserver_core.sh to every path in this repository that carries a copy. With --check it compares instead of writing and exits non-zero on drift, which is what the CI job runs. Nothing enforced the lock before this: it recorded which release the file was supposed to come from, and a local edit to the vendored copy passed every gate. The lock and the vendored file have to move together, so a Renovate bump on its own fails the check until the refreshed file lands in the same PR. That is the intended coupling rather than a rough edge — the alternative is a lock claiming a release the tree does not contain. .mcp-sdk.lock joins the CI path filters, replacing templates/**, which matched nothing since the split from ai-coding-tools. Without it a lock-only change triggers no workflow run at all, and the gate it exists to trip never executes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jq's // treats a JSON false as absent, so `.paginate // true` and `.failed_only // true` yielded true whenever a caller explicitly disabled them. Sending the string "false" worked around that until argument validation began enforcing the declared boolean type, which left no value a client could send to turn either option off. Both reads now use the has() idiom already established for `release_list.latest`. The vendoring script authenticated its download by shebang alone, so a payload swapped at the mutable v1.0.0 tag would have been vendored as trusted code and then certified by `--check`. `.mcp-sdk.lock` gains a sha256 line, `download_sdk` compares against it on both paths, and a Renovate version bump now fails the check until someone re-vendors. curl also gains connect and overall timeouts, since `--retry` only covers a transfer that completed and failed, not one that stalled. The changelog claimed enum validation was already applied before this change, that `project_view` checks its number through `_gh_validate_number`, and that union-typed identifiers are validated. The vendored SDK brings enum validation with it, `project_view` only checks that the number is non-empty, and v1.0.0 skips array-valued `type` entirely. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Martin Bens (SpiGAndromeda)
deleted the
chore/vendor-bash-mcp-sdk-v1.0.0
branch
September 2, 2026 23:16
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.
What
plugins/github-mcp/shared/mcpserver_core.shis no longer maintained in this repository. It is vendored from shopwareLabs/bash-mcp-sdk atv1.0.0, byte-identical tolib/mcpserver_core.shat that tag..mcp-sdk.lockpins the release and itssha256,renovate.jsonwatches it for new ones, and a CI step fails the build when the vendored copy and the lock disagree.Why now
The file was copied here when the plugin split out of
ai-coding-tools, and upstream moved three commits since:b2ff1d8enumenforcement on any present propertyd698712argumentsf928a47type,pattern, and arrayitemsenforcementd698712is the one with teeth, and the bug was live in what we ship.validate_tool_argumentsended its jq pipeline in|| true, so anargumentsvalue ofnullorfalsemade$args | keyserror, the failure was swallowed, and the call dispatched withrequiredandadditionalPropertiesunenforced. Those were the only two checks the copy in this repository had;enumarrives with the vendored file.Breaking change
An argument whose type does not match its schema now returns an
isErrorresult instead of reachinggh.This affects the integer-typed paging and output parameters —
limit,max_lines,tail_lines,grep_context_before,grep_context_after,line_start,line_end. A quoted number such as"20"is refused; send a JSON number instead.Identifier parameters are unaffected: they declare
["integer", "string"]and take both forms. Worth knowing when reading the rest of this PR:v1.0.0enforces a declaredtypeonly when it is a single string, so a union like that one is not checked against either member. Upstream has fixed this on its main branch and has not released it, so the union buys acceptance of both forms rather than validation of them.Schema fix
Five numeric identifiers were still declared integer-only and would have started refusing the string form clients send:
project_view.number, plusissue_number/sub_issue_numberonsub_issue_addandsub_issue_remove. They now declare the union like every other identifier.No tool-function change was needed. All five already read the value through
jq -r, which stringifies123and"123"identically.sub_issue_addandsub_issue_removethen check the result with_gh_validate_number;project_viewchecks only that it is non-empty. Only the schemas were narrower than the code.Boolean options that could not be disabled
pr_comments.paginateandrun_logs.failed_onlyread their value withjq -r '.paginate // true'. jq's//treats a JSONfalseas absent, so an explicitfalsecame back astrueand neither option could be turned off. Sending the string"false"worked around it until the stricter validation above began enforcing the declared boolean type, which left no value a client could send.Both now use the
has()idiom already established forrelease_list.latest. The// falsereads elsewhere are unaffected: their default isfalse, so//swallowing afalseyields the right value anyway.Tests
Removed
plugin-tests/mcp-shared/mcp_argument_validation.batsandplugin-tests/github-mcp/extra_log_file.bats. Both covered functions that belong to the SDK (validate_tool_arguments,handle_tools_call,log,_configure_extra_log_file), which tests them in its own suite.Added
plugin-tests/github-mcp/tool_schemas.bats(9 tests) covering the seam only this repository can check:requiredname exists inproperties— otherwise the tool is uncallable underadditionalProperties: falsedefaultsatisfies its own type and enumpr_list {"limit": "20"}is refused, naming the parametermcp_tool_gh.batsgains arun_logscounterpart to itspr_commentspaginate test, and both now send a JSON boolean rather than the string that used to be the only thing that worked.Vendoring gate
.github/scripts/vendor-mcp-sdk.shre-vendors at the pinned release;--checkcompares and exits non-zero on drift, which is what CI runs..mcp-sdk.lockreplacedtemplates/**in the workflow path filters — that glob has matched nothing since the split, and without the lock in the filters a lock-only change triggers no workflow run at all.The download is verified against the
sha256in the lock before it is written anywhere, on both the--checkand the re-vendor path. Without that, the only thing separating a good payload from a swapped one was a#!on the first line, and a git tag is mutable by whoever owns the repository it points into.curlalso carries connect and overall timeouts, since--retrycovers a transfer that completed and failed rather than one that stalled.After Renovate bumps the lock, the refreshed file has to land in the same PR or the check fails — now on the hash as well as the drift comparison. That coupling is deliberate: the alternative is a lock claiming a release the tree does not contain.
Bugs surfaced while exercising the script, fixed here: the
EXITtrap read alocalofmainafter it had returned, so underset -ua clean--checkprintedokand then exited 1; andchmod 755 --fails on macOS, where BSDchmodhas no end-of-options marker and reads--as a filename.