
1. 引言AI助手擴展能力的痛點與解決方案在AI助手日益普及的今天Claude和ChatGPT已經成為開發者日常工作中不可或缺的智能伙伴。然而許多開發者在使用過程中發現這些AI助手雖然功能強大但在特定領域的專業能力仍有局限。比如需要查詢實時數據、調用內部API、或者處理特定格式的文件時往往需要頻繁切換工具效率大打折扣。這正是MCPModel Context Protocol協議要解決的核心問題。MCP允許開發者創建自定義服務器為AI助手擴展專屬能力讓Claude和ChatGPT能夠直接調用外部工具和服務。本文將完整演示如何從零開始構建MCP服務器并成功集成到Claude Desktop和ChatGPT中實現真正的個性化AI助手。無論你是想要為團隊內部工具添加AI支持還是希望讓AI助手具備處理特定業務數據的能力本文提供的完整實戰方案都能直接復用。我們將從基礎概念講起逐步深入到代碼實現、部署配置和實際應用場景。2. MCP協議核心概念解析2.1 什么是MCP協議MCPModel Context Protocol是一個開放協議旨在標準化AI模型與外部工具和服務之間的交互方式。可以將其理解為AI領域的驅動程序標準——就像打印機需要驅動程序才能與電腦通信一樣MCP服務器就是AI助手與外部世界通信的驅動。MCP協議的核心價值在于解耦AI模型與具體工具的實現。通過統一的協議規范開發者可以編寫一次MCP服務器就能讓所有支持該協議的AI模型如Claude、ChatGPT等使用這些工具能力。2.2 MCP協議的核心組件一個完整的MCP生態系統包含三個關鍵組件MCP客戶端即AI助手本身如Claude Desktop或ChatGPT。客戶端負責發起工具調用請求并處理服務器返回的結果。MCP服務器開發者編寫的自定義服務封裝了特定的工具能力。服務器接收客戶端的請求執行相應的操作并返回結果。傳輸層客戶端與服務器之間的通信通道支持stdio標準輸入輸出和HTTP兩種方式。2.3 MCP與傳統插件架構的區別與傳統插件架構相比MCP具有幾個顯著優勢語言無關性MCP服務器可以用任何編程語言編寫只要遵循協議規范即可。進程隔離MCP服務器運行在獨立的進程中即使服務器崩潰也不會影響AI助手主程序。標準化接口統一的協議規范意味著更好的兼容性和可維護性。安全可控每個工具都需要顯式授權用戶對AI助手的權限有完全的控制權。3. 環境準備與開發工具選擇3.1 基礎環境要求在開始MCP服務器開發前需要確保本地環境滿足以下要求操作系統Windows 10/11、macOS 10.15 或 Linux Ubuntu 18.04Node.js版本18.0.0或更高推薦使用LTS版本Python版本3.8或更高可選用于某些特定的工具實現Git用于版本控制和示例代碼下載3.2 開發工具推薦代碼編輯器Visual Studio Code推薦或WebStormMCP SDK使用官方提供的TypeScript/JavaScript SDK調試工具VS Code內置調試器、Chrome DevToolsAPI測試工具Postman或curl用于HTTP傳輸層測試3.3 Claude Desktop安裝與配置由于Claude Desktop是目前對MCP支持最完善的客戶端我們以其為例進行演示訪問Anthropic官網下載Claude Desktop安裝完成后啟動程序在設置中啟用開發者模式確認MCP配置目錄位置通常位于用戶主目錄下的.claude/mcp-servers3.4 項目結構規劃在開始編碼前我們先規劃標準的MCP項目結構my-mcp-server/ ├── src/ │ ├── tools/ # 工具實現 │ ├── resources/ # 資源管理 │ ├── types/ # 類型定義 │ └── index.ts # 入口文件 ├── package.json # 項目配置 ├── tsconfig.json # TypeScript配置 ├── claude.json # Claude配置文件 └── README.md # 項目說明4. 創建第一個MCP服務器天氣查詢示例4.1 初始化項目首先創建項目目錄并初始化npm包# 創建項目目錄 mkdir weather-mcp-server cd weather-mcp-server # 初始化npm項目 npm init -y # 安裝MCP SDK和TypeScript npm install modelcontextprotocol/sdk typescript types/node ts-node # 創建TypeScript配置 npx tsc --init修改tsconfig.json文件確保包含以下關鍵配置{ compilerOptions: { target: ES2020, module: CommonJS, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules, dist] }4.2 定義工具接口創建src/types/weather.ts文件定義天氣查詢的數據結構export interface WeatherRequest { city: string; country?: string; units?: metric | imperial; } export interface WeatherResponse { city: string; country: string; temperature: number; description: string; humidity: number; windSpeed: number; timestamp: string; } export interface WeatherError { error: string; message: string; }4.3 實現天氣查詢工具創建src/tools/weatherTool.ts文件實現核心的天氣查詢邏輯import { Tool } from modelcontextprotocol/sdk; import { WeatherRequest, WeatherResponse, WeatherError } from ../types/weather; export class WeatherTool { private readonly apiKey: string; private readonly baseUrl: string http://api.openweathermap.org/data/2.5; constructor(apiKey: string) { this.apiKey apiKey; } // 定義工具元數據 getToolDefinition(): Tool { return { name: get_weather, description: 獲取指定城市的當前天氣信息, inputSchema: { type: object, properties: { city: { type: string, description: 城市名稱英文或拼音 }, country: { type: string, description: 國家代碼可選如CN、US }, units: { type: string, enum: [metric, imperial], description: 溫度單位metric為攝氏度imperial為華氏度 } }, required: [city] } }; } // 執行天氣查詢 async execute(input: WeatherRequest): PromiseWeatherResponse | WeatherError { try { const query input.country ? ${input.city},${input.country} : input.city; const response await fetch( ${this.baseUrl}/weather?q${encodeURIComponent(query)}units${input.units || metric}appid${this.apiKey} ); if (!response.ok) { return { error: API_ERROR, message: 天氣API請求失敗: ${response.statusText} }; } const data await response.json(); return { city: data.name, country: data.sys.country, temperature: Math.round(data.main.temp), description: data.weather[0].description, humidity: data.main.humidity, windSpeed: data.wind.speed, timestamp: new Date().toISOString() }; } catch (error) { return { error: NETWORK_ERROR, message: 網絡請求失敗: ${error instanceof Error ? error.message : 未知錯誤} }; } } }4.4 創建MCP服務器主程序創建src/index.ts文件實現MCP服務器的主邏輯import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequest, ListToolsRequest, ListToolsRequestSchema, CallToolRequestSchema, } from modelcontextprotocol/sdk/types.js; import { WeatherTool } from ./tools/weatherTool.js; class WeatherMCPServer { private server: Server; private weatherTool: WeatherTool; constructor() { this.server new Server( { name: weather-mcp-server, version: 1.0.0, }, { capabilities: { tools: {}, }, } ); // 初始化天氣工具在實際使用中應從環境變量獲取API密鑰 this.weatherTool new WeatherTool(process.env.WEATHER_API_KEY || your-api-key-here); this.setupToolHandlers(); this.setupErrorHandlers(); } private setupToolHandlers() { // 處理工具列表請求 this.server.setRequestHandler(ListToolsRequestSchema, async (): Promiseany { return { tools: [this.weatherTool.getToolDefinition()], }; }); // 處理工具調用請求 this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest): Promiseany { if (request.params.name get_weather) { const result await this.weatherTool.execute(request.params.arguments as any); return { content: [ { type: text, text: JSON.stringify(result, null, 2), }, ], }; } throw new Error(未知的工具: ${request.params.name}); }); } private setupErrorHandlers() { this.server.onerror (error) { console.error(服務器錯誤:, error); }; process.on(SIGINT, async () { await this.server.close(); process.exit(0); }); } async run() { const transport new StdioServerTransport(); await this.server.connect(transport); console.error(Weather MCP服務器已啟動正在等待連接...); } } // 啟動服務器 const server new WeatherMCPServer(); server.run().catch(console.error);4.5 配置Claude Desktop集成創建claude.json配置文件告訴Claude如何連接我們的MCP服務器{ mcpServers: { weather-server: { command: node, args: [ /absolute/path/to/your/weather-mcp-server/dist/index.js ], env: { WEATHER_API_KEY: your-actual-api-key } } } }4.6 編譯和測試添加構建腳本到package.json{ scripts: { build: tsc, start: node dist/index.js, dev: ts-node src/index.ts } }執行構建和測試# 編譯TypeScript代碼 npm run build # 測試服務器 npm start5. 高級MCP服務器功能實現5.1 多工具集成服務器在實際項目中我們通常需要集成多個相關工具。下面演示如何創建一個包含多個功能的MCP服務器創建src/tools/index.ts整合多個工具import { Tool } from modelcontextprotocol/sdk; import { WeatherTool } from ./weatherTool; import { TimeZoneTool } from ./timezoneTool; import { CurrencyTool } from ./currencyTool; export class MultiToolServer { private tools: Mapstring, any new Map(); constructor() { this.tools.set(weather, new WeatherTool(process.env.WEATHER_API_KEY!)); this.tools.set(timezone, new TimeZoneTool()); this.tools.set(currency, new CurrencyTool(process.env.CURRENCY_API_KEY!)); } getToolDefinitions(): Tool[] { return Array.from(this.tools.values()).map(tool tool.getToolDefinition() ); } async executeTool(name: string, input: any): Promiseany { const tool this.tools.get(name); if (!tool) { throw new Error(工具未找到: ${name}); } return await tool.execute(input); } getAvailableTools(): string[] { return Array.from(this.tools.keys()); } }5.2 資源管理功能MCP協議不僅支持工具調用還支持資源管理。下面實現一個文件資源管理器創建src/resources/fileResource.tsimport { ResourceTemplate } from modelcontextprotocol/sdk; import { readFile, writeFile, readdir, stat } from fs/promises; import { join } from path; export class FileResourceManager { getResourceTemplates(): ResourceTemplate[] { return [ { uri: file:///{path}, name: 文件資源, description: 訪問本地文件系統, mimeType: text/plain, schema: { type: object, properties: { path: { type: string, description: 文件路徑 } }, required: [path] } } ]; } async readFileResource(uri: string): Promisestring { const path uri.replace(file:///, ); return await readFile(path, utf-8); } async listDirectory(path: string): Promiseany[] { const files await readdir(path); const result []; for (const file of files) { const filePath join(path, file); const stats await stat(filePath); result.push({ name: file, path: filePath, type: stats.isDirectory() ? directory : file, size: stats.size, modified: stats.mtime }); } return result; } }5.3 錯誤處理與日志記錄健壯的MCP服務器需要完善的錯誤處理機制創建src/utils/logger.tsexport class Logger { private readonly logLevel: string; constructor(level: string info) { this.logLevel level; } error(message: string, error?: any) { console.error([ERROR] ${message}, error || ); } warn(message: string) { if (this.logLevel error) return; console.warn([WARN] ${message}); } info(message: string) { if (this.logLevel error || this.logLevel warn) return; console.log([INFO] ${message}); } debug(message: string) { if (this.logLevel ! debug) return; console.debug([DEBUG] ${message}); } }增強的錯誤處理中間件import { Logger } from ../utils/logger; export class ErrorHandler { private logger: Logger; constructor() { this.logger new Logger(process.env.LOG_LEVEL || info); } handleToolError(error: any, toolName: string, input: any) { this.logger.error(工具執行失敗: ${toolName}, { error: error.message, input, timestamp: new Date().toISOString() }); return { error: EXECUTION_ERROR, message: 工具 ${toolName} 執行失敗: ${error.message}, timestamp: new Date().toISOString() }; } validateToolInput(schema: any, input: any): string[] { const errors: string[] []; // 檢查必需字段 if (schema.required) { for (const field of schema.required) { if (input[field] undefined || input[field] null) { errors.push(缺少必需字段: ${field}); } } } // 檢查字段類型 if (schema.properties) { for (const [field, definition] of Object.entries(schema.properties) as [string, any][]) { if (input[field] ! undefined) { if (definition.type typeof input[field] ! definition.type) { errors.push(字段 ${field} 類型錯誤期望 ${definition.type}); } } } } return errors; } }6. Claude Desktop集成配置詳解6.1 配置文件詳解Claude Desktop通過JSON配置文件管理MCP服務器集成。以下是完整的配置示例創建~/.claude/mcp-servers/weather-server.json{ command: /usr/local/bin/node, args: [ /Users/yourusername/projects/weather-mcp-server/dist/index.js ], env: { WEATHER_API_KEY: your-openweathermap-api-key, LOG_LEVEL: info, NODE_ENV: production }, timeout: 30000, cwd: /Users/yourusername/projects/weather-mcp-server, disabled: false }6.2 環境變量安全管理敏感信息如API密鑰應該通過環境變量管理創建.env文件不要提交到版本控制WEATHER_API_KEYyour_actual_api_key_here CURRENCY_API_KEYyour_currency_api_key LOG_LEVELinfo使用dotenv加載配置import { config } from dotenv; config(); // 驗證必需的環境變量 const requiredEnvVars [WEATHER_API_KEY]; for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { throw new Error(缺少必需的環境變量: ${envVar}); } }6.3 調試配置創建VS Code調試配置文件.vscode/launch.json{ version: 0.2.0, configurations: [ { name: 調試MCP服務器, type: node, request: launch, program: ${workspaceFolder}/src/index.ts, outFiles: [${workspaceFolder}/dist/**/*.js], runtimeArgs: [-r, ts-node/register], env: { WEATHER_API_KEY: test-key, LOG_LEVEL: debug }, console: integratedTerminal } ] }7. ChatGPT自定義GPT集成方案7.1 創建自定義GPT動作雖然ChatGPT目前對MCP的原生支持不如Claude完善但我們可以通過自定義GPT的Actions功能實現類似效果創建openapi.yaml文件定義API接口openapi: 3.1.0 info: title: Weather MCP Server API description: 為ChatGPT提供天氣查詢功能的MCP兼容接口 version: 1.0.0 servers: - url: https://your-api-domain.com description: 生產環境服務器 paths: /weather: post: operationId: getWeather summary: 獲取城市天氣信息 description: 查詢指定城市的當前天氣狀況 requestBody: required: true content: application/json: schema: type: object properties: city: type: string description: 城市名稱 country: type: string description: 國家代碼可選 units: type: string enum: [metric, imperial] description: 溫度單位 required: - city responses: 200: description: 成功返回天氣信息 content: application/json: schema: type: object properties: city: type: string temperature: type: number description: type: string humidity: type: number windSpeed: type: number7.2 實現HTTP MCP服務器創建基于HTTP的MCP服務器適配器import express from express; import { WeatherTool } from ./tools/weatherTool; class HTTPMCPServer { private app: express.Application; private weatherTool: WeatherTool; constructor() { this.app express(); this.weatherTool new WeatherTool(process.env.WEATHER_API_KEY!); this.setupMiddleware(); this.setupRoutes(); } private setupMiddleware() { this.app.use(express.json()); this.app.use((req, res, next) { res.header(Access-Control-Allow-Origin, *); res.header(Access-Control-Allow-Headers, Content-Type); next(); }); } private setupRoutes() { // MCP協議兼容端點 this.app.post(/mcp/tools/list, (req, res) { res.json({ tools: [this.weatherTool.getToolDefinition()] }); }); this.app.post(/mcp/tools/call, async (req, res) { try { const { name, arguments: args } req.body; if (name get_weather) { const result await this.weatherTool.execute(args); res.json({ content: [{ type: text, text: JSON.stringify(result) }] }); } else { res.status(404).json({ error: 工具未找到 }); } } catch (error) { res.status(500).json({ error: 執行失敗, message: error instanceof Error ? error.message : 未知錯誤 }); } }); // ChatGPT Actions兼容端點 this.app.post(/chatgpt/weather, async (req, res) { try { const { city, country, units } req.body; const result await this.weatherTool.execute({ city, country, units }); res.json(result); } catch (error) { res.status(500).json({ error: 天氣查詢失敗, details: error instanceof Error ? error.message : 未知錯誤 }); } }); } start(port: number 3000) { this.app.listen(port, () { console.log(HTTP MCP服務器運行在端口 ${port}); }); } } const server new HTTPMCPServer(); server.start();8. 常見問題與解決方案8.1 連接與配置問題問題1Claude Desktop無法識別MCP服務器現象啟動Claude后看不到自定義工具排查步驟檢查配置文件路徑是否正確~/.claude/mcp-servers/確認JSON配置文件格式正確查看Claude Desktop日志幫助 → 查看日志驗證node路徑和腳本路徑是否正確解決方案# 檢查node路徑 which node # 測試直接運行MCP服務器 node /path/to/your/mcp-server/dist/index.js問題2權限錯誤或文件不存在現象服務器啟動失敗提示權限不足解決方案# 給腳本添加執行權限 chmod x /path/to/your/script.js # 檢查文件路徑是否存在 ls -la /path/to/your/mcp-server/8.2 工具執行問題問題3工具調用超時現象AI助手顯示工具執行超時可能原因網絡連接緩慢API響應時間過長服務器處理邏輯復雜優化方案// 添加超時控制 async executeWithTimeout(input: any, timeoutMs: number 10000) { const timeoutPromise new Promise((_, reject) setTimeout(() reject(new Error(執行超時)), timeoutMs) ); const executionPromise this.execute(input); return Promise.race([executionPromise, timeoutPromise]); }問題4API密鑰錯誤或配額不足現象工具返回認證錯誤解決方案// 實現API密鑰輪換 class APIKeyManager { private keys: string[]; private currentIndex: number 0; constructor(keys: string[]) { this.keys keys; } getCurrentKey(): string { return this.keys[this.currentIndex]; } rotateKey(): void { this.currentIndex (this.currentIndex 1) % this.keys.length; } handleAuthError(): void { this.rotateKey(); } }8.3 性能優化問題問題5服務器響應緩慢優化策略實現結果緩存使用連接池優化數據庫查詢啟用壓縮// 簡單的內存緩存實現 class CacheManager { private cache: Mapstring, { data: any; expiry: number } new Map(); private defaultTTL: number 300000; // 5分鐘 get(key: string): any { const item this.cache.get(key); if (!item || Date.now() item.expiry) { this.cache.delete(key); return null; } return item.data; } set(key: string, data: any, ttl?: number): void { this.cache.set(key, { data, expiry: Date.now() (ttl || this.defaultTTL) }); } }9. 安全最佳實踐9.1 輸入驗證與消毒所有用戶輸入都必須經過嚴格驗證export class SecurityValidator { static validateCityName(city: string): boolean { // 只允許字母、空格、連字符和基本標點 const validPattern /^[a-zA-Z\s\-,\.]$/; return validPattern.test(city) city.length 100; } static sanitizeInput(input: string): string { // 移除潛在的惡意字符 return input .replace(/[]/g, ) .replace(/javascript:/gi, ) .replace(/on\w/gi, ) .trim(); } static validateAPIInput(schema: any, input: any): { isValid: boolean; errors: string[] } { const errors: string[] []; for (const [key, value] of Object.entries(input)) { // 檢查未知字段 if (!schema.properties[key]) { errors.push(未知字段: ${key}); continue; } // 類型檢查 const fieldSchema schema.properties[key]; if (fieldSchema.type typeof value ! fieldSchema.type) { errors.push(字段 ${key} 類型錯誤); } // 枚舉值檢查 if (fieldSchema.enum !fieldSchema.enum.includes(value)) { errors.push(字段 ${key} 值不在允許范圍內); } } return { isValid: errors.length 0, errors }; } }9.2 權限控制與訪問限制實現基于上下文的權限控制class PermissionManager { private allowedTools: Mapstring, string[] new Map(); constructor() { // 定義工具訪問權限 this.allowedTools.set(weather, [public]); this.allowedTools.set(file_read, [authenticated]); this.allowedTools.set(admin_tools, [admin]); } canAccessTool(toolName: string, userContext: any): boolean { const requiredRoles this.allowedTools.get(toolName); if (!requiredRoles) return false; return requiredRoles.some(role userContext.roles?.includes(role) || role public ); } auditToolUsage(toolName: string, input: any, userContext: any): void { console.log([AUDIT] 工具使用記錄, { tool: toolName, user: userContext.userId, input: this.sanitizeForLogging(input), timestamp: new Date().toISOString(), ip: userContext.ipAddress }); } private sanitizeForLogging(input: any): any { const sanitized { ...input }; // 移除敏感信息 if (sanitized.password) delete sanitized.password; if (sanitized.apiKey) delete sanitized.apiKey; return sanitized; } }9.3 錯誤信息處理避免在錯誤響應中泄露敏感信息export class SafeErrorHandler { static sanitizeError(error: any): { message: string; code: string } { // 生產環境中隱藏詳細錯誤信息 if (process.env.NODE_ENV production) { if (error instanceof DatabaseError) { return { message: 數據庫操作失敗, code: DB_ERROR }; } if (error instanceof NetworkError) { return { message: 網絡連接失敗, code: NETWORK_ERROR }; } return { message: 操作失敗, code: GENERIC_ERROR }; } // 開發環境顯示詳細錯誤 return { message: error.message, code: error.code || UNKNOWN_ERROR }; } }10. 生產環境部署指南10.1 容器化部署創建Dockerfile優化生產環境部署FROM node:18-alpine WORKDIR /app # 安裝依賴 COPY package*.json ./ RUN npm ci --onlyproduction # 復制編譯后的代碼 COPY dist/ ./dist/ # 創建非root用戶 RUN addgroup -g 1001 -S nodejs RUN adduser -S mcp-server -u 1001 USER mcp-server # 健康檢查 HEALTHCHECK --interval30s --timeout3s \ CMD node -e require(http).get(http://localhost:3000/health, (res) { process.exit(res.statusCode 200 ? 0 : 1) }) EXPOSE 3000 CMD [node, dist/index.js]創建docker-compose.yml簡化部署version: 3.8 services: mcp-server: build: . ports: - 3000:3000 environment: - NODE_ENVproduction - WEATHER_API_KEY${WEATHER_API_KEY} - LOG_LEVELinfo restart: unless-stopped healthcheck: test: [CMD, node, -e, require(http).get(http://localhost:3000/health, (res) { process.exit(res.statusCode 200 ? 0 : 1) })] interval: 30s timeout: 10s retries: 310.2 監控與日志實現完整的監控體系import { createLogger, format, transports } from winston; export const logger createLogger({ level: process.env.LOG_LEVEL || info, format: format.combine( format.timestamp(), format.errors({ stack: true }), format.json() ), transports: [ new transports.File({ filename: error.log, level: error }), new transports.File({ filename: combined.log }), new transports.Console({ format: format.simple() }) ] }); // 性能監控 export class PerformanceMonitor { private metrics: Mapstring, number[] new Map(); startTimer(operation: string): () number { const start Date.now(); return () { const duration Date.now() - start; this.recordMetric(operation, duration); return duration; }; } private recordMetric(operation: string, duration: number): void { if (!this.metrics.has(operation)) { this.metrics.set(operation, []); } this.metrics.get(operation)!.push(duration); // 定期清理舊數據 if (this.metrics.get(operation)!.length 1000) { this.metrics.set(operation, this.metrics.get(operation)!.slice(-500)); } } getMetrics(): any { const result: any {}; for (const [operation, durations] of this.metrics) { result[operation] { count: durations.length, average: durations.reduce((a, b) a b, 0) / durations.length, p95: this.percentile(durations, 95), max: Math.max(...durations) }; } return result; } private percentile(arr: number[], p: number): number { const sorted [...arr].sort((a, b) a - b); const index Math.ceil((p / 100) * sorted.length) - 1; return sorted[index]; } }通過本文的完整指南你應該已經掌握了MCP服務器的核心概念、開發流程和部署實踐。從簡單的天氣查詢工具到復雜的企業級集成MCP協議為AI助手的能力擴展提供了標準化且強大的解決方案。在實際項目中建議先從簡單的工具開始逐步擴展到復雜的業務場景。記得始終遵循安全最佳實踐特別是在處理敏感數據和外部API集成時。隨著MCP生態的不斷發展這項技術將為AI應用開發帶來更多可能性。