Build a GitHub MCP Server 實戰 — 自動化管理 Issue / PR / Repo

2026-05-12 · MCP 實戰 · Eric Cheung

前言:AI 直接操控 GitHub 嘅威力

快速結論(TL;DR)

想像下:對住 Claude Code 講一句「幫我 create 一個 PR,將 login page refactor 去用 Tailwind,assign 俾 @design-team」,10 秒後 PR 已經出咗。今日我哋就實現呢個夢想 — 建立一個 GitHub MCP Server,令 Claude Code 直接操控 GitHub。

呢個 MCP Server 會支援 8 個 Tools:管理 Issues(create/read/list/update)、管理 Pull Requests(create/read/merge)、管理 Repositories(get info/search)。全部經自然語言操控。


Part 1:Project Setup

安裝依賴

mkdir github-mcp-server && cd github-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk @octokit/rest dotenv

獲取 GitHub Token

完整 Server 架構

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListToolsRequestSchema,
  CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Octokit } from "@octokit/rest";
import "dotenv/config";

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

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

Part 2:定義 8 個 GitHub Tools

Tool 清單

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    // === Issues ===
    { name: "github_create_issue",
      description: "Create a new GitHub issue",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string", description: "Repo owner" },
          repo: { type: "string", description: "Repo name" },
          title: { type: "string", description: "Issue title" },
          body: { type: "string", description: "Issue description" },
          labels: { type: "array", items: { type: "string" } },
          assignees: { type: "array", items: { type: "string" } },
        }, required: ["owner", "repo", "title"],
      },
    },
    { name: "github_list_issues",
      description: "List issues in a repository",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          state: { type: "string", enum: ["open", "closed", "all"] },
          labels: { type: "string" },
        }, required: ["owner", "repo"],
      },
    },
    { name: "github_get_issue",
      description: "Get a specific issue by number",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          issue_number: { type: "number" },
        }, required: ["owner", "repo", "issue_number"],
      },
    },
    { name: "github_update_issue",
      description: "Update an existing issue",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          issue_number: { type: "number" },
          title: { type: "string" },
          body: { type: "string" },
          state: { type: "string", enum: ["open", "closed"] },
          labels: { type: "array", items: { type: "string" } },
        }, required: ["owner", "repo", "issue_number"],
      },
    },
    // === Pull Requests ===
    { name: "github_create_pr",
      description: "Create a new pull request",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          title: { type: "string" },
          head: { type: "string", description: "Source branch" },
          base: { type: "string", description: "Target branch" },
          body: { type: "string" },
        }, required: ["owner", "repo", "title", "head", "base"],
      },
    },
    { name: "github_list_prs",
      description: "List pull requests",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          state: { type: "string", enum: ["open", "closed", "all"] },
        }, required: ["owner", "repo"],
      },
    },
    { name: "github_merge_pr",
      description: "Merge a pull request",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
          pull_number: { type: "number" },
          merge_method: {
            type: "string",
            enum: ["merge", "squash", "rebase"],
          },
        }, required: ["owner", "repo", "pull_number"],
      },
    },
    // === Repo ===
    { name: "github_get_repo",
      description: "Get repository information",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string" },
          repo: { type: "string" },
        }, required: ["owner", "repo"],
      },
    },
  ],
}));

Part 3:實作 Tool Handlers

Handle Tool Calls

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const { name, arguments: args } = req.params;

  switch (name) {
    case "github_create_issue": {
      const { data } = await octokit.issues.create({
        owner: args.owner,
        repo: args.repo,
        title: args.title,
        body: args.body,
        labels: args.labels,
        assignees: args.assignees,
      });
      return { content: [{ type: "text",
        text: `✅ Issue #${data.number} created: ${data.html_url}` }] };
    }

    case "github_list_issues": {
      const { data } = await octokit.issues.listForRepo({
        owner: args.owner, repo: args.repo,
        state: args.state || "open",
        labels: args.labels,
      });
      const summary = data.map(i =>
        `#${i.number} [${i.state}] ${i.title}`).join("\n");
      return { content: [{ type: "text", text: summary }] };
    }

    case "github_create_pr": {
      const { data } = await octokit.pulls.create({
        owner: args.owner, repo: args.repo,
        title: args.title,
        head: args.head, base: args.base,
        body: args.body,
      });
      return { content: [{ type: "text",
        text: `✅ PR #${data.number} created: ${data.html_url}` }] };
    }

    case "github_merge_pr": {
      const { data } = await octokit.pulls.merge({
        owner: args.owner, repo: args.repo,
        pull_number: args.pull_number,
        merge_method: args.merge_method || "merge",
      });
      return { content: [{ type: "text",
        text: `✅ PR merged: ${data.message}` }] };
    }

    case "github_get_repo": {
      const { data } = await octokit.repos.get({
        owner: args.owner, repo: args.repo,
      });
      return { content: [{ type: "text",
        text: `${data.full_name}\n⭐ ${data.stargazers_count}` +
             `\n🍴 ${data.forks_count}\n${data.description}` }] };
    }

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

Part 4:註冊到 Claude Code

Claude Code MCP Config

{
  "mcpServers": {
    "github": {
      "command": "node",
      "args": ["./github-mcp-server/index.js"],
      "env": {
        "GITHUB_TOKEN": "$GITHUB_TOKEN"
      }
    }
  }
}

實際使用:自然語言操控 GitHub


Part 5:進階擴展 — 由 8 個 Tool 到無限可能

掌握咗基礎之後,你可以繼續擴展更多功能:

GitHub API 有 500+ endpoints — 你嘅 MCP Server 只需要 expose 你真正需要用到嘅功能。關鍵係令 AI 可以用自然語言操控,而唔係每次都記 API endpoint 名。


總結:由開發者到 DevEx Automation Engineer

建立 GitHub MCP Server 唔單止係學 MCP 開發技巧,更加係一個思維轉變:由「人用 Git + GitHub UI」到「AI 自動化 GitHub 操作」。當你的 CI/CD pipeline 有咗 AI 直接操控 GitHub 嘅能力,成個開發流程就會大幅加速。

Written by Eric Cheung · Learn One AI Thing Every Day · Day 4: Build a GitHub MCP Server