Skip to main content
Glama
gh_api.py7.02 kB
"""GitHub GraphQL API wrapper using gh CLI.""" import json import subprocess from typing import Any class GitHubAPIError(Exception): """Exception raised for GitHub API errors.""" pass class GitHubAPI: """GitHub GraphQL API wrapper using gh CLI.""" @staticmethod def execute_graphql(query: str, variables: dict[str, Any]) -> dict[str, Any]: """ Execute a GraphQL query using gh CLI. Args: query: GraphQL query string variables: Query variables Returns: GraphQL response data Raises: GitHubAPIError: If the query fails or returns errors """ try: # Build gh api graphql command cmd = ["gh", "api", "graphql"] cmd.extend(["-f", f"query={query}"]) # Add variables with proper type conversion for key, value in variables.items(): if isinstance(value, bool): # Boolean values use -F flag cmd.extend(["-F", f"{key}={str(value).lower()}"]) elif isinstance(value, int): # Integer values use -F flag cmd.extend(["-F", f"{key}={value}"]) else: # String values use -f flag cmd.extend(["-f", f"{key}={value}"]) # Execute command result = subprocess.run(cmd, capture_output=True, text=True, check=True) # Parse response data = json.loads(result.stdout) # Check for GraphQL errors if "errors" in data: error_messages = [err.get("message", str(err)) for err in data["errors"]] raise GitHubAPIError(f"GraphQL errors: {', '.join(error_messages)}") return data.get("data", {}) except subprocess.CalledProcessError as e: raise GitHubAPIError(f"gh command failed: {e.stderr}") from e except json.JSONDecodeError as e: raise GitHubAPIError(f"Invalid JSON response: {e}") from e def get_pr_id(self, owner: str, repo: str, pull_number: int) -> str: """ Get the node ID of a pull request. Args: owner: Repository owner repo: Repository name pull_number: Pull request number Returns: Pull request node ID """ query = """ query GetPRId($owner: String!, $repo: String!, $pullNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pullNumber) { id } } } """ variables = {"owner": owner, "repo": repo, "pullNumber": pull_number} data = self.execute_graphql(query, variables) pr_id = data.get("repository", {}).get("pullRequest", {}).get("id") if not pr_id: raise GitHubAPIError(f"Pull request #{pull_number} not found") return pr_id def list_review_threads( self, owner: str, repo: str, pull_number: int, unresolved_only: bool = True ) -> list[dict[str, Any]]: """ List review threads for a pull request. Args: owner: Repository owner repo: Repository name pull_number: Pull request number unresolved_only: Only return unresolved threads Returns: List of review thread objects """ query = """ query GetReviewThreads($owner: String!, $repo: String!, $pullNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pullNumber) { reviewThreads(first: 100) { nodes { id isResolved path line startLine diffSide comments(first: 50) { nodes { id author { login } body createdAt } } } } } } } """ variables = {"owner": owner, "repo": repo, "pullNumber": pull_number} data = self.execute_graphql(query, variables) threads = ( data.get("repository", {}) .get("pullRequest", {}) .get("reviewThreads", {}) .get("nodes", []) ) # Filter by resolution status if requested if unresolved_only: threads = [t for t in threads if not t.get("isResolved", False)] return threads def add_thread_reply(self, pull_request_id: str, thread_id: str, body: str) -> dict[str, Any]: """ Add a reply to a review thread. Args: pull_request_id: Pull request node ID thread_id: Review thread node ID body: Reply content Returns: Created comment object """ query = """ mutation AddReply($pullRequestId: ID!, $threadId: ID!, $body: String!) { addPullRequestReviewThreadReply(input: { pullRequestId: $pullRequestId pullRequestReviewThreadId: $threadId body: $body }) { comment { id body createdAt author { login } } } } """ variables = {"pullRequestId": pull_request_id, "threadId": thread_id, "body": body} data = self.execute_graphql(query, variables) comment = data.get("addPullRequestReviewThreadReply", {}).get("comment", {}) if not comment: raise GitHubAPIError("Failed to create comment") return comment def resolve_thread(self, thread_id: str) -> dict[str, Any]: """ Resolve a review thread. Args: thread_id: Review thread node ID Returns: Updated thread object """ query = """ mutation ResolveThread($threadId: ID!) { resolveReviewThread(input: { threadId: $threadId }) { thread { id isResolved } } } """ variables = {"threadId": thread_id} data = self.execute_graphql(query, variables) thread = data.get("resolveReviewThread", {}).get("thread", {}) if not thread: raise GitHubAPIError("Failed to resolve thread") return thread

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/UtakataKyosui/PR-Review-Resolve-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server