Project 16: GitHub MCP Integration - PR Workflow Automation

Project 16: GitHub MCP Integration - PR Workflow Automation


Project Overview

Attribute Value
File P16-github-mcp-integration.md
Main Programming Language TypeScript
Alternative Programming Languages Python, Go
Coolness Level Level 3: Genuinely Clever
Business Potential 3. The “Service & Support” Model
Difficulty Level 3: Advanced
Knowledge Area MCP / GitHub API / DevOps
Software or Tool MCP SDK, GitHub API, gh CLI
Main Book “Software Engineering at Google” by Winters et al.
Time Estimate 2 weeks
Prerequisites Project 15 completed, GitHub API familiarity, OAuth understanding

What You Will Build

An MCP server that wraps the GitHub API for PR workflows: create PRs, list open PRs, add reviewers, respond to comments, merge PRs. Includes OAuth authentication and rate limiting.

This project shows how MCP servers can wrap existing APIs to make them accessible to Claude. You will learn about authentication, pagination, and real-world API integration.


Real World Outcome

You: Create a PR for my current branch

Claude: [Invokes mcp__github__create_pr]

I have created PR #142:

  Title: feat(auth): Add OAuth2 support
  URL: https://github.com/you/repo/pull/142
  Description: Added OAuth2 authentication with refresh token support
  Labels: enhancement, needs-review
  Reviewers: @alice, @bob (auto-assigned based on CODEOWNERS)

Status: Ready for review

You: What comments are on the PR?

Claude: [Invokes mcp__github__list_pr_comments]

Comments on PR #142:

@alice (2 hours ago):
> The token refresh logic looks good, but can we add a test
> for the edge case when the refresh token expires?

@bob (1 hour ago):
> +1 on Alice's comment. Also, should we log token refresh events?

Would you like me to respond to these comments or make changes?

The Core Question You Are Answering

“How do I wrap an existing API (like GitHub) as an MCP server so Claude can interact with it naturally?”

Many developers already have tools they love. MCP lets you keep using those tools through Claude, creating a natural language interface to existing workflows. This pattern applies to any REST API you want to expose.


MCP API Wrapper Architecture

+------------------------------------------------------------------+
|                        CLAUDE CODE                                 |
|                                                                    |
|   "Create a PR for my current branch"                             |
|                          |                                         |
|                          v                                         |
|   +------------------------------------------------------------+   |
|   |                    MCP CLIENT                               |   |
|   +------------------------------------------------------------+   |
|                          |                                         |
+--------------------------|------------------------------------------+
                           | stdio (JSON-RPC)
                           v
+------------------------------------------------------------------+
|                   GITHUB MCP SERVER                                |
|                                                                    |
|  +--------------------+  +--------------------+                    |
|  | Tool: create_pr    |  | Tool: list_prs     |                    |
|  | Tool: merge_pr     |  | Tool: add_reviewer |                    |
|  | Tool: list_comments|  | Tool: respond       |                    |
|  +--------------------+  +--------------------+                    |
|                          |                                         |
|  +--------------------+  +--------------------+                    |
|  | Auth Handler       |  | Rate Limiter       |                    |
|  | (Token mgmt)       |  | (Quota tracking)   |                    |
|  +--------------------+  +--------------------+                    |
|                          |                                         |
+--------------------------|------------------------------------------+
                           | HTTPS
                           v
+------------------------------------------------------------------+
|                     GITHUB REST API                                |
|                   api.github.com                                   |
+------------------------------------------------------------------+

GitHub API Rate Limiting

+---------------------------------------------------------------+
|                    RATE LIMIT FLOW                             |
+---------------------------------------------------------------+
|                                                                |
|  Request 1          Request 2          Request 3              |
|      |                  |                  |                  |
|      v                  v                  v                  |
|  +---------+        +---------+        +---------+            |
|  | Check   |        | Check   |        | Check   |            |
|  | Quota   |        | Quota   |        | Quota   |            |
|  +---------+        +---------+        +---------+            |
|      |                  |                  |                  |
|  Remaining: 4999    Remaining: 4998    Remaining: 0           |
|      |                  |                  |                  |
|      v                  v                  v                  |
|  +----------+       +----------+       +----------+           |
|  | PROCEED  |       | PROCEED  |       | WAIT FOR |           |
|  |          |       |          |       | RESET    |           |
|  +----------+       +----------+       +----------+           |
|                                             |                 |
|                            Reset-At: 2025-12-22T10:00:00Z    |
|                                             |                 |
|                                             v                 |
|                                        Wait 45 sec            |
|                                             |                 |
|                                             v                 |
|                                         RETRY                 |
|                                                                |
+---------------------------------------------------------------+

Response Headers:
  X-RateLimit-Limit: 5000
  X-RateLimit-Remaining: 4998
  X-RateLimit-Reset: 1734865200

Concepts You Must Understand First

Stop and research these before coding:

1. GitHub API

| Aspect | Details | Reference | |——–|———|———–| | REST vs GraphQL | When to use each | docs.github.com/en/rest | | Authentication | PAT, OAuth Apps, GitHub Apps | GitHub authentication docs | | Rate limits | 5000 req/hour for authenticated | API rate limit docs | | Pagination | Link headers, cursor-based | REST pagination guide |

2. MCP Authentication Patterns

+--------------------------------------------------+
|           AUTHENTICATION FLOW                     |
+--------------------------------------------------+
|                                                   |
|  1. Environment Variable                          |
|     GITHUB_TOKEN=ghp_xxxx...                     |
|           |                                       |
|           v                                       |
|  2. MCP Server reads on startup                  |
|     const token = process.env.GITHUB_TOKEN       |
|           |                                       |
|           v                                       |
|  3. Server includes in all requests              |
|     Authorization: Bearer ghp_xxxx...            |
|                                                   |
+--------------------------------------------------+

3. Complex Tool Schemas

  • Nested objects in inputSchema
  • Optional vs required parameters
  • Array parameters for batch operations
  • Reference: JSON Schema specification

Questions to Guide Your Design

Before implementing, think through these:

1. What Operations to Support?

Category Operations Complexity
PRs create, list, merge, close, request_review Medium-High
Comments list, create, respond, resolve Medium
Issues list, create, close, label Medium
Repos list, get_info Low

2. Authentication Strategy

Method Pros Cons Use When
Personal Access Token Simple setup Per-user, manual Local development
OAuth App User-friendly Complex flow Multi-user apps
GitHub CLI auth Reuse existing Dependency on gh When gh is available

3. Error Handling Scenarios

Error Response Code Your Action
Rate limit exceeded 403 Return remaining/reset time
Not found 404 Clear error message
Permission denied 403 Explain required permissions
Network failure - Retry with backoff
Invalid input 422 Surface validation errors

Thinking Exercise

Design the PR Creation Tool

{
  "name": "create_pr",
  "description": "Create a pull request from a branch",
  "inputSchema": {
    "type": "object",
    "properties": {
      "repo": {
        "type": "string",
        "description": "Repository in format owner/repo"
      },
      "head": {
        "type": "string",
        "description": "Branch containing changes"
      },
      "base": {
        "type": "string",
        "description": "Branch to merge into",
        "default": "main"
      },
      "title": {
        "type": "string",
        "description": "PR title"
      },
      "body": {
        "type": "string",
        "description": "PR description in markdown"
      },
      "draft": {
        "type": "boolean",
        "description": "Create as draft PR",
        "default": false
      },
      "reviewers": {
        "type": "array",
        "items": {"type": "string"},
        "description": "GitHub usernames to request review from"
      },
      "labels": {
        "type": "array",
        "items": {"type": "string"},
        "description": "Labels to apply to the PR"
      }
    },
    "required": ["repo", "head", "title"]
  }
}

Questions to consider:

  • Should you auto-detect repo from the current git directory?
  • How do you handle branch names with slashes (e.g., feature/auth)?
  • What if the user does not specify reviewers: should you use CODEOWNERS?
  • Should you auto-generate the PR body from commit messages?

The Interview Questions They Will Ask

  1. “How would you build a natural language interface to the GitHub API?”
    • Discuss MCP tool design, parameter mapping, and response formatting.
  2. “How do you handle API rate limits in a user-facing tool?”
    • Explain exponential backoff, remaining/reset headers, and user notification.
  3. “What is the security model for API tokens in CLI tools?”
    • Discuss environment variables, secure storage, and credential rotation.
  4. “How do you design tool schemas for complex operations?”
    • Explain JSON Schema, optional parameters, and sensible defaults.
  5. “How would you test an MCP server that depends on external APIs?”
    • Discuss mocking, integration tests, and recorded responses.

Hints in Layers

Hint 1: Start with gh CLI

The gh CLI is already installed on most developer machines. Shell out to it for a simpler initial implementation:

import { execSync } from "child_process";

const result = execSync(`gh pr create --title "${title}" --body "${body}"`, {
  encoding: "utf-8",
  cwd: repoPath
});

Hint 2: Use Environment Variables

Set GITHUB_TOKEN and read it in your server. Never hardcode tokens:

const token = process.env.GITHUB_TOKEN;
if (!token) {
  throw new Error("GITHUB_TOKEN environment variable required");
}

Hint 3: Track Rate Limit Headers

GitHub returns rate limit info in every response. Surface it to Claude:

const remaining = response.headers.get("X-RateLimit-Remaining");
const reset = response.headers.get("X-RateLimit-Reset");

return {
  data: result,
  rateLimit: {
    remaining: parseInt(remaining),
    resetsAt: new Date(parseInt(reset) * 1000)
  }
};

Hint 4: Handle Pagination

For list operations, accept pagination parameters and indicate if there are more results:

{
  name: "list_prs",
  inputSchema: {
    type: "object",
    properties: {
      repo: { type: "string" },
      state: { type: "string", enum: ["open", "closed", "all"] },
      page: { type: "number", default: 1 },
      per_page: { type: "number", default: 30, maximum: 100 }
    }
  }
}

Books That Will Help

Topic Book Chapter Why It Helps
API Design “Building Microservices” by Newman Ch. 4 Service communication patterns
OAuth Patterns “OAuth 2.0 Simplified” by Parecki All Token-based authentication
Rate Limiting “Designing Data-Intensive Applications” Ch. 4 Distributed system patterns
Git Workflows “Software Engineering at Google” Ch. 16 Code review practices

Implementation Skeleton

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { execSync } from "child_process";

