Thanks to visit codestin.com
Credit goes to github.com

Skip to content

MCP PR tools report gh/auth failures as successful results (isError unset) #3162

Description

@noQbot

Summary

The MCP list_prs, get_pr_impact, and triage_prs tools return their failure messages ("gh CLI not found or not authenticated", "PR #N not found") as ordinary text content with isError left unset (i.e. false). A client that checks the isError field to distinguish failure from success reads a genuine failure as a successful tool call.

Steps to reproduce

  1. Run the MCP server without gh installed / authenticated.
  2. Call list_prs (or get_pr_impact({"pr_number": 1})).
  3. Inspect the tool result. The content carries the error text, but the result has isError: false (or isError absent) — it looks like a success.

Expected behavior

A failed tool call — gh missing / not authenticated, or a PR that cannot be resolved — should return a result with isError: true, so clients can tell a real failure from a valid result. (Input-validation errors on other tools already return isError: true; the PR tools are inconsistent with that.)

Actual behavior

isError is never set. Under the hood graphify.prs.fetch_prs raises RuntimeError, and the tool handler turns it into an ordinary string:

  • _tool_list_prs / _tool_triage_prs (graphify/serve.py): except RuntimeError as e: return f"Error: {e}"
  • _tool_get_pr_impact: if pr_data is None: return f"PR #{number} not found …"

call_tool then wraps the string as a normal TextContent, and the transport returns CallToolResult(content=…) with no isError. So the MCP contract shows success for every one of these failures.

Root cause

The PR tool handlers return error strings instead of signalling an error, and the dispatch layer (call_tool / the HTTP _on_call_tool) never sets isError.

Fix

Introduce an error signal a handler raises (e.g. a ToolError) for the gh-missing / unresolved-PR cases; propagate it through call_tool so the mcp 1.x decorator wraps it as an error result, and have the 2.x _on_call_tool return CallToolResult(content=…, isError=True). Normal not-found text (missing node/community) and a PR that exists but touched no files stay isError: false.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions