From b5d0df6d1d8dc3f54fdd4e9e8dfb5001c01222a9 Mon Sep 17 00:00:00 2001 From: Martin Bens Date: Sat, 3 Oct 2026 14:17:50 +0200 Subject: [PATCH 1/3] fix(github-mcp): return the issue or PR together with its comments Run without a terminal, `gh issue view --comments` and `gh pr view --comments` print only the comment list and skip the preview. issue_view with with_comments and pr_view with comments passed that flag, so they returned the comments without the title, body, or metadata, and empty output for an item without comments, although both schema descriptions promise the comments in addition to the item. A new _gh_view_with_comments helper runs the view once for the preview and once with `--comments`, then appends the comments after a `--- comments ---` line, or `--- comments and reviews ---` for a PR, because gh's PR list includes reviews. The second call always runs because gh's PR preview prints no comment count to decide on. A failure of either call fails the tool call or returns fallback, never the preview on its own. REFERENCE.md describes the separator lines for both tools. Co-Authored-By: Claude --- plugin-tests/github-mcp/mcp_tool_gh.bats | 88 +++++++++++++++++-- plugins/github-mcp/CHANGELOG.md | 7 ++ plugins/github-mcp/REFERENCE.md | 5 +- .../github-mcp/mcp-server-gh/lib/common.sh | 43 +++++++++ plugins/github-mcp/mcp-server-gh/lib/issue.sh | 9 +- plugins/github-mcp/mcp-server-gh/lib/pr.sh | 9 +- 6 files changed, 143 insertions(+), 18 deletions(-) diff --git a/plugin-tests/github-mcp/mcp_tool_gh.bats b/plugin-tests/github-mcp/mcp_tool_gh.bats index 3f899b8..89cbcbe 100644 --- a/plugin-tests/github-mcp/mcp_tool_gh.bats +++ b/plugin-tests/github-mcp/mcp_tool_gh.bats @@ -2004,21 +2004,91 @@ diff --git a/src/Third.php b/src/Third.php } } -@test "issue_view: with_comments adds --comments flag" { +# ============================================================================= +# issue_view with_comments / pr_view comments +# Without a TTY, gh's view --comments prints only the comments, so these tools +# make a plain call for the preview and a --comments call for the comments. +# ============================================================================= + +# Stub gh as a view command: the preview without --comments, and VIEW_COMMENTS +# (exit VIEW_COMMENTS_EXIT) with it, the way gh prints them without a TTY. +_stub_gh_view() { + VIEW_COMMENTS="$1" + VIEW_COMMENTS_EXIT="${2:-0}" gh() { - echo "$*" > "${BATS_TEST_TMPDIR}/captured_cmd" - echo 'issue body' + if [[ " $* " == *" --comments "* ]]; then + if [[ "${VIEW_COMMENTS_EXIT}" -ne 0 ]]; then + echo "comments request failed" >&2 + return "${VIEW_COMMENTS_EXIT}" + fi + [[ -z "${VIEW_COMMENTS}" ]] || printf '%s\n' "${VIEW_COMMENTS}" + return 0 + fi + printf '%s\n' "title: Example" "state: OPEN" "--" "Example body" } - run tool_issue_view '{"number":"42","with_comments":true}' +} + +# Usage: assert_view_without_comments +assert_view_without_comments() { + _stub_gh_view "" + run "$1" "$2" assert_success - local captured_cmd - captured_cmd=$(cat "${BATS_TEST_TMPDIR}/captured_cmd") - [[ "${captured_cmd}" == *"--comments"* ]] || { - echo "Expected --comments in command: ${captured_cmd}" - return 1 + assert_output $'title:\tExample\nstate:\tOPEN\n--\nExample body' +} + +# Usage: assert_view_with_comments +assert_view_with_comments() { + _stub_gh_view $'author:\tmonalisa\n--\nFirst comment' + run "$1" "$2" + assert_success + assert_output "$(printf 'title:\tExample\nstate:\tOPEN\n--\nExample body\n\n%s\nauthor:\tmonalisa\n--\nFirst comment' "$3")" +} + +# Usage: assert_view_comments_failure +assert_view_comments_failure() { + _stub_gh_view "" 1 + run "$1" "$2" + assert_failure + assert_output "comments request failed" +} + +# Under suppress_errors, a gh warning on stderr stays out of a successful +# call's output, for the comments call as well as the preview call. +# Usage: assert_view_comments_warning_suppressed +assert_view_comments_warning_suppressed() { + gh() { + echo "warning: rate limit low" >&2 + if [[ " $* " == *" --comments "* ]]; then + echo "First comment" + else + echo "Example body" + fi } + run "$1" "$2" + assert_success + assert_output "$(printf 'Example body\n\n%s\nFirst comment' "$3")" } +_test_issue_view_comments_suppressed() { assert_view_comments_warning_suppressed tool_issue_view '{"number":"42","with_comments":true,"suppress_errors":true}' "--- comments ---"; } +_test_pr_view_comments_suppressed() { assert_view_comments_warning_suppressed tool_pr_view '{"number":"123","comments":true,"suppress_errors":true}' "--- comments and reviews ---"; } + +bats_test_function --description "issue_view: with_comments under suppress_errors leaves gh's warnings out" -- _test_issue_view_comments_suppressed +bats_test_function --description "pr_view: comments under suppress_errors leaves gh's warnings out" -- _test_pr_view_comments_suppressed + +_test_issue_view_no_comments() { assert_view_without_comments tool_issue_view '{"number":"42","with_comments":true}'; } +_test_issue_view_comments() { assert_view_with_comments tool_issue_view '{"number":"42","with_comments":true}' "--- comments ---"; } +_test_issue_view_comments_failed() { assert_view_comments_failure tool_issue_view '{"number":"42","with_comments":true}'; } +_test_pr_view_no_comments() { assert_view_without_comments tool_pr_view '{"number":"123","comments":true}'; } +_test_pr_view_comments() { assert_view_with_comments tool_pr_view '{"number":"123","comments":true}' "--- comments and reviews ---"; } +_test_pr_view_comments_failed() { assert_view_comments_failure tool_pr_view '{"number":"123","comments":true}'; } + +bats_test_function --description "issue_view: with_comments on an issue without comments returns the issue" -- _test_issue_view_no_comments +bats_test_function --description "issue_view: with_comments appends the comments after the issue" -- _test_issue_view_comments +bats_test_function --description "issue_view: with_comments fails when the comments call fails" -- _test_issue_view_comments_failed +bats_test_function --description "pr_view: comments on a PR without comments returns the PR" -- _test_pr_view_no_comments +bats_test_function --description "pr_view: comments appends the comments and reviews after the PR" -- _test_pr_view_comments +bats_test_function --description "pr_view: comments fails when the comments call fails" -- _test_pr_view_comments_failed + @test "issue_view: jq_filter applied to output" { GH_STUB_OUTPUT='{"title":"my issue","state":"open"}' run tool_issue_view '{"number":"42","fields":"title,state","jq_filter":".title"}' diff --git a/plugins/github-mcp/CHANGELOG.md b/plugins/github-mcp/CHANGELOG.md index 47da62d..45c00b7 100644 --- a/plugins/github-mcp/CHANGELOG.md +++ b/plugins/github-mcp/CHANGELOG.md @@ -21,6 +21,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `search`, `search_repos`, `search_commits`, and `search_code` now put the search after `--`, so a search that starts with `-`, such as `-label:bug crash` or the code `->getId(`, is no longer read as a `gh` flag. `search_code` still sends the whole search as one exact-text phrase. +- `issue_view` with `with_comments: true` and `pr_view` with `comments: true` now return the + issue or PR (title, state, labels, body, and the other fields gh prints), followed by a + `--- comments ---` (issue) or `--- comments and reviews ---` (PR) line and the comments, or only + the issue or PR when it has none. Before, they returned only the comments, and nothing for an item + without comments: run without a terminal, `gh issue view --comments` and `gh pr view --comments` + print the comments alone. The tools now make one call for the item and a second with `--comments`; + a failed second call fails the tool call, or returns `fallback` when set. - In pi, `tool_search` now ranks the requested gh-tooling tool first for most exact-name queries, such as `issue_view` (before: 5th). Names that share terms with another tool can still rank 2nd to 5th: `search`, `repo_file`, and with the write server `pr_comments`, `pr_reviews`, and diff --git a/plugins/github-mcp/REFERENCE.md b/plugins/github-mcp/REFERENCE.md index 940ab64..247024c 100644 --- a/plugins/github-mcp/REFERENCE.md +++ b/plugins/github-mcp/REFERENCE.md @@ -64,7 +64,7 @@ Use gh-tooling pr_view with number 14642 and comments true - `number` (integer, required): PR number. - Repository selection: see [Repository selection](#repository-selection). - `fields` (string, optional): Comma-separated JSON fields (e.g. `title,body,state,reviews,files`) -- `comments` (boolean, optional): Include PR comments in text output. +- `comments` (boolean, optional): Append the PR's comments and reviews to the text output, after a `--- comments and reviews ---` line. Ignored when `fields` is set. ### `pr_diff` @@ -151,6 +151,9 @@ Use gh-tooling issue_view with number 8498 and fields "title,body,state,labels,c Use gh-tooling issue_view with number 8498 and with_field_values true ``` +`with_comments: true` appends the issue's comments to the text output, after a +`--- comments ---` line. It is ignored when `fields` is set. + `with_field_values` returns the issue's type and its field values keyed by field name, which `gh issue view` exposes for neither: diff --git a/plugins/github-mcp/mcp-server-gh/lib/common.sh b/plugins/github-mcp/mcp-server-gh/lib/common.sh index 2889ef9..94e8801 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/common.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/common.sh @@ -219,6 +219,49 @@ _gh_capture_split() { return "${__gh_cs_exit}" } +####################################### +# Run a gh issue/pr view command for the item's text preview, then again with +# --comments for its comments. Without a TTY, gh prints only the comments when +# given --comments, so the preview needs a call of its own. +# Arguments: +# $1 separator line put between the preview and the comments, +# $2 suppress_errors; "true" leaves gh's stderr out of the output, +# $3... the view command and its arguments, without --comments. +# Outputs: +# The preview, then a blank line, the separator, and the comments when gh +# printed any. On failure, the failed call's output. +# Returns: +# 0 when both calls succeed, otherwise the failed call's exit status. +####################################### +_gh_view_with_comments() { + local separator="$1" suppress_errors="$2" + shift 2 + local preview comments exit_code=0 + + if [[ "${suppress_errors}" == "true" ]]; then + preview=$("$@" 2>/dev/null) || exit_code=$? + else + preview=$("$@" 2>&1) || exit_code=$? + fi + if [[ ${exit_code} -ne 0 ]]; then + printf '%s\n' "${preview}" + return "${exit_code}" + fi + + if [[ "${suppress_errors}" == "true" ]]; then + comments=$("$@" --comments 2>/dev/null) || exit_code=$? + else + comments=$("$@" --comments 2>&1) || exit_code=$? + fi + if [[ ${exit_code} -ne 0 ]]; then + printf '%s\n' "${comments}" + return "${exit_code}" + fi + + printf '%s\n' "${preview}" + [[ -z "${comments}" ]] || printf '\n%s\n%s\n' "${separator}" "${comments}" +} + ####################################### # Resolve the organization owning org-level resources (issue types, issue fields). # Priority: org > owner > repo-shaped args > GH_DEFAULT_REPO > git remote. diff --git a/plugins/github-mcp/mcp-server-gh/lib/issue.sh b/plugins/github-mcp/mcp-server-gh/lib/issue.sh index e9d03ff..ac53769 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/issue.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/issue.sh @@ -3,7 +3,8 @@ # Tools: issue_view, issue_list # View a GitHub issue with optional comments, type, and field values. -# Maps to: gh issue view [--repo owner/repo] [--json ] [--comments] +# Maps to: gh issue view [--repo owner/repo] [--json ], +# then gh issue view [--repo owner/repo] --comments for comments, # and gh api repos//issues/ for the type and field values tool_issue_view() { local args="$1" @@ -61,13 +62,13 @@ tool_issue_view() { if [[ -n "${fields}" ]]; then cmd+=("--json" "${fields}") - elif [[ "${with_comments}" == "true" ]]; then - cmd+=("--comments") fi log "INFO" "issue_view: ${cmd[*]}" local __raw __exit=0 - if [[ "${suppress_errors}" == "true" ]]; then + if [[ -z "${fields}" && "${with_comments}" == "true" ]]; then + __raw=$(_gh_view_with_comments "--- comments ---" "${suppress_errors}" "${cmd[@]}") || __exit=$? + elif [[ "${suppress_errors}" == "true" ]]; then __raw=$("${cmd[@]}" 2>/dev/null) || __exit=$? else __raw=$("${cmd[@]}" 2>&1) || __exit=$? diff --git a/plugins/github-mcp/mcp-server-gh/lib/pr.sh b/plugins/github-mcp/mcp-server-gh/lib/pr.sh index 4cbf7a4..28f8e7f 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/pr.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/pr.sh @@ -3,7 +3,8 @@ # Tools: pr_view, pr_diff, pr_list, pr_checks, pr_comments, pr_reviews, pr_files, pr_commits # View pull request details. -# Maps to: gh pr view [] [--repo owner/repo] [--json ] [--comments] +# Maps to: gh pr view [--repo owner/repo] [--json ], +# then gh pr view [--repo owner/repo] --comments for comments tool_pr_view() { local args="$1" @@ -40,13 +41,13 @@ tool_pr_view() { if [[ -n "${fields}" ]]; then cmd+=("--json" "${fields}") - elif [[ "${comments}" == "true" ]]; then - cmd+=("--comments") fi log "INFO" "pr_view: ${cmd[*]}" local __raw __exit=0 - if [[ "${suppress_errors}" == "true" ]]; then + if [[ -z "${fields}" && "${comments}" == "true" ]]; then + __raw=$(_gh_view_with_comments "--- comments and reviews ---" "${suppress_errors}" "${cmd[@]}") || __exit=$? + elif [[ "${suppress_errors}" == "true" ]]; then __raw=$("${cmd[@]}" 2>/dev/null) || __exit=$? else __raw=$("${cmd[@]}" 2>&1) || __exit=$? From 1721d9e45dbffdedc96c490d83fb4af4475696a4 Mon Sep 17 00:00:00 2001 From: Martin Bens Date: Sat, 3 Oct 2026 15:40:18 +0200 Subject: [PATCH 2/3] fix(github-mcp): keep gh warnings out of issue and PR comments _gh_view_with_comments captured each call's stderr together with its stdout, so without suppress_errors a gh warning on an item without comments produced a `--- comments ---` line followed by the warning. Both calls now go through _gh_capture_split: the separator depends on the comments output alone, a warning on a successful call is dropped as in the other tools that capture stderr apart, and a failed call reports its stderr. The helper no longer takes suppress_errors, since the callers already decide whether to print a failure. It logs the --comments call, which the server log left out. The schema descriptions of with_comments and comments now name the separator, the reviews in a PR's list, and that max_lines counts the item and its comments together. REFERENCE.md corrects the with_field_values case and lists the known limits of the two calls: a comment count that can disagree with the list, hidden comments gh leaves out, and a separator a body can contain. New tests cover a warning on an item without comments, a failed first call that skips the comments call, fallback when the comments call fails, the number and --repo reaching the comments call, and fields turning the flag off. Co-Authored-By: Claude --- plugin-tests/github-mcp/mcp_tool_gh.bats | 84 +++++++++++++++---- plugins/github-mcp/CHANGELOG.md | 4 +- plugins/github-mcp/REFERENCE.md | 17 +++- .../github-mcp/mcp-server-gh/lib/common.sh | 34 ++++---- plugins/github-mcp/mcp-server-gh/lib/issue.sh | 2 +- plugins/github-mcp/mcp-server-gh/lib/pr.sh | 2 +- .../github-mcp/mcp-server-gh/tools-read.json | 4 +- 7 files changed, 108 insertions(+), 39 deletions(-) diff --git a/plugin-tests/github-mcp/mcp_tool_gh.bats b/plugin-tests/github-mcp/mcp_tool_gh.bats index 89cbcbe..177dff2 100644 --- a/plugin-tests/github-mcp/mcp_tool_gh.bats +++ b/plugin-tests/github-mcp/mcp_tool_gh.bats @@ -2012,10 +2012,12 @@ diff --git a/src/Third.php b/src/Third.php # Stub gh as a view command: the preview without --comments, and VIEW_COMMENTS # (exit VIEW_COMMENTS_EXIT) with it, the way gh prints them without a TTY. +# Each call's arguments go to gh_calls, one call per line. _stub_gh_view() { VIEW_COMMENTS="$1" VIEW_COMMENTS_EXIT="${2:-0}" gh() { + echo "$*" >> "${BATS_TEST_TMPDIR}/gh_calls" if [[ " $* " == *" --comments "* ]]; then if [[ "${VIEW_COMMENTS_EXIT}" -ne 0 ]]; then echo "comments request failed" >&2 @@ -2052,28 +2054,82 @@ assert_view_comments_failure() { assert_output "comments request failed" } -# Under suppress_errors, a gh warning on stderr stays out of a successful -# call's output, for the comments call as well as the preview call. -# Usage: assert_view_comments_warning_suppressed -assert_view_comments_warning_suppressed() { +# A gh warning on stderr is not a comment: an item without comments still +# comes back without a separator. +# Usage: assert_view_warning_not_a_comment +assert_view_warning_not_a_comment() { gh() { echo "warning: rate limit low" >&2 - if [[ " $* " == *" --comments "* ]]; then - echo "First comment" - else - echo "Example body" - fi + [[ " $* " == *" --comments "* ]] || echo "Example body" } run "$1" "$2" assert_success - assert_output "$(printf 'Example body\n\n%s\nFirst comment' "$3")" + assert_output "Example body" } -_test_issue_view_comments_suppressed() { assert_view_comments_warning_suppressed tool_issue_view '{"number":"42","with_comments":true,"suppress_errors":true}' "--- comments ---"; } -_test_pr_view_comments_suppressed() { assert_view_comments_warning_suppressed tool_pr_view '{"number":"123","comments":true,"suppress_errors":true}' "--- comments and reviews ---"; } +# A failed preview call is reported as is, and the comments call never runs. +# Usage: assert_view_preview_failure +assert_view_preview_failure() { + gh() { + echo "$*" >> "${BATS_TEST_TMPDIR}/gh_calls" + echo "could not resolve to an issue" >&2 + return 1 + } + run "$1" "$2" + assert_failure + assert_output "could not resolve to an issue" + run grep -c -- "--comments" "${BATS_TEST_TMPDIR}/gh_calls" + assert_output "0" +} -bats_test_function --description "issue_view: with_comments under suppress_errors leaves gh's warnings out" -- _test_issue_view_comments_suppressed -bats_test_function --description "pr_view: comments under suppress_errors leaves gh's warnings out" -- _test_pr_view_comments_suppressed +# Usage: assert_view_comments_failure_fallback +assert_view_comments_failure_fallback() { + _stub_gh_view "" 1 + run "$1" "$2" + assert_success + assert_output "no data" +} + +# Usage: assert_view_comments_call_args +assert_view_comments_call_args() { + _stub_gh_view "First comment" + run "$1" "$2" + assert_success + run tail -n 1 "${BATS_TEST_TMPDIR}/gh_calls" + assert_output "$3" +} + +# With fields, gh returns JSON and the flag does not apply: one call, no --comments. +# Usage: assert_view_fields_skip_comments +assert_view_fields_skip_comments() { + _stub_gh_view "First comment" + run "$1" "$2" + assert_success + run cat "${BATS_TEST_TMPDIR}/gh_calls" + assert_output "$3" +} + +_test_issue_view_warning() { assert_view_warning_not_a_comment tool_issue_view '{"number":"42","with_comments":true}'; } +_test_pr_view_warning() { assert_view_warning_not_a_comment tool_pr_view '{"number":"123","comments":true}'; } +_test_issue_view_preview_failed() { assert_view_preview_failure tool_issue_view '{"number":"42","with_comments":true}'; } +_test_pr_view_preview_failed() { assert_view_preview_failure tool_pr_view '{"number":"123","comments":true}'; } +_test_issue_view_comments_fallback() { assert_view_comments_failure_fallback tool_issue_view '{"number":"42","with_comments":true,"fallback":"no data"}'; } +_test_pr_view_comments_fallback() { assert_view_comments_failure_fallback tool_pr_view '{"number":"123","comments":true,"fallback":"no data"}'; } +_test_issue_view_comments_args() { assert_view_comments_call_args tool_issue_view '{"number":"42","repo":"acme/app","with_comments":true}' "issue view 42 --repo acme/app --comments"; } +_test_pr_view_comments_args() { assert_view_comments_call_args tool_pr_view '{"number":"123","repo":"acme/app","comments":true}' "pr view 123 --repo acme/app --comments"; } +_test_issue_view_fields_comments() { assert_view_fields_skip_comments tool_issue_view '{"number":"42","repo":"acme/app","fields":"title","with_comments":true}' "issue view 42 --repo acme/app --json title"; } +_test_pr_view_fields_comments() { assert_view_fields_skip_comments tool_pr_view '{"number":"123","repo":"acme/app","fields":"title","comments":true}' "pr view 123 --repo acme/app --json title"; } + +bats_test_function --description "issue_view: with_comments does not count a gh warning as a comment" -- _test_issue_view_warning +bats_test_function --description "pr_view: comments does not count a gh warning as a comment" -- _test_pr_view_warning +bats_test_function --description "issue_view: with_comments reports a failed issue call without fetching comments" -- _test_issue_view_preview_failed +bats_test_function --description "pr_view: comments reports a failed PR call without fetching comments" -- _test_pr_view_preview_failed +bats_test_function --description "issue_view: with_comments returns fallback when the comments call fails" -- _test_issue_view_comments_fallback +bats_test_function --description "pr_view: comments returns fallback when the comments call fails" -- _test_pr_view_comments_fallback +bats_test_function --description "issue_view: the comments call targets the same issue and repo" -- _test_issue_view_comments_args +bats_test_function --description "pr_view: the comments call targets the same PR and repo" -- _test_pr_view_comments_args +bats_test_function --description "issue_view: with_comments does not apply when fields is set" -- _test_issue_view_fields_comments +bats_test_function --description "pr_view: comments does not apply when fields is set" -- _test_pr_view_fields_comments _test_issue_view_no_comments() { assert_view_without_comments tool_issue_view '{"number":"42","with_comments":true}'; } _test_issue_view_comments() { assert_view_with_comments tool_issue_view '{"number":"42","with_comments":true}' "--- comments ---"; } diff --git a/plugins/github-mcp/CHANGELOG.md b/plugins/github-mcp/CHANGELOG.md index 45c00b7..a234d49 100644 --- a/plugins/github-mcp/CHANGELOG.md +++ b/plugins/github-mcp/CHANGELOG.md @@ -27,7 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the issue or PR when it has none. Before, they returned only the comments, and nothing for an item without comments: run without a terminal, `gh issue view --comments` and `gh pr view --comments` print the comments alone. The tools now make one call for the item and a second with `--comments`; - a failed second call fails the tool call, or returns `fallback` when set. + a failed second call fails the tool call, or returns `fallback` when set. A warning gh prints on + stderr stays out of the result. `max_lines` now counts the item and its comments together, where + before it applied to the comments alone. `REFERENCE.md` lists the known limits of the two calls. - In pi, `tool_search` now ranks the requested gh-tooling tool first for most exact-name queries, such as `issue_view` (before: 5th). Names that share terms with another tool can still rank 2nd to 5th: `search`, `repo_file`, and with the write server `pr_comments`, `pr_reviews`, and diff --git a/plugins/github-mcp/REFERENCE.md b/plugins/github-mcp/REFERENCE.md index 247024c..76bedc8 100644 --- a/plugins/github-mcp/REFERENCE.md +++ b/plugins/github-mcp/REFERENCE.md @@ -64,7 +64,7 @@ Use gh-tooling pr_view with number 14642 and comments true - `number` (integer, required): PR number. - Repository selection: see [Repository selection](#repository-selection). - `fields` (string, optional): Comma-separated JSON fields (e.g. `title,body,state,reviews,files`) -- `comments` (boolean, optional): Append the PR's comments and reviews to the text output, after a `--- comments and reviews ---` line. Ignored when `fields` is set. +- `comments` (boolean, optional): Append the PR's comments and reviews to the text output, after a `--- comments and reviews ---` line. Ignored when `fields` is set. `max_lines` counts the PR and its comments together. The limits listed under [`issue_view`](#issue_view) apply here too. ### `pr_diff` @@ -152,7 +152,20 @@ Use gh-tooling issue_view with number 8498 and with_field_values true ``` `with_comments: true` appends the issue's comments to the text output, after a -`--- comments ---` line. It is ignored when `fields` is set. +`--- comments ---` line. It is ignored when `fields` is set, and rejected together +with `with_field_values`. `max_lines` counts the issue and its comments together, +so a long body can leave the comments out. + +The issue and its comments come from two `gh` calls. If the comments call fails, +the whole call fails, or returns `fallback`; the tool never returns the issue +without its comments. Known limits, which apply to `pr_view` with `comments` too: + +- A comment posted or deleted between the two calls makes the issue's + `comments:` count disagree with the list that follows. +- `gh` leaves hidden (minimized) comments out of the list, so an issue whose only + comments are hidden shows a count but no separator. +- The separator is plain text: a body that contains the same line reads as the + start of the comments. `with_field_values` returns the issue's type and its field values keyed by field name, which `gh issue view` exposes for neither: diff --git a/plugins/github-mcp/mcp-server-gh/lib/common.sh b/plugins/github-mcp/mcp-server-gh/lib/common.sh index 94e8801..5622019 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/common.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/common.sh @@ -222,39 +222,37 @@ _gh_capture_split() { ####################################### # Run a gh issue/pr view command for the item's text preview, then again with # --comments for its comments. Without a TTY, gh prints only the comments when -# given --comments, so the preview needs a call of its own. +# given --comments, so the preview needs a call of its own. Each call's stderr +# is kept out of its output, so a gh warning can never pass for a comment. +# Known limits: the two calls are separate requests, so a comment posted +# between them can make the preview's comment count disagree with the list; +# gh leaves hidden (minimized) comments out of the list. # Arguments: -# $1 separator line put between the preview and the comments, -# $2 suppress_errors; "true" leaves gh's stderr out of the output, +# $1 tool name, for the log, +# $2 separator line put between the preview and the comments, # $3... the view command and its arguments, without --comments. # Outputs: # The preview, then a blank line, the separator, and the comments when gh -# printed any. On failure, the failed call's output. +# printed any. On failure, the failed call's stderr, or its stdout when +# stderr is empty. # Returns: # 0 when both calls succeed, otherwise the failed call's exit status. ####################################### _gh_view_with_comments() { - local separator="$1" suppress_errors="$2" + local tool_name="$1" separator="$2" shift 2 - local preview comments exit_code=0 + local preview comments err exit_code=0 - if [[ "${suppress_errors}" == "true" ]]; then - preview=$("$@" 2>/dev/null) || exit_code=$? - else - preview=$("$@" 2>&1) || exit_code=$? - fi + _gh_capture_split preview err "$@" || exit_code=$? if [[ ${exit_code} -ne 0 ]]; then - printf '%s\n' "${preview}" + printf '%s\n' "${err:-${preview}}" return "${exit_code}" fi - if [[ "${suppress_errors}" == "true" ]]; then - comments=$("$@" --comments 2>/dev/null) || exit_code=$? - else - comments=$("$@" --comments 2>&1) || exit_code=$? - fi + log "INFO" "${tool_name}: $* --comments" + _gh_capture_split comments err "$@" --comments || exit_code=$? if [[ ${exit_code} -ne 0 ]]; then - printf '%s\n' "${comments}" + printf '%s\n' "${err:-${comments}}" return "${exit_code}" fi diff --git a/plugins/github-mcp/mcp-server-gh/lib/issue.sh b/plugins/github-mcp/mcp-server-gh/lib/issue.sh index ac53769..494875b 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/issue.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/issue.sh @@ -67,7 +67,7 @@ tool_issue_view() { log "INFO" "issue_view: ${cmd[*]}" local __raw __exit=0 if [[ -z "${fields}" && "${with_comments}" == "true" ]]; then - __raw=$(_gh_view_with_comments "--- comments ---" "${suppress_errors}" "${cmd[@]}") || __exit=$? + __raw=$(_gh_view_with_comments "issue_view" "--- comments ---" "${cmd[@]}") || __exit=$? elif [[ "${suppress_errors}" == "true" ]]; then __raw=$("${cmd[@]}" 2>/dev/null) || __exit=$? else diff --git a/plugins/github-mcp/mcp-server-gh/lib/pr.sh b/plugins/github-mcp/mcp-server-gh/lib/pr.sh index 28f8e7f..5fe320d 100644 --- a/plugins/github-mcp/mcp-server-gh/lib/pr.sh +++ b/plugins/github-mcp/mcp-server-gh/lib/pr.sh @@ -46,7 +46,7 @@ tool_pr_view() { log "INFO" "pr_view: ${cmd[*]}" local __raw __exit=0 if [[ -z "${fields}" && "${comments}" == "true" ]]; then - __raw=$(_gh_view_with_comments "--- comments and reviews ---" "${suppress_errors}" "${cmd[@]}") || __exit=$? + __raw=$(_gh_view_with_comments "pr_view" "--- comments and reviews ---" "${cmd[@]}") || __exit=$? elif [[ "${suppress_errors}" == "true" ]]; then __raw=$("${cmd[@]}" 2>/dev/null) || __exit=$? else diff --git a/plugins/github-mcp/mcp-server-gh/tools-read.json b/plugins/github-mcp/mcp-server-gh/tools-read.json index a15f8ad..4ebb950 100644 --- a/plugins/github-mcp/mcp-server-gh/tools-read.json +++ b/plugins/github-mcp/mcp-server-gh/tools-read.json @@ -26,7 +26,7 @@ }, "comments": { "type": "boolean", - "description": "Include PR comments in output. Only applies when 'fields' is not set.", + "description": "Append the PR's comments and reviews after the PR text, below a '--- comments and reviews ---' line. Only applies when 'fields' is not set. max_lines counts the PR and its comments together.", "default": false }, "jq_filter": { @@ -478,7 +478,7 @@ }, "with_comments": { "type": "boolean", - "description": "Include issue comments in text output. Only applies when 'fields' is not set.", + "description": "Append the issue's comments after the issue text, below a '--- comments ---' line. Only applies when 'fields' is not set; cannot be combined with 'with_field_values'. max_lines counts the issue and its comments together.", "default": false }, "with_field_values": { From a8f6d4829f911ee0c26290cdc91e4d16a99b33a6 Mon Sep 17 00:00:00 2001 From: Martin Bens Date: Sat, 3 Oct 2026 15:43:05 +0200 Subject: [PATCH 3/3] fix(github-mcp): keep pr_view's comments description out of pi's ranking pi's tool_search adds each tool's parameter descriptions to its search text. The new description of pr_view's comments repeated "comments" and "reviews", so pr_comments ranked 6th and pr_reviews 4th, past the limits tool-search-ranking.test.ts allows (5th and 3rd). The description in tools-read.json now names those words once at most and leaves the separator text and the reviews to REFERENCE.md. Co-Authored-By: Claude --- plugins/github-mcp/mcp-server-gh/tools-read.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/github-mcp/mcp-server-gh/tools-read.json b/plugins/github-mcp/mcp-server-gh/tools-read.json index 4ebb950..3ce3c78 100644 --- a/plugins/github-mcp/mcp-server-gh/tools-read.json +++ b/plugins/github-mcp/mcp-server-gh/tools-read.json @@ -26,7 +26,7 @@ }, "comments": { "type": "boolean", - "description": "Append the PR's comments and reviews after the PR text, below a '--- comments and reviews ---' line. Only applies when 'fields' is not set. max_lines counts the PR and its comments together.", + "description": "Append the PR comments after the PR text, below a separator line. Only applies when 'fields' is not set. max_lines counts both together.", "default": false }, "jq_filter": {