Build a GitHub MCP Server 實戰 — 自動化管理 Issue / PR / Repo
2026-05-12 · MCP 實戰 · Eric Cheung
前言:AI 直接操控 GitHub 嘅威力
快速結論(TL;DR)
- 呢個 GitHub MCP Server 用 @modelcontextprotocol/sdk 加 Octokit 建立,支援 8 個 Tools:Issues(create/read/list/update)、PR(create/read/merge)同 Repo(get)。
- GitHub Token 要揀 Fine-grained tokens,設定 Issues 同 Pull requests 嘅 Read/Write 權限,再存入 .env。
- 用 StdioServerTransport 起 server,透過 ListToolsRequestSchema 同 CallToolRequestSchema 處理工具清單同呼叫。
- 註冊到 Claude Code 只需喺 .claude 設定 mcpServers 指向 server 檔案,之後就可以用自然語言操控 GitHub。
- GitHub API 有 500+ endpoints,你可以繼續擴展 search code、create release、manage actions 等工具,由開發者變成 DevEx Automation Engineer。
想像下:對住 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
- ① 上 GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- ② Generate new token → 設定 repo 權限:Issues (Read/Write)、Pull requests (Read/Write)、Metadata (Read)
- ③ 複製 token → 存入 .env:GITHUB_TOKEN=ghp_xxxxxxxxxxxx
完整 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
- "幫我 create 一個 issue: Fix login page responsive layout, label: bug, assign: @eric" → AI 自動 call github_create_issue
- "Show me all open PRs in ericcheung621-ckw/eric-cheung-portfolio" → AI 自動 call github_list_prs
- "Merge PR #42 with squash" → AI 自動 call github_merge_pr
- "What's the current status of my main repo?" → AI 自動 call github_get_repo
Part 5:進階擴展 — 由 8 個 Tool 到無限可能
掌握咗基礎之後,你可以繼續擴展更多功能:
- github_search_code — 用 GitHub Code Search API 搜尋全 repo code
- github_create_release — 自動 generate release notes + create release
- github_get_diff — 比較兩個 commit/branch 嘅 diff
- github_create_webhook — 管理 repo webhooks
- github_manage_actions — Trigger / cancel / list CI runs
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