你會做出什麼
這篇教學結束後,你會有一個能運作的 MCP server,可以即時查詢 GitHub 使用者資料。Claude Code 會直接從終端呼叫它。你輸入「查一下 torvalds 的 GitHub 資料」,Claude Code 就會調用你的 server、打 GitHub API、回傳結構化的結果。
MCP (Model Context Protocol) 是一個開放標準,讓 AI agent 透過統一介面存取外部工具和資料。你不需要把 API 呼叫寫死在 prompt 裡,而是建一個 server 來公開 tool。任何支援 MCP 的 client(Claude Code、Claude Desktop、Cursor 等)都能自動發現並呼叫這些 tool。
這篇教學做的是真的東西。不是 hello-world echo server,是一個會打真實 API 回傳有用資料的 tool。
你需要:
- Node.js 18+ 和 npm
- 已安裝並驗證的 Claude Code
- 一個終端
- 20 分鐘
為什麼要自己建 MCP Server?
你可能已經在用 MCP server 了,只是沒意識到。Claude Code 內建的檔案讀取、網頁搜尋、bash 執行,底層都是 MCP tool。
自己建 server 代表你可以讓 Claude Code 存取任何東西:公司內部 API、資料庫、監控儀表板、部署管線。Protocol 處理發現、schema 驗證和傳輸。你只寫邏輯。
三個理由讓你選擇自己建:
- 自訂資料來源。 把 Claude Code 接上還沒有現成 MCP server 的 API。
- 控制存取。 你的 server 決定公開哪些資料、怎麼格式化。邊界你來定。
- 重複使用。 一個 server 在所有 MCP client 上都能用。建一次,到處用。
想了解 MCP 在整個 AI CLI 工具生態裡的位置,那篇指南有完整的全景。
第一步:建立專案
建一個新目錄並初始化:
mkdir github-mcp-server
cd github-mcp-server
npm init -y
安裝 MCP SDK 和 Zod(用來做 schema 驗證):
npm install @modelcontextprotocol/sdk zod@3
npm install -D @types/node typescript
更新 package.json,啟用 ES modules 並加入 build script:
{
"type": "module",
"bin": {
"github-mcp": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
建立 tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
建立原始碼目錄:
mkdir src
專案結構:
github-mcp-server/
├── package.json
├── tsconfig.json
└── src/
└── index.ts # 接下來要寫的檔案
第二步:撰寫 Server
建立 src/index.ts。這是整個 server。每一行都有行內說明。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 建立 MCP server 實例。
// name 和 version 會在 client 探索 server 時顯示。
const server = new McpServer({
name: "github-lookup",
version: "1.0.0",
});
// 定義 GitHub API 回應的 TypeScript 介面。
// 只取我們需要的欄位。
interface GitHubUser {
login: string;
name: string | null;
bio: string | null;
public_repos: number;
followers: number;
following: number;
html_url: string;
created_at: string;
}
// 註冊一個叫 "get_github_user" 的 tool。
// 第一個參數是 tool 名稱。
// 第二個參數是 metadata:description + input schema。
// 第三個參數是 handler function。
server.registerTool(
"get_github_user",
{
description: "Look up a GitHub user profile by username",
inputSchema: {
username: z
.string()
.min(1)
.describe("GitHub username (e.g. torvalds, sindresorhus)"),
},
},
async ({ username }) => {
const url = `https://api.github.com/users/${encodeURIComponent(username)}`;
try {
const response = await fetch(url, {
headers: {
"User-Agent": "github-mcp-server/1.0",
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
return {
content: [
{
type: "text" as const,
text: `GitHub API returned ${response.status}: user "${username}" not found or rate-limited.`,
},
],
};
}
const user = (await response.json()) as GitHubUser;
const summary = [
`**${user.name || user.login}** (@${user.login})`,
user.bio ? `Bio: ${user.bio}` : null,
`Public repos: ${user.public_repos}`,
`Followers: ${user.followers} | Following: ${user.following}`,
`Profile: ${user.html_url}`,
`Account created: ${user.created_at.split("T")[0]}`,
]
.filter(Boolean)
.join("\n");
return {
content: [{ type: "text" as const, text: summary }],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Failed to reach GitHub API: ${error instanceof Error ? error.message : "unknown error"}`,
},
],
};
}
},
);
// 把 server 接上 stdio transport。
// Claude Code 透過 stdin/stdout 跟 MCP server 溝通。
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("GitHub MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
重點說明:
server.registerTool接收名稱、設定物件(含description和inputSchema)、以及一個 async handler。inputSchema直接使用 Zod 物件。SDK 會在 handler 執行前自動驗證輸入。- 回傳格式一律是
{ content: [{ type: "text", text: "..." }] }。這是 MCP 標準的回應結構。 - 用
console.error而非console.log。基於 stdio 的 MCP server 用 stdout 傳送 JSON-RPC 訊息。任何console.log都會破壞 protocol。除錯訊息一律用console.error。
第三步:Build 並在本機測試
編譯 TypeScript:
npm run build
你應該會看到 build/index.js 檔案。測試它能不能正常啟動:
node build/index.js
Process 會停住(等待 stdin 輸入)。這是正確的。按 Ctrl+C 停止。
在接上 Claude Code 之前,用 MCP Inspector 做互動式測試:
npx @modelcontextprotocol/inspector node build/index.js
這會在 http://localhost:6274 開一個 web UI。你可以選 get_github_user tool、輸入使用者名稱、看原始回應。Inspector 是在整合到任何 client 之前驗證 tool 運作最快的方法。
第四步:接上 Claude Code
Claude Code 從專案根目錄的 .mcp.json 讀取 MCP server 設定。在你的工作專案(不是 MCP server 目錄裡,而是你用 Claude Code 的那個專案)建立這個檔案:
{
"mcpServers": {
"github-lookup": {
"command": "node",
"args": ["/absolute/path/to/github-mcp-server/build/index.js"]
}
}
}
把 /absolute/path/to/ 換成實際路徑。在 github-mcp-server 目錄裡執行 pwd 就能拿到。
或者用 Claude Code CLI 加入,讓它在所有專案都可用:
claude mcp add github-lookup --scope user -- node /absolute/path/to/github-mcp-server/build/index.js
加完設定後重啟 Claude Code。在 Claude Code 裡執行 /mcp 確認 server 已連線。你應該會看到 github-lookup 顯示綠色狀態。
第五步:實際使用
在設定好 server 的任何專案裡打開 Claude Code,輸入:
查一下 torvalds 的 GitHub 資料
Claude Code 會:
- 辨識
get_github_user是適合這個請求的 tool。 - 詢問你是否同意呼叫(只有第一次,除非你啟用了自動核准)。
- 把
{"username": "torvalds"}傳給你的 server。 - 顯示格式化後的結果。
再試幾個:
sindresorhus 的 GitHub 帳號有什麼專案?
比較 gaearon 和 dan-abramov 的 GitHub 資料
Claude Code 讀取 tool 的 description 和 input schema 來決定何時、如何呼叫。你不需要寫任何 prompt engineering。MCP protocol 處理了所有接線。
疑難排解
/mcp 輸出裡看不到 server。
.mcp.json 裡的路徑不對。用絕對路徑。手動執行 node /your/path/build/index.js 確認它能啟動。
Server 啟動了但 tool 沒出現。
重新執行 npm run build。Claude Code 跑的是編譯後的 JavaScript,不是 TypeScript 原始碼。改了 src/index.ts 就必須重新 build。
GitHub 回傳 "Rate limit exceeded"。
GitHub API 未驗證的請求每小時限 60 次。要更高額度,加一個 GITHUB_TOKEN 環境變數,在 fetch headers 裡用 Bearer token。你可以在 .mcp.json 裡傳環境變數給 MCP server:
{
"mcpServers": {
"github-lookup": {
"command": "node",
"args": ["/absolute/path/to/github-mcp-server/build/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
Server 無聲地 crash。
檢查 stderr 輸出。手動跑 server:node build/index.js 2>&1,看有沒有錯誤訊息。常見原因:缺少 node_modules(跑 npm install)、Node.js 版本太舊(需要 18+)、build 輸出有語法錯誤。
Claude Code 出現 JSON parse 錯誤。
你的 server 程式碼裡有 console.log。把每個 console.log 都換成 console.error。Stdout 是 MCP protocol 訊息專用的。
開發 MCP Server 的多面板工作流
在開發 MCP server 的時候,你同時要處理三件事:編輯 src/index.ts、執行 npm run build 並重啟 server、用 Claude Code 或 MCP Inspector 測試。用 Ctrl+Tab 或 Alt+Tab 在這些之間切換很慢,而且會打斷專注。
有效率的做法是三個終端面板同時可見。左邊放編輯器,右上放 build 和 server log,右下放 Claude Code 或 Inspector。存檔、看 build 輸出、測試 tool。不切視窗。不丟失脈絡。
下一步
你已經有一個能運作的 MCP server。接下來可以往這些方向走。
加更多 tool。 再呼叫一次 server.registerTool,給不同的名稱和 schema。一個 server 可以公開數十個 tool。考慮加入 get_github_repo(儲存庫詳情)、list_github_repos(列出使用者的儲存庫)、或 search_github_code(跨儲存庫搜尋程式碼)。
加 resource。 MCP server 還能公開 resource(唯讀資料,像檔案或 API 回應)和 prompt(預先寫好的範本)。SDK 三種都支援。詳情請參考 MCP 規格文件。
發布分享。 如果你的 server 對別人有用,發到 npm 上。寫清楚 README,附上 .mcp.json 設定片段。MCP 生態正在快速成長,最好的貢獻方式就是為還沒有 server 的 API 建一個。
探索生態系。 到 MCP servers 儲存庫瀏覽現有的 server 找靈感。如果你正在建構自己的 AI CLI 工作流,MCP server 就是讓 agent 真正好用的可組合元件。
更深入的 Claude Code 設定模式,包括 CLAUDE.md 常見錯誤和進階 agent skill,那些指南從這篇教學結束的地方接續。
Ready to streamline your terminal workflow?
Multi-terminal drag-and-drop layout, workspace Git sync, built-in AI integration, AST code analysis — all in one app.