const server = new Server(
  { name: "github-mcp", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Rate limit tracking
let rateLimitRemaining = 5000;
let rateLimitReset = new Date();

server.setRequestHandler("tools/list", async () => {
  return {
    tools: [
      {
        name: "create_pr",
        description: "Create a pull request",
        inputSchema: {
          type: "object",
          properties: {
            repo: { type: "string", description: "owner/repo format" },
            head: { type: "string", description: "Source branch" },
            base: { type: "string", description: "Target branch", default: "main" },
            title: { type: "string", description: "PR title" },
            body: { type: "string", description: "PR description" },
            draft: { type: "boolean", default: false },
            reviewers: { type: "array", items: { type: "string" } }
          },
          required: ["repo", "head", "title"]
        }
      },
      {
        name: "list_prs",
        description: "List pull requests",
        inputSchema: {
          type: "object",
          properties: {
            repo: { type: "string" },
            state: { type: "string", enum: ["open", "closed", "all"], default: "open" }
          },
          required: ["repo"]
        }
      },
      {
        name: "list_pr_comments",
        description: "List comments on a pull request",
        inputSchema: {
          type: "object",
          properties: {
            repo: { type: "string" },
            pr_number: { type: "number" }
          },
          required: ["repo", "pr_number"]
        }
      },
      {
        name: "merge_pr",
        description: "Merge a pull request",
        inputSchema: {
          type: "object",
          properties: {
            repo: { type: "string" },
            pr_number: { type: "number" },
            merge_method: {
              type: "string",
              enum: ["merge", "squash", "rebase"],
              default: "squash"
            }
          },
          required: ["repo", "pr_number"]
        }
      }
    ]
  };
});

server.setRequestHandler("tools/call", async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === "create_pr") {
      const { repo, head, base = "main", title, body, draft, reviewers } = args;

      let cmd = `gh pr create --repo ${repo} --head ${head} --base ${base}`;
      cmd += ` --title ${JSON.stringify(title)}`;
      if (body) cmd += ` --body ${JSON.stringify(body)}`;
      if (draft) cmd += " --draft";
      if (reviewers?.length) cmd += ` --reviewer ${reviewers.join(",")}`;

      const result = execSync(cmd, { encoding: "utf-8" });

      return {
        content: [{
          type: "text",
          text: `PR created successfully:\n${result}`
        }]
      };
    }

    if (name === "list_prs") {
      const { repo, state = "open" } = args;
      const cmd = `gh pr list --repo ${repo} --state ${state} --json number,title,author,createdAt,url`;
      const result = JSON.parse(execSync(cmd, { encoding: "utf-8" }));

      if (result.length === 0) {
        return { content: [{ type: "text", text: `No ${state} PRs found.` }] };
      }

      let output = `Found ${result.length} ${state} PR(s):\n\n`;
      for (const pr of result) {
        output += `#${pr.number}: ${pr.title}\n`;
        output += `  Author: ${pr.author.login}\n`;
        output += `  Created: ${pr.createdAt}\n`;
        output += `  URL: ${pr.url}\n\n`;
      }

      return { content: [{ type: "text", text: output }] };
    }

    if (name === "list_pr_comments") {
      const { repo, pr_number } = args;
      const cmd = `gh pr view ${pr_number} --repo ${repo} --json comments`;
      const result = JSON.parse(execSync(cmd, { encoding: "utf-8" }));

      if (result.comments.length === 0) {
        return { content: [{ type: "text", text: "No comments on this PR." }] };
      }

      let output = `Comments on PR #${pr_number}:\n\n`;
      for (const comment of result.comments) {
        output += `@${comment.author.login} (${comment.createdAt}):\n`;
        output += `> ${comment.body.split("\n").join("\n> ")}\n\n`;
      }

      return { content: [{ type: "text", text: output }] };
    }

    if (name === "merge_pr") {
      const { repo, pr_number, merge_method = "squash" } = args;
      const cmd = `gh pr merge ${pr_number} --repo ${repo} --${merge_method}`;
      execSync(cmd, { encoding: "utf-8" });

      return {
        content: [{
          type: "text",
          text: `PR #${pr_number} merged successfully using ${merge_method} method.`
        }]
      };
    }

    return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };

  } catch (error) {
    return {
      content: [{
        type: "text",
        text: `Error: ${error.message}`
      }],
      isError: true
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("GitHub MCP server running on stdio");
}

main().catch(console.error);

Learning Milestones

Milestone What It Proves Verification
Basic PR operations work You understand API wrapping Create a test PR
Authentication is seamless You understand token management Works with GITHUB_TOKEN
Rate limits are handled Production-ready implementation Display remaining quota
Pagination works Handle large result sets List 100+ PRs

Core Challenges Mapped to Concepts

Challenge Concept Book Reference
OAuth authentication flow Token management “OAuth 2.0 Simplified”
Handling pagination API design patterns “Building Microservices” Ch. 4
Rate limit handling Resilient services “Designing Data-Intensive Applications”
Complex tool schemas Nested objects and arrays JSON Schema Specification

Extension Ideas

Once the basic server works, consider these enhancements:

  1. Add PR diff viewing to show changed files
  2. Implement comment threading for responding to specific comments
  3. Add CI status checking to verify checks pass before merge
  4. Implement auto-assign using CODEOWNERS file parsing
  5. Add PR templates support for consistent descriptions

Common Pitfalls

  1. Not handling rate limits - Always check remaining quota and wait if needed
  2. Forgetting to escape shell arguments - Use proper quoting or libraries
  3. Not handling pagination - Large repos have many PRs
  4. Exposing tokens in error messages - Sanitize error output
  5. Not handling network failures - Add retry logic with exponential backoff

Success Criteria

You have completed this project when:

  • Your MCP server authenticates with GitHub via GITHUB_TOKEN
  • Claude can create pull requests with title, body, and reviewers
  • Claude can list open PRs for any repository
  • Claude can view comments on a specific PR
  • Claude can merge PRs using different strategies
  • Rate limit information is tracked and surfaced
  • Errors are handled gracefully with helpful messages
  • The server works with any GitHub repository you have access to