Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gh-mcp-server

An MCP (Model Context Protocol) server written in Go that wraps the gh GitHub CLI. It exposes GitHub operations required by the AI agent skills in ~/.agents/skills as purpose-built MCP tools, delegating all execution to the gh CLI (no reimplementation of the GitHub API).

Built with github.com/mark3labs/mcp-go.

Design principle: purpose-built, not generic

This server deliberately exposes no generic API wrappers (no gh_api or gh_api_graphql pass-through). Each tool encapsulates a specific, concrete workflow derived from the skill files. The caller passes business-level inputs (PR number, comment ID, reply body) — never API paths, HTTP methods, or GraphQL queries. This ensures tools are self-documenting, type-safe, and map 1:1 to skill steps.

Requirements

  • Go 1.23+ (built/tested on Go 1.26)
  • gh CLI installed and authenticated (gh auth login)
  • git on PATH (used implicitly by some gh subcommands such as gh pr checkout)

Build

go build -o gh-mcp-server .

Run

The server speaks MCP over stdio:

./gh-mcp-server

Claude Code / Claude Desktop config

{
  "mcpServers": {
    "gh": {
      "command": "/Users/Shared/Opensource/gh-mcp-server/gh-mcp-server"
    }
  }
}

How it works

Every tool shells out to gh and returns stdout as an MCP text result. Non-zero gh exit codes are surfaced as MCP error results (isError: true) with the gh stderr message. Multi-line markdown bodies (PR/issue create, PR comment) are written to a temp file and passed via --body-file so newlines are preserved. For REST/GraphQL tools that need owner/repo resolution, the GH_REPO env var is set so gh api's {owner}/{repo} placeholders resolve correctly — the caller never constructs API paths.

Working directory (cwd)

Every tool requires a cwd string argument: the filesystem path to run gh from. The MCP server is launched once by the client and inherits the client's working directory, which is usually not the project you're operating on. Without cwd, gh resolves the repository from whatever git remote is reachable from the server's launch directory (often none), so PR/issue commands fail or target the wrong repo. Always pass cwd set to the project root so gh reads the correct git remote.

Tools (12)

Every tool also requires a cwd string param — see Working directory above.

Pull request tools

gh_pr_view

  • Purpose: Load a pull request's metadata as JSON (number, title, body, branches, state, commits).
  • Use case: pr-fix Step 2 — load PR context before analyzing feedback. pr-create — detect an existing PR for the current branch.
  • Differs from generic API: caller passes a PR number/URL/branch, not an API endpoint. Returns structured JSON fields selected for the review-fix workflow, not raw REST output.
  • gh mapping: gh pr view <pr_number> --json <fields> [--repo OWNER/REPO]
  • Params: pr_number (required), fields (default: number,title,body,url,headRefName,baseRefName,author,state,commits), repo

gh_pr_checkout

  • Purpose: Check out a PR's head branch locally to align the working branch with the PR.
  • Use case: pr-fix Step 3 — switch to the PR's branch before making fixes. The skill requires this even if the current branch looks related; the PR ref is authoritative.
  • Differs from generic API: encapsulates the checkout workflow, not a raw git command. The caller doesn't need to know the branch name — just the PR number.
  • gh mapping: gh pr checkout <pr_number> [--repo OWNER/REPO]
  • Params: pr_number (required), repo

gh_pr_diff

  • Purpose: Get the diff of a pull request against its base branch.
  • Use case: pr-fix Step 4 — understand what the PR currently changes before deciding what review feedback to address.
  • Differs from generic API: returns a PR-specific diff, not an arbitrary git diff. The caller passes a PR number, not a ref range.
  • gh mapping: gh pr diff <pr_number> [--patch ref] [--repo OWNER/REPO]
  • Params: pr_number (required), patch, repo

gh_pr_create

  • Purpose: Create a pull request for the current branch with a structured markdown body.
  • Use case: pr-create Step 8 — open a PR with ticket link, description, and checklist. ship-changes Step 5 — via pr-create.
  • Differs from generic API: handles the full PR creation workflow including temp-file body preservation and self-assignment. Caller passes title/body/base/head, not CLI flags.
  • gh mapping: gh pr create --title --body-file --base --head --assignee [--repo OWNER/REPO]
  • Params: title (required), body (required, markdown), base, head, assignee (default @me), repo

gh_pr_comment

  • Purpose: Post a single flat (non-threaded) PR comment.
  • Use case: pr-fix Step 12 — the sole exception for quote-replying to flat PR comments that have no line anchor and thus no thread to reply to. For line-anchored review-thread replies, use gh_pr_reply_to_review_thread instead.
  • Differs from generic API: narrowly scoped to the one permitted flat-comment scenario. The description explicitly warns against using it for thread replies.
  • gh mapping: gh pr comment <pr_number> --body-file [--repo OWNER/REPO]
  • Params: pr_number (required), body (required), repo

gh_pr_list_comments

  • Purpose: List flat (non-threaded, non-line-anchored) PR comments — the standalone conversation comments invisible to gh_pr_list_review_comments.
  • Use case: pr-fix Step 5 — load general PR discussion, questions, or status notes that have no file/line context. Without this tool, standalone comments are invisible to the agent.
  • Differs from gh_pr_list_review_comments: queries pullRequest.comments (flat, no path/line, no isResolved) instead of pullRequest.reviewThreads (line-anchored threads). The two are complementary — a PR can have both kinds.
  • gh mapping: gh api graphql with pullRequest.comments(first:100)
  • Params: pr_number (required), repo

gh_pr_list_review_comments

  • Purpose: List line-anchored review comments (review threads) on a PR with full metadata.
  • Use case: pr-fix Steps 2 & 5 — load inline review comments with file/line/body/ID to categorize feedback, identify actionable threads, and extract comment IDs for thread replies.
  • Differs from generic API: caller passes a PR number, not a GraphQL query. The query, variables, and placeholder resolution are encapsulated. Returns threaded GraphQL data (threads with id, isResolved, path, and comments with databaseId, body, author, path, line) — not a summary.
  • gh mapping: gh api graphql with pullRequest.reviewThreads(first:100).comments(first:100)
  • Params: pr_number (required), repo

gh_pr_list_unresolved_review_comments

  • Purpose: List only UNRESOLVED line-anchored review threads on a PR (filters isResolved=false server-side).
  • Use case: pr-fix Step 5 — load only actionable threads, skipping already-resolved ones to save tokens and avoid re-addressing fixed feedback.
  • Differs from gh_pr_list_review_comments: same GraphQL query, but unresolved threads are filtered server-side before rendering. Smaller output. Pagination applies before the filter, so a page may return fewer unresolved threads than first.
  • gh mapping: gh api graphql with pullRequest.reviewThreads(first:100).comments(first:100), filtered to isResolved=false
  • Params: pr_number (required), repo

gh_pr_list_reviews

  • Purpose: List reviews (approve/request-changes/comment states) on a PR.
  • Use case: pr-fix Step 5 — check review states (CHANGES_REQUESTED, APPROVED, etc.) to determine which reviews still have open feedback requiring fixes.
  • Differs from generic API: caller passes a PR number, not a GraphQL query. Encapsulates the reviews query specifically — distinct from review comments. Returns GraphQL nodes with id, body, state, author, submittedAt.
  • gh mapping: gh api graphql with pullRequest.reviews(first:100)
  • Params: pr_number (required), repo

gh_pr_reply_to_review_thread

  • Purpose: Reply directly to a specific line-anchored review thread comment.
  • Use case: pr-fix Step 12 — the ABSOLUTE RULE: always reply to the specific thread, never add standalone comments. The reply appears inline in the existing thread.
  • Differs from generic API: caller passes pr_number + comment_id + body, not a POST endpoint with a JSON payload. The endpoint construction, HTTP method, and JSON body encoding are encapsulated. The reply body is JSON-encoded and sent via stdin (--input -).
  • gh mapping: gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies --method POST --input -
  • Params: pr_number (required), comment_id (required), body (required), repo

gh_pr_list_review_threads

  • Purpose: Fetch unresolved review threads with their GraphQL node IDs (PRRT_...).
  • Use case: pr-fix Step 13 — map REST comment database IDs to GraphQL thread node IDs so addressed threads can be resolved. Thread resolution is GraphQL-only (no REST endpoint).
  • Differs from generic API: caller passes a PR number, not a GraphQL query string. The query, variable binding, and placeholder resolution are encapsulated. Returns threads with id, isResolved, and first comment metadata.
  • gh mapping: gh api graphql -f query=... -F owner={owner} -F repo={repo} -F pr=<n>
  • Params: pr_number (required), repo

gh_pr_resolve_review_thread

  • Purpose: Resolve a review thread by its GraphQL node ID.
  • Use case: pr-fix Step 13 — mark a thread as resolved after feedback is fully addressed and a reply has been posted. Only resolve threads where the fix is complete.
  • Differs from generic API: caller passes a thread node ID, not a GraphQL mutation string. The resolveReviewThread mutation is encapsulated.
  • gh mapping: gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "..."}) { thread { isResolved } } }'
  • Params: thread_id (required)

Issue tools

gh_issue_create

  • Purpose: Create a GitHub issue with a structured markdown body.
  • Use case: pr-create Step 6 — auto-create a ticket from summarized branch work when in auto ticket mode, with a generated title, description, and validation checklist.
  • Differs from generic API: handles the full issue creation workflow including temp-file body preservation and self-assignment. Caller passes title/body, not CLI flags.
  • gh mapping: gh issue create --title --body-file --assignee [--label] [--repo OWNER/REPO]
  • Params: title (required), body (required, markdown), assignee (default @me), label, repo

Auth tools

gh_auth_status

  • Purpose: Check gh CLI authentication status.
  • Use case: pr-fix, pr-create — verify gh is authenticated before attempting PR or issue operations, since all other tools will fail if auth is missing or expired.
  • Differs from generic API: surfaces auth diagnostics (including non-zero exit output) as readable text, not an error. A dedicated precondition check, not a generic shell-out.
  • gh mapping: gh auth status
  • Params: none

Skill coverage map

Skill Tools used
pr-fix gh_auth_status (precondition) → gh_pr_view (Step 2) → gh_pr_checkout (Step 3) → gh_pr_diff (Step 4) → gh_pr_list_review_comments + gh_pr_list_reviews (Step 5) → gh_pr_reply_to_review_thread (Step 12) → gh_pr_list_review_threads + gh_pr_resolve_review_thread (Step 13); gh_pr_comment (sole flat-comment exception)
pr-create gh_auth_status (precondition) → gh_issue_create (Step 6: auto-ticket) → gh_pr_create (Step 8) → gh_pr_view (detect existing PR)
ship-changes orchestrates new-branchcommit-and-pushpr-create; reaches GitHub only via gh_pr_create (through pr-create)

Why no generic gh_api / gh_api_graphql

The first iteration exposed generic gh_api and gh_api_graphql pass-through tools. These were removed because they violate the purpose-built principle: they required the caller to know API paths, HTTP methods, and GraphQL query strings — the exact knowledge the MCP server should encapsulate. The 5 specific tools that replaced them (gh_pr_list_review_comments, gh_pr_list_reviews, gh_pr_reply_to_review_thread, gh_pr_list_review_threads, gh_pr_resolve_review_thread) cover every gh api / gh api graphql use case found in the skill files, with business-level inputs instead of API-level plumbing.

Project layout

main.go                      entrypoint; starts stdio server
internal/gh/gh.go            gh CLI runner (stdin, args, env, exit-code/error handling)
internal/mcpserver/server.go server creation + tool registration
internal/mcpserver/tools.go  tool definitions (names, schemas, purpose descriptions)
internal/mcpserver/handlers.go tool handler implementations

Verifying

Quick smoke test against the running server:

printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0.0.0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| ./gh-mcp-server

Test a purpose-built REST tool (lists reviews on cli/cli PR #1):

printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0.0.0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"gh_pr_list_reviews","arguments":{"pr_number":"1","repo":"cli/cli"}}}' \
| ./gh-mcp-server

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages