Model Context Protocol (MCP) 是 Anthropic 推出的開放標準,讓 AI 助手能夠無縫連接外部工具與資料。但是,要怎麼從零開始實作一個 MCP Server呢?
在這篇文章中,將通過建立一個履歷評分器的實際案例,完整展示 MCP 的實作過程。我們會涵蓋從項目初始化、核心架構設計、到與 Claude Desktop 整合的每一個步驟。
什麼是 MCP?為什麼要學會實作它?
在開始之前,讓我們先理解 MCP 解決的核心問題:
傳統痛點: 當你在使用 Claude 或是像 ChatGPT 這類 LLM 工具時,多數情境下,LLM 能理解並評論一份履歷,但真正的痛點不在於做不做得到,而在於做不到標準化、可驗證、可重現:
流程會斷:得在對話、表單、內部工具間來回切換,狀態與上下文常遺失。
結果難重現:每次都靠人工貼資料+臨場指示,標準不一致。
輸出不好用:模型多回自然語言,不是可驗證的 JSON,難進報表或自動化。
另外,確實也有 LLM 做不到或不該直接做的事,例如:
直接連進你的資料庫(權限、稽核與安全控管)。
真正操作瀏覽器做登入、點擊、擷取資料等自動化。
MCP 的解決方案: 讓 AI 助手直接調用你開發的工具,在同一個對話流程中完成所有操作。
這就像是為 AI 助手開發「套件」,讓它能夠執行自訂功能。
而我選「履歷評分」當範例,是因為具有需要可驗證的標準(量表、權重、結構化輸出),可以展現出 MCP 的價值。
直接先看實際運作畫面:

從 Demo 畫面中可以看到,可以調用「履歷評分器」工具,並且得到結構化的 JSON 輸出,包含各項評分指標和改進建議。接下來就來開始實作吧!
第一步:項目初始化與依賴管理
讓我們從建立一個新的 MCP 項目開始:
# 建立專案目錄
mkdir mcp-resume-scorer
cd mcp-resume-scorer
# 初始化 npm 專案
npm init -y
# 安裝 MCP SDK 和其他依賴
npm install @modelcontextprotocol/sdk openai ajv ajv-formats
npm install -D @types/node tsx typescript
專案結構規劃
mcp-resume-scorer/
├── src/
│ ├── server.ts # MCP Server主程式
│ ├── utils/score.ts # 評分邏輯
│ ├── llm/chatgpt.ts # LLM 整合
│ ├── tools/ajv.ts # 驗證工具
│ ├── schema/ # JSON Schema
│ └── prompts/ # 提示詞
├── server.js # JavaScript 版本(生產用)
├── package.json
└── .gitignore # 排除敏感文件
我們同時提供 TypeScript 和 JavaScript 兩個版本,TypeScript 用於開發,JavaScript 用於生產部署。
重要設計原則:環境變數管理
在 MCP 架構中,有一個關鍵的設計原則:所有敏感資訊(如 API 金鑰)都應該由 Claude Desktop 管理,而不是存放在專案中。
為什麼這樣設計?
- 安全性:避免 API 金鑰意外提交到版本控制系統
- 統一管理:所有 MCP Server 的金鑰都在 Claude Desktop 中集中管理
- 環境隔離:開發、測試、生產環境可以使用不同的金鑰
- 符合最佳實踐:遵循「配置與程式碼分離」的原則
Claude Desktop 配置:
{
"mcpServers": {
"resume-scorer": {
"env": {
"OPENAI_API_KEY": "sk-..." // 在這裡設定
}
}
}
}
啟動 Claude Desktop 時會自動載入環境變數
這樣設計的好處是:
- 開發時:可以用
OPENAI_API_KEY=test npm run dev測試 - 生產時:完全由 Claude Desktop 管理,無需專案中存放金鑰
第二步:建立 MCP Server 核心
MCP Server 的核心是實作兩個關鍵功能:工具發現和工具調用。
基礎Server結構 (src/server.ts)
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types";
async function main() {
// 1. 建立 MCP Server實例
const server = new Server(
{
name: "resume-scorer-mcp", // Server名稱
version: "0.1.0" // 版本號
},
{
capabilities: {
tools: {} // 聲明支援工具功能
}
}
);
console.error("MCP Resume Scorer starting...");
// 2. 註冊工具列表處理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "resume_scorer",
description: "【履歷評分器】專業AI履歷分析工具",
inputSchema: {
type: "object",
required: ["resume_text"],
properties: {
resume_text: {
type: "string",
description: "履歷內容文字"
}
}
}
}
]
};
});
// 3. 註冊工具調用處理器
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "resume_scorer") {
// 這裡會調用實際的評分邏輯
const result = await scoreResume(request.params.arguments || {});
return {
content: [{ type: 'text', text: JSON.stringify(result) }]
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
// 4. 建立 Stdio 傳輸層並連接
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Resume Scorer connected and ready!");
}
main().catch(console.error);
關鍵概念解析
1. Server 建構函數
name和version:用於識別你的 MCP Servercapabilities.tools:告訴客戶端這個Server支援工具功能
2. ListToolsRequestSchema 處理器
- 當 Claude Desktop 連接時,會發送此請求來發現可用工具
- 必須回傳工具列表,包含名稱、描述和輸入 Schema
3. CallToolRequestSchema 處理器
- 當使用者觸發工具時,會發送此請求
request.params.name是工具名稱request.params.arguments是輸入參數
4. StdioServerTransport
- MCP 使用標準輸入/輸出進行通訊
- 這讓 Claude Desktop 能夠啟動並與Server通訊
第三步:實作 OpenAI API 整合
接下來我們建立與 OpenAI 的連接模組:
LLM 整合 (src/llm/chatgpt.ts)
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
export async function chatJson({
system,
user,
model = "gpt-4o-mini"
}: {
system: string;
user: string;
model?: string
}) {
const res = await client.chat.completions.create({
model,
messages: [
{ role: "system", content: system },
{ role: "user", content: user }
],
response_format: { type: "json_object" }, // 強制 JSON 輸出
temperature: 0.2 // 降低隨機性
});
const msg = res.choices[0].message?.content ?? "{}";
return {
json: JSON.parse(msg),
usage: res.usage,
model: res.model
};
}
重要設計決策
1. 強制 JSON 輸出
response_format: { type: "json_object" }
這確保 AI 回應必定是有效的 JSON,避免解析錯誤。
2. 低 Temperature 設定
temperature: 0.2
對於評分任務,我們需要一致性高於創意性。
3. 錯誤處理
const msg = res.choices[0].message?.content ?? "{}";
確保即使 API 回應異常,也有預設值可用。
第四步:實作業務邏輯 - 履歷評分器
現在我們來實作這個 MCP Server的核心功能:履歷評分。
JSON Schema 定義 (src/schema/resume_score.schema.json)
首先定義評分結果的資料結構:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"overall_score",
"skill_completeness",
"experience_quality",
"achievement_showcase",
"career_progression",
"resume_structure",
"summary",
"recommendations"
],
"properties": {
"overall_score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "綜合評分 (0-100)"
},
"skill_completeness": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "技能完整性評分"
},
"experience_quality": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "經驗描述品質評分"
},
"achievement_showcase": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "成果展示評分"
},
"career_progression": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "職涯發展評分"
},
"resume_structure": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "履歷結構評分"
},
"summary": {
"type": "string",
"description": "評分總結"
},
"recommendations": {
"type": "array",
"items": {
"type": "string"
},
"description": "改進建議"
}
}
}
AI 提示詞設計 (src/prompts/system.txt)
建立專業的評分提示詞:
你是一個專業的履歷分析專家。你的任務是對履歷進行全方位的結構化分析。
請根據以下評分維度來評估履歷品質:
1. **技能完整性 (skill_completeness)**: 技能列表是否完整、相關且有說服力
2. **經驗描述品質 (experience_quality)**: 工作經驗描述是否詳細、具體且有價值
3. **成果展示 (achievement_showcase)**: 是否有具體數據、成就和影響力展示
4. **職涯發展 (career_progression)**: 職涯軌跡是否合理、有成長性
5. **履歷結構 (resume_structure)**: 格式、組織、表達是否清晰專業
評分標準:
- 0-30分:需要大幅改進
- 31-50分:有明顯不足
- 51-70分:基本合格
- 71-85分:品質良好
- 86-100分:優秀出色
如果同時提供職缺描述,則額外分析匹配度。
請提供具體的評分理由和針對性的改進建議。
輸出必須是有效的 JSON 格式,嚴格遵循提供的 Schema。
JSON Schema 驗證工具 (src/tools/ajv.ts)
建立驗證工具:
import Ajv from "ajv";
import addFormats from "ajv-formats";
export function makeAjv() {
const ajv = new Ajv({
allErrors: true, // 顯示所有錯誤
strict: false // 允許額外屬性
});
addFormats(ajv); // 添加格式驗證(日期、email 等)
return ajv;
}
核心評分邏輯 (src/utils/score.ts)
現在實作完整的評分工具:
import { Tool } from "@modelcontextprotocol/sdk/types";
import { chatJson } from "../llm/chatgpt";
import { makeAjv } from "../tools/ajv";
import schema from "../schema/resume_score.schema.json";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
// 取得當前文件路徑(ES modules 需要)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 定義權重類型
type Weights = {
skill_completeness: number;
experience_quality: number;
achievement_showcase: number;
career_progression: number;
resume_structure: number;
};
export const scoreResumeTool: Tool<{
resume_text: string;
job_text?: string;
weights?: Weights;
model?: string;
}> = {
name: "resume_scorer",
description: "【履歷評分器】專業 AI 履歷分析工具,提供 5 大維度評分和改進建議",
// 定義輸入參數 Schema
inputSchema: {
type: "object",
required: ["resume_text"],
properties: {
resume_text: {
type: "string",
description: "履歷內容文字"
},
job_text: {
type: "string",
description: "職缺描述(選填,用於匹配度分析)"
},
weights: {
type: "object",
description: "評分權重設定(選填)"
},
model: {
type: "string",
description: "使用的 AI 模型(選填)"
}
}
},
// 實作評分邏輯
async *invoke({ resume_text, job_text, weights, model }) {
try {
// 1. 設定預設權重
const defaultWeights: Weights = {
skill_completeness: 0.25, // 技能完整性 25%
experience_quality: 0.25, // 經驗品質 25%
achievement_showcase: 0.20, // 成果展示 20%
career_progression: 0.15, // 職涯發展 15%
resume_structure: 0.15 // 履歷結構 15%
};
const w = { ...defaultWeights, ...weights };
// 2. 讀取系統提示詞
const systemPromptPath = path.join(__dirname, "../prompts/system.txt");
const system = fs.readFileSync(systemPromptPath, "utf8");
// 3. 組合使用者輸入
const user = job_text ? [
"【履歷內容】", resume_text,
"【職缺描述】", job_text,
"【評分權重】", JSON.stringify(w),
"【Schema】", JSON.stringify(schema, null, 2),
"【產出要求】只輸出 JSON,符合 Schema。進行履歷分析並額外評估與職缺的匹配度。"
].join("\n\n") : [
"【履歷內容】", resume_text,
"【評分權重】", JSON.stringify(w),
"【Schema】", JSON.stringify(schema, null, 2),
"【產出要求】只輸出 JSON,符合 Schema。專注於履歷本身的品質分析。"
].join("\n\n");
// 4. 調用 AI 進行評分
const { json, usage, model: usedModel } = await chatJson({
system,
user,
model
});
// 5. 驗證輸出格式
const ajv = makeAjv();
const validate = ajv.compile(schema as any);
const ok = validate(json);
if (!ok) {
return {
error: "Schema 驗證失敗",
ajvErrors: validate.errors,
raw: json,
success: false
};
}
// 6. 回傳成功結果
return {
...json,
tokens: usage,
model: usedModel,
version: "0.1.0",
success: true
};
} catch (error) {
console.error("Resume scorer error:", error);
return {
error: error instanceof Error ? error.message : "未知錯誤",
success: false,
timestamp: new Date().toISOString()
};
}
}
};
關鍵實作細節解析
1. Generator 函數使用
async *invoke({ ... }) {
MCP Tool 使用 Generator 函數,這允許未來支援串流輸出。
2. 權重系統設計
const defaultWeights: Weights = {
skill_completeness: 0.25, // 技能占比最高
experience_quality: 0.25, // 經驗同等重要
achievement_showcase: 0.20, // 成果展示次之
career_progression: 0.15, // 職涯發展
resume_structure: 0.15 // 結構清晰度
};
這個權重分配反映了履歷評估的優先順序。
3. 條件性提示詞組合
const user = job_text ? [
// 有職缺描述時的提示詞
] : [
// 只有履歷時的提示詞
];
根據是否提供職缺描述,動態調整 AI 的分析重點。
4. 多層錯誤處理
- API 調用錯誤
- JSON 解析錯誤
- Schema 驗證錯誤
- 未知錯誤
第五步:建立生產環境版本
為了提高相容性和啟動速度,我們建立一個 JavaScript 版本用於生產環境。
生產環境Server (server.js)
這是一個完整的 JavaScript 版本,將所有模組整合在一個文件中:
const { Server } = require("@modelcontextprotocol/sdk/dist/cjs/server/index");
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/dist/cjs/server/stdio");
const fs = require("fs");
const path = require("path");
const OpenAI = require("openai");
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 內嵌 JSON Schema(避免文件路徑問題)
const schema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["overall_score", "skill_completeness", "experience_quality",
"achievement_showcase", "career_progression", "resume_structure",
"summary", "recommendations"],
"properties": {
"overall_score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "綜合評分 (0-100)"
},
// ... 其他屬性
}
};
// OpenAI API 整合
async function chatJson({ system, user, model = "gpt-4o-mini" }) {
const res = await client.chat.completions.create({
model,
messages: [
{ role: "system", content: system },
{ role: "user", content: user }
],
response_format: { type: "json_object" },
temperature: 0.2
});
const msg = (res.choices[0].message && res.choices[0].message.content) || "{}";
return { json: JSON.parse(msg), usage: res.usage, model: res.model };
}
// 履歷評分函數
async function scoreResume({ resume_text, job_text, weights, model }) {
try {
const defaultWeights = {
skill_completeness: 0.25,
experience_quality: 0.25,
achievement_showcase: 0.20,
career_progression: 0.15,
resume_structure: 0.15
};
const w = Object.assign({}, defaultWeights, weights || {});
// 讀取系統提示詞
const systemPromptPath = path.join(__dirname, "src", "prompts", "system.txt");
const system = fs.readFileSync(systemPromptPath, "utf8");
// 組合提示詞
const user = job_text ? [
"【履歷內容】", resume_text,
"【職缺描述】", job_text,
"【評分權重】", JSON.stringify(w),
"【Schema】", JSON.stringify(schema, null, 2),
"【產出要求】只輸出 JSON,符合 Schema。進行履歷分析並額外評估與職缺的匹配度。"
].join("\n\n") : [
"【履歷內容】", resume_text,
"【評分權重】", JSON.stringify(w),
"【Schema】", JSON.stringify(schema, null, 2),
"【產出要求】只輸出 JSON,符合 Schema。專注於履歷本身的品質分析。"
].join("\n\n");
const { json, usage, model: usedModel } = await chatJson({ system, user, model });
return Object.assign({}, json, {
tokens: usage,
model: usedModel,
version: "0.1.0",
success: true
});
} catch (error) {
console.error("Resume scorer error:", error);
return {
error: error instanceof Error ? error.message : "未知錯誤",
success: false,
timestamp: new Date().toISOString()
};
}
}
// 主程式
async function main() {
const server = new Server({
name: "resume-scorer-mcp",
version: "0.1.0"
}, {
capabilities: { tools: {} }
});
console.error("MCP Resume Scorer starting...");
const { ListToolsRequestSchema, CallToolRequestSchema } =
require("@modelcontextprotocol/sdk/dist/cjs/types");
// 工具發現
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "resume_scorer",
description: "【履歷評分器】專業AI履歷分析工具,提供5大維度評分和改進建議",
inputSchema: {
type: "object",
required: ["resume_text"],
properties: {
resume_text: { type: "string", description: "履歷內容文字" },
job_text: { type: "string", description: "職缺描述(選填)" },
weights: { type: "object", description: "評分權重設定(選填)" },
model: { type: "string", description: "使用的AI模型(選填)" }
}
}
}]
}));
// 工具調用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "resume_scorer") {
const result = await scoreResume(request.params.arguments || {});
return {
content: [{ type: 'text', text: JSON.stringify(result) }]
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
// 連接傳輸層
const transport = new StdioServerTransport();
console.error("Connecting to transport...");
await server.connect(transport);
console.error("MCP Resume Scorer connected and ready!");
// 優雅關閉
process.on('SIGINT', async () => {
console.error("Shutting down server...");
await server.close();
process.exit(0);
});
}
main().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
package.json 腳本設定
{
"name": "mcp-resume-scorer",
"version": "0.1.0",
"description": "MCP server for scoring resumes against job descriptions using AI",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "npx tsx watch src/server.ts",
"start:ts": "npx tsx src/server.ts",
"build": "echo 'Using JavaScript server.js - no build needed' && exit 0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.20.2",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"openai": "^6.7.0"
},
"devDependencies": {
"@types/node": "^24.10.0",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
}
第六步:與 Claude Desktop 整合
Claude Desktop 配置
macOS 配置位置:
~/Library/Application\ Support/Claude/claude_desktop_config.json
Windows 配置位置:
%APPDATA%\Claude\claude_desktop_config.json
正確的配置內容:
{
"mcpServers": {
"resume-scorer": {
"command": "node",
"args": ["/absolute/path/to/your/mcp-resume-scorer/server.js"],
"env": {
"OPENAI_API_KEY": "your_openai_api_key_here"
}
}
}
}
實際在 Claude Desktop 中應該長這樣:

測試與驗證
1. 重啟 Claude Desktop 配置完成後,重啟 Claude Desktop 應用程式。
2. 觸發履歷評分器 在對話中使用關鍵字觸發工具:
請使用履歷評分器分析這份履歷:
我是一名有5年經驗的全端工程師,專精於 React、Node.js 和 PostgreSQL。
在上一份工作中,我主導開發了一個電商平台,成功提升轉換率 35%。
具有敏捷開發經驗,曾帶領 3 人團隊完成多個專案。
請提供詳細的技能完整性、經驗品質、成果展示、履歷結構、職涯發展評分。
3. 預期輸出格式
{
"overall_score": 82,
"skill_completeness": 85,
"experience_quality": 88,
"achievement_showcase": 80,
"career_progression": 75,
"resume_structure": 82,
"summary": "這份履歷展現了優秀的全端開發能力...",
"recommendations": [
"建議增加具體的技術架構描述",
"可以補充更多量化的業務成果"
],
"tokens": { "prompt_tokens": 245, "completion_tokens": 156 },
"model": "gpt-4o-mini",
"version": "0.1.0",
"success": true
}
嘗試用另一份履歷來測試,可以看到會輸出相同格式的評分結果。

總結
通過這個完整的 MCP 履歷評分器專案,我們可以學到:
1. MCP 協議理解
2. 架構設計原則
3. AI 整合最佳實踐
而這個 MCP 履歷評分器不只是一個技術展示,它實際解決了:
- 標準化評分:消除主觀偏見
- 效率提升:自動化重複性工作
- 一致性保證:每次評分都遵循相同標準
MCP 為 AI 工具的整合提供了標準化的解決方案,讓我們能夠建立更加智慧和高效的工作流程。這個履歷評分器只是開始——想像一下,當你所有專業工具都能夠在同一個 AI 對話中無縫協作,應該會是一個很棒的開發體驗。
完整專案可參考 Repo