Repository navigation
fix(github-mcp): block api_read for a single issue - #20
Merged
Martin Bens (SpiGAndromeda) merged 5 commits intoOct 3, 2026
Merged
Conversation
With block_api_tool_read enabled, check-api-tools.sh let a GET of repos/OWNER/REPO/issues/N and of search/code through, although issue_view and search_code cover both. Its GET patterns covered only issues/N/issue-field-values and no search endpoint. The issue pattern is anchored at the issue number, so sub-resources without a dedicated tool, such as issues/N/timeline, stay allowed. search/issues, search/repositories, and search/commits stay allowed as well; REFERENCE.md uses search/issues through api_read as an example. The issue_view hint names pr_view for a pull request number, since the issues endpoint also answers for pull requests. Co-Authored-By: Claude <noreply@anthropic.com>
search_code and search_commits used GH_DEFAULT_REPO whenever repo was empty, and passed owner to gh only when no default repo was configured. With a default repo set, an explicit owner was dropped, and neither tool could search outside that repository. With api_read blocked for search/code, no other tool could either. The scope now resolves an explicit repo first, then an explicit owner, then the default repo. The owner descriptions in tools-read.json and REFERENCE.md state the precedence. Co-Authored-By: Claude <noreply@anthropic.com>
The search/code block left queries without a working tool: search_code sends its search to gh as one exact phrase, so separate keywords, OR/NOT, and path: could only go through api_read. check-api-tools.sh no longer blocks search/code.
The issues/N rule matched the whole endpoint, so a fragment, which gh drops before sending, got past it, and an issue path inside another endpoint's query string, such as search/issues?q=..., triggered it. It now matches the path without query and fragment, along whole segments, and also covers a trailing slash. The block message names the fields issue_view cannot return: closed_by, author_association, and the lock state. The CHANGELOG entry now also names block_api_tool_write, under which the write server's api tool applies the same rule.
Tests cover a leading slash, a query string, a fragment, a trailing slash, gh's {owner}/{repo} placeholders, an issue path inside a search query, search/code, and the write tool.
Co-Authored-By: Claude <noreply@anthropic.com>
The SessionStart directive says the search tools accept owner with a bare repo name, but search_code and search_commits sent the bare name to gh as the repo and failed it as not owner/repo. _gh_resolve_search_scope now resolves both tools' scope in one place: an owner/repo value in repo first, then owner joined with a bare repo, then owner alone, then GH_DEFAULT_REPO. owner goes through _gh_validate_org, so a value such as "acme,other", which gh's --owner would read as two owners, or a whitespace-only owner fails before gh runs. The owner and repo descriptions in tools-read.json and REFERENCE.md describe the split form, and the README says when the default repo applies to these two tools. The tests now check the exact gh arguments for each order of precedence in both tools. Co-Authored-By: Claude <noreply@anthropic.com>
_gh_resolve_search_scope checked a lone owner with _gh_validate_org, whose pattern allows only letters, digits, and hyphens. That is right for organizations, but a search owner can be a user, and an Enterprise Managed User login such as octocat_acme contains an underscore, so search_code and search_commits rejected a valid owner that gh had accepted before. The owner check now uses the characters _gh_validate_repo allows in an owner, so the lone owner and the owner in the split form follow the same rule, and the error names a user or organization login. An owner passed with an owner/repo value in repo was ignored, so repo acme/app with owner other searched acme/app without saying so. Such a call now fails before gh runs unless the two owners match, compared without regard to case. REFERENCE.md and the CHANGELOG state the rule. Co-Authored-By: Claude <noreply@anthropic.com>
Martin Bens (SpiGAndromeda)
deleted the
fix/api-read-issue-and-code-search
branch
October 3, 2026 14:38
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.
Closes #14.
With
block_api_tool_read: true,check-api-tools.shlet a GET ofrepos/OWNER/REPO/issues/Nthrough, althoughissue_viewcovers it. Its GET patterns covered onlyissues/N/issue-field-values.Changes
repos/[^/]+/[^/]+/issues/[0-9]+now blocks and points toissue_view, and topr_viewfor a pull request number, since the issues endpoint also answers for pull requests. The same rule applies to the write server'sapitool underblock_api_tool_write: true.search/issues?q=…, does not trigger it. Sub-resources without a dedicated tool, such asissues/N/timeline, stay allowed.ownerand the default repo insearch_codeandsearch_commits: both used the configured default repo wheneverrepowas empty, and passedownerto gh only when no default repo was set, so an explicitownerwas dropped. They also failed theowner+ barereposplit form that the SessionStart directive promises for the search tools._gh_resolve_search_scopenow resolves both: anowner/repovalue inrepo, thenownerjoined with a barerepo, thenowneralone, then the default repo.ownerchecks:ownertakes the characters_gh_validate_repoallows in an owner, so a user login with an underscore (Enterprise Managed Users, such asoctocat_acme) works, while a value such asacme,other, which gh's--ownerwould read as two owners, fails before gh runs. Anownerpassed with anowner/repovalue must name the same owner, compared without regard to case; before, it was ignored without a message.ownerandrepodescriptions intools-read.jsonandREFERENCE.mdstate the order and the matching rule, and the README says when the default repo applies to these two tools.Not blocked
search/code:search_codesends its search to gh as one exact phrase, so separate keywords,OR/NOT, andpath:have no other route. Blocking it would leave those queries without a working tool.Known limits
issue_viewdoes not returnclosed_by,author_association, or the lock state. With the block on, those fields are not available, and the block message says so.