1. 项目概述一个为TypeScript开发者量身定制的MCP服务器如果你是一名TypeScript/JavaScript开发者最近又在关注AI应用开发特别是想给Claude、Cursor这类智能助手“装”上一些自定义工具那么你很可能已经接触过“模型上下文协议”Model Context Protocol 简称MCP。MCP的核心思想很酷它定义了一套标准让AI助手能够安全、可控地访问外部工具和数据源比如你的数据库、文件系统或者第三方API。这就像是给AI配了一个标准化的“插件底座”。然而当你兴冲冲地想去GitHub上找一个现成的TypeScript MCP服务器模板时可能会发现官方SDK虽然提供了Python和JavaScript/TypeScript版本但后者更偏向于库library形态缺少一个开箱即用、结构清晰、集成了最佳实践的项目脚手架。你需要自己从零搭建项目结构、处理依赖、配置构建工具、设计资源Resource和工具Tool的模块化组织方式——这对于想快速验证一个MCP工具创意的开发者来说门槛不低。这就是haneiva1/ts-mcp项目出现的背景。它不是一个功能庞杂的MCP服务器实现而是一个专为TypeScript/Node.js环境设计的、生产就绪的MCP服务器开发模板和最佳实践集合。你可以把它理解为一个“种子项目”Starter Template它已经帮你做好了所有繁琐的基础设施工作从TypeScript配置、ESLint代码规范、到MCP核心服务器的封装、工具类的抽象、以及本地文件系统资源读取的示例。你的任务就是从克隆这个仓库开始专注于实现你自己的业务逻辑和工具。简单来说ts-mcp解决的核心痛点是让TypeScript开发者能够以最快的速度、最规范的方式启动一个高质量的MCP服务器开发项目避免在项目初始化、架构设计和工程化配置上重复踩坑。2. 核心架构与设计哲学拆解2.1 为什么选择TypeScript和Node.js在MCP的生态中Python由于其在AI和数据科学领域的统治地位自然是首选之一。但前端和全栈领域是JavaScript/TypeScript的天下。很多我们希望赋予AI的能力恰恰存在于这个生态中比如操作浏览器Puppeteer、处理前端项目构建Webpack/Vite、调用云服务SDKAWS SDK、Vercel CLI、或者与现有的Node.js后端服务集成。ts-mcp项目坚定地选择了TypeScript这带来了几个显著优势类型安全MCP协议本身有严格的数据结构定义如Tool、Resource、CallToolResult。TypeScript的接口和类型系统能确保你在实现工具时输入输出的数据结构完全符合协议规范极大减少了运行时错误。出色的开发体验现代IDE如VSCode对TypeScript的支持是无与伦比的提供精准的代码补全、跳转和重构能力。这对于开发需要精确匹配协议定义的MCP工具来说效率提升巨大。庞大的npm生态任何你需要的功能几乎都能在npm上找到对应的、带有类型定义的库可以快速集成到你的MCP服务器中。与前端工具链无缝集成如果你的MCP工具是为了辅助前端开发例如读取项目配置、生成组件代码那么用TypeScript来实现是再自然不过的选择。2.2 模板的核心目录结构解析一个优秀的模板其价值首先体现在清晰、可扩展的目录结构上。ts-mcp的目录设计体现了模块化和关注点分离的思想ts-mcp/ ├── src/ │ ├── index.ts # 服务器主入口初始化并启动MCP服务器 │ ├── server/ # MCP服务器核心封装 │ │ └── mcp-server.ts # 继承官方Server类提供增强功能 │ ├── tools/ # 工具Tool实现目录 │ │ ├── index.ts # 集中导出所有工具 │ │ └── example-tool.ts # 示例工具实现 │ ├── resources/ # 资源Resource实现目录 │ │ ├── index.ts # 集中导出所有资源 │ │ └── file-resource.ts # 示例文件资源实现 │ └── types/ # 项目自定义类型定义 │ └── ... ├── scripts/ # 构建、开发脚本 ├── dist/ # TypeScript编译输出目录 ├── package.json ├── tsconfig.json # TypeScript配置 ├── .eslintrc.js # 代码规范检查 └── README.md设计亮点与考量src/index.ts作为单一入口职责清晰只负责初始化服务器、注册工具和资源然后启动。这符合Node.js服务的最佳实践。server/mcp-server.ts的封装项目没有直接使用官方的modelcontextprotocol/sdk中的Server类而是选择封装一层。这样做的好处是可以在这一层统一添加日志、错误处理、生命周期管理或自定义的协议扩展而不会污染具体的工具实现代码。这是面向未来扩展性的设计。工具与资源的模块化将tools/和resources/分离并通过各自的index.ts文件统一导出使得添加新功能变得极其简单你只需要在对应目录新建一个文件实现功能然后在index.ts中导出即可。主入口文件无需频繁修改。独立的types/目录用于存放超出MCP基础协议的自定义类型保持类型定义的集中管理。注意这种结构并非唯一解但它为中小型MCP服务器项目提供了一个非常理想的起点。对于超大型项目你可能需要按领域domain进一步划分子目录但ts-mcp的模块化思想依然适用。2.3 工程化配置开箱即用的现代开发环境模板预置的工程化配置是其“生产就绪”宣称的重要支撑TypeScript配置 (tsconfig.json)针对Node.js环境优化开启了严格模式strict: true确保代码质量。输出目录设置为dist/源码目录为src/结构清晰。ESLint Prettier通常集成模板集成了代码风格检查和自动格式化。统一的代码风格对于团队协作和项目可维护性至关重要。它强制你养成好的编码习惯比如使用一致的引号、分号规则和缩进。开发脚本 (package.json中的 scripts)npm run dev通常配置了类似tsx或ts-node-dev的工具实现源码变更的热重载。你修改src/下的代码服务器会自动重启无需手动编译和重启极大提升开发效率。npm run build将TypeScript编译为纯净的JavaScript到dist/目录用于生产环境部署。npm start运行编译后的生产版本。依赖管理核心依赖modelcontextprotocol/sdk被锁定在一个稳定的版本。模板的package.json通常会将typescript、eslint等工具依赖放在devDependencies中而运行时依赖放在dependencies中区分明确。实操心得很多开发者会忽略这些工程化配置直到项目变得难以维护。ts-mcp模板帮你一步到位地解决了这个问题。你克隆项目后第一件事应该是运行npm install然后尝试npm run dev感受一下这种“开箱即用”的流畅感。这节省了你至少半天的项目初始化时间。3. 核心模块深度解析与实现3.1 增强型MCP服务器封装 (mcp-server.ts)这是模板的“心脏”。让我们深入看看它可能做了哪些增强以下代码为基于常见实践的推测和补充// src/server/mcp-server.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolResult, ErrorCode, ListToolsResult, Tool } from modelcontextprotocol/sdk/types.js; import { createLogger, format, transports } from winston; // 示例添加日志 export class MCPServer { private server: Server; private transport: StdioServerTransport; private logger; constructor(serverName: string) { // 1. 初始化日志系统 this.logger createLogger({ level: info, format: format.combine(format.timestamp(), format.json()), transports: [new transports.Console()], }); // 2. 创建官方Server实例 this.server new Server( { name: serverName, version: 1.0.0 }, { capabilities: { tools: {}, resources: {} } } // 初始能力后续动态注册 ); // 3. 创建传输层标准输入输出适配Claude Desktop等客户端 this.transport new StdioServerTransport(); // 4. 绑定连接和错误处理 this.server.onerror (error) { this.logger.error(MCP Server error:, error); }; // 5. 可选的统一工具调用错误处理中间件 this.setupErrorHandling(); } // 注册工具的统一入口 async registerTool(tool: Tool) { // 这里可以添加工具名冲突检查、日志记录等逻辑 this.logger.info(Registering tool: ${tool.name}); this.server.setRequestHandler(ListToolsRequestSchema, async () { // 动态更新工具列表 return { tools: [tool] }; }); // 更常见的做法是在server初始化后通过某个管理器统一添加 } // 封装启动逻辑 async start() { try { await this.server.connect(this.transport); this.logger.info(MCP Server started successfully.); } catch (error) { this.logger.error(Failed to start MCP Server:, error); process.exit(1); } } // 私有方法设置统一的错误处理 private setupErrorHandling() { // 可以拦截所有工具调用进行try-catch包装返回格式化的错误信息 // 确保任何工具抛出的异常都不会导致服务器崩溃而是返回一个规范的CallToolResult } }关键设计解析日志集成生产级应用必须拥有可观测性。封装层集成Winston或Pino等日志库可以结构化地记录服务器生命周期事件、工具调用请求和结果方便调试和监控。错误处理增强原版SDK的错误处理可能比较基础。封装层可以捕获未预料的异常将其转换为MCP协议规定的错误响应{ error: ... }防止服务器进程因单个工具崩溃而退出。生命周期管理start()方法封装了连接和启动逻辑并进行了错误处理。未来还可以在此处加入健康检查、优雅关闭graceful shutdown的逻辑。中心化的注册点虽然上面的registerTool方法是一个简化示例但思想是提供一个清晰、统一的方法来管理所有工具而不是让工具散落在各处。3.2 工具Tool的实现范式与示例工具是MCP的灵魂是AI助手可以调用的“函数”。模板在src/tools/example-tool.ts中提供了一个典范。// src/tools/example-tool.ts import { Tool } from modelcontextprotocol/sdk/types.js; // 1. 定义工具的参数Schema基于JSON Schema const GetWeatherSchema { type: object, properties: { city: { type: string, description: The name of the city to get weather for, e.g., London or New York. }, units: { type: string, enum: [metric, imperial], description: The unit system for the temperature. Default is metric., default: metric } }, required: [city], additionalProperties: false // 严格模式禁止额外参数 } as const; // 2. 定义工具元数据 export const GetWeatherTool: Tool { name: get_weather, // 工具名通常用蛇形命名 description: Fetches the current weather conditions for a specified city., inputSchema: GetWeatherSchema // 关联输入参数定义 }; // 3. 定义工具的执行函数Handler export async function handleGetWeather(args: { city: string; units?: metric | imperial }): Promiseany { const { city, units metric } args; // 参数验证Schema已做基础验证这里可做业务逻辑验证 if (!city.trim()) { throw new Error(City name cannot be empty.); } // 模拟或真实调用外部API // 在实际项目中这里可能是调用OpenWeatherMap、WeatherAPI等 const mockTemperature units metric ? 22°C : 72°F; const mockCondition Partly Cloudy; // 返回结构化的结果AI助手能很好地解析 return { content: [ { type: text, text: The current weather in ${city} is ${mockCondition} with a temperature of ${mockTemperature}. } ], // 可以附加更多结构化数据供AI后续推理使用 _meta: { city, temperature: mockTemperature, condition: mockCondition, units, fetchedAt: new Date().toISOString() } }; }实现要点与最佳实践清晰的Schema定义使用as const断言确保类型推断准确。description字段至关重要它是AI理解工具用途和参数含义的主要依据应尽可能详细、准确。严格的输入验证additionalProperties: false能防止AI传递未定义的参数避免意外行为。在Handler中可以进行二次业务验证。结构化的输出返回结果不仅包含给用户看的文本content[0].text还可以在_meta等字段中包含原始数据。这给了AI更大的灵活性例如它可以选择用不同的方式向用户呈现或者利用这些数据进行下一步计算。错误处理Handler函数中应使用throw new Error()来抛出错误。这些错误应该在服务器封装层被捕获并转换成规范的错误响应。注意事项工具名 (name) 在整个MCP服务器中必须唯一。建议使用动词开头、蛇形命名法snake_case如search_web,create_file这与AI助手调用工具的自然语言描述方式更匹配。3.3 资源Resource的抽象与文件系统示例资源代表了AI可以读取的“数据源”。ts-mcp模板通常会包含一个文件系统资源的示例因为它是最通用、最直接的需求之一。// src/resources/file-resource.ts import { Resource, ResourceTemplate } from modelcontextprotocol/sdk/types.js; import fs from fs/promises; import path from path; import { fileURLToPath } from url; // 1. 定义资源模板用于动态生成资源URI export const FileResourceTemplate: ResourceTemplate { uriTemplate: file://{path}, name: File from local filesystem, description: Read contents of a file from the local machine., mimeType: text/plain // 默认MIME类型可根据文件扩展名动态判断 }; // 2. 资源列表提供函数告诉客户端有哪些资源可用 export async function listFileResources(basePath: string process.cwd()): PromiseResource[] { // 这是一个简单示例列出当前目录下的所有.txt文件作为资源 // 警告在生产环境中必须严格限制可访问的路径范围 const files await fs.readdir(basePath); const textFiles files.filter(f f.endsWith(.txt)); return textFiles.map(file ({ uri: file://${path.join(basePath, file)}, name: File: ${file}, description: Contents of ${file}, mimeType: text/plain })); } // 3. 资源内容读取函数Handler export async function readFileResource(uri: string): Promise{ contents: Array{ type: string; text: string } } { // 解析URI获取文件路径 const url new URL(uri); if (url.protocol ! file:) { throw new Error(Unsupported protocol: ${url.protocol}); } const filePath fileURLToPath(uri); // 安全地将file:// URL转换为路径 // 重要的安全边界检查防止目录遍历攻击 const resolvedPath path.resolve(filePath); const allowedBase path.resolve(process.cwd()); // 限制在当前工作目录内 if (!resolvedPath.startsWith(allowedBase)) { throw new Error(Access to path ${resolvedPath} is not allowed.); } // 读取文件内容 const content await fs.readFile(resolvedPath, utf-8); return { contents: [ { type: text, text: content } ] }; }安全与设计考量URI模板file://{path}是一个标准的文件URI模板。{path}表示一个路径变量客户端如AI可以请求file:///Users/me/project/README.md。绝对的安全边界这是资源实现中最重要的部分。readFileResource函数中必须进行路径解析和边界检查startsWith。永远不要相信客户端传入的URI路径必须将其解析并限制在预先定义好的安全目录如项目根目录、某个特定数据目录内否则将造成严重的任意文件读取漏洞。动态MIME类型示例中写死了text/plain。更完善的实现应该根据文件扩展名如.json,.md,.html返回对应的MIME类型帮助AI更好地理解内容格式。资源列表的动态性listFileResources展示了如何动态生成资源列表。你可以根据数据库查询结果、API响应等来动态创建资源让AI助手能“看到”不断变化的数据集。4. 从零到一的完整开发与集成流程4.1 环境准备与项目初始化假设你已经安装了Node.js建议LTS版本如18.x或20.x和npm或yarn/pnpm。# 1. 克隆模板仓库这里以假设的仓库地址为例 git clone https://github.com/haneiva1/ts-mcp.git my-mcp-server cd my-mcp-server # 2. 安装依赖 npm install # 3. 探索项目结构 # 打开src/目录熟悉index.ts, server/, tools/, resources/的结构 # 4. 运行开发模式测试示例是否工作 npm run dev运行npm run dev后你的MCP服务器就会在标准输入输出stdio上启动并等待连接。此时它还没有被任何客户端使用。4.2 开发你的第一个自定义工具一个“项目文件搜索”工具让我们基于模板添加一个实用的工具在项目目录中搜索包含特定关键词的文件。步骤一创建工具文件在src/tools/目录下新建search-files.ts。// src/tools/search-files.ts import { Tool } from modelcontextprotocol/sdk/types.js; import fs from fs/promises; import path from path; // 定义Schema const SearchFilesSchema { type: object, properties: { keyword: { type: string, description: The text or keyword to search for within project files. }, fileExtension: { type: string, description: Optional filter by file extension (e.g., .ts, .md). Leave empty to search all files., default: }, maxResults: { type: number, description: Maximum number of results to return. Default is 10., default: 10, minimum: 1, maximum: 50 } }, required: [keyword], additionalProperties: false } as const; // 定义工具 export const SearchFilesTool: Tool { name: search_project_files, description: Searches for a keyword within text files in the current project directory. Useful for finding code snippets, documentation, or specific configurations., inputSchema: SearchFilesSchema }; // 实现Handler export async function handleSearchFiles(args: { keyword: string; fileExtension?: string; maxResults?: number; }): Promiseany { const { keyword, fileExtension , maxResults 10 } args; const searchDir process.cwd(); // 搜索当前工作目录 const results: Array{file: string, line: number, snippet: string} []; // 一个简单的递归文件搜索函数忽略node_modules等目录 async function searchInDirectory(dirPath: string) { const entries await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath path.join(dirPath, entry.name); // 跳过node_modules和.git目录 if (entry.isDirectory()) { if ([node_modules, .git, dist].includes(entry.name)) { continue; } await searchInDirectory(fullPath); // 递归搜索子目录 } else if (entry.isFile()) { // 检查文件扩展名过滤 if (fileExtension !entry.name.endsWith(fileExtension)) { continue; } // 只搜索文本文件简单判断 const textExtensions [.txt, .md, .js, .ts, .json, .yml, .yaml, .html, .css]; if (!textExtensions.some(ext entry.name.endsWith(ext))) { continue; } try { const content await fs.readFile(fullPath, utf-8); const lines content.split(\n); lines.forEach((line, index) { if (line.includes(keyword)) { // 计算相对路径更友好 const relativePath path.relative(searchDir, fullPath); results.push({ file: relativePath, line: index 1, snippet: line.trim().substring(0, 100) // 截取片段 }); } }); } catch (error) { // 忽略无法读取的文件如二进制文件 console.warn(Could not read file ${fullPath}:, error.message); } } } } await searchInDirectory(searchDir); // 排序和限制结果数量 const sortedResults results.slice(0, maxResults); if (sortedResults.length 0) { return { content: [{ type: text, text: No files found containing the keyword ${keyword}${fileExtension ? with extension ${fileExtension} : }. }] }; } // 格式化输出为Markdown表格便于AI和用户阅读 const resultTable | File | Line | Snippet | |------|------|---------| ${sortedResults.map(r | ${r.file} | ${r.line} | ${r.snippet} |).join(\n)}; return { content: [{ type: text, text: Found ${sortedResults.length} result(s) for keyword ${keyword}:\n\n${resultTable} }], _meta: { keyword, totalMatches: results.length, resultsReturned: sortedResults.length, results: sortedResults // 提供结构化数据供AI进一步处理 } }; }步骤二注册新工具修改src/tools/index.ts文件导出新工具。// src/tools/index.ts export { GetWeatherTool, handleGetWeather } from ./example-tool.js; // 注意导入编译后的.js文件或在dev模式下配置ts-node等 export { SearchFilesTool, handleSearchFiles } from ./search-files.js; // 统一导出工具列表和处理器映射 import type { Tool } from modelcontextprotocol/sdk/types.js; export const tools: Tool[] [GetWeatherTool, SearchFilesTool]; // 工具名到处理函数的映射 import type { CallToolRequest } from modelcontextprotocol/sdk/types.js; export const toolHandlers: Recordstring, (args: any) Promiseany { [GetWeatherTool.name]: handleGetWeather, [SearchFilesTool.name]: handleSearchFiles, };步骤三在主入口中集成修改src/index.ts确保新工具被服务器注册。// src/index.ts (部分) import { MCPServer } from ./server/mcp-server.js; import { tools, toolHandlers } from ./tools/index.js; import { listFileResources, readFileResource } from ./resources/index.js; async function main() { const server new MCPServer(my-awesome-mcp-server); // 注册所有工具 for (const tool of tools) { // 这里调用server上自定义的注册方法或直接使用原版server.setRequestHandler // 假设我们的MCPServer类有一个registerToolAndHandler方法 server.registerToolAndHandler(tool, toolHandlers[tool.name]); } // 注册资源相关处理器示例 server.setRequestHandler(ListResourcesRequestSchema, async () { const fileResources await listFileResources(); return { resources: fileResources }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) { if (request.params.uri.startsWith(file://)) { return await readFileResource(request.params.uri); } throw new Error(Unsupported resource URI: ${request.params.uri}); }); await server.start(); } main().catch(console.error);4.3 与Claude Desktop集成测试开发完成后最关键的一步是实际测试。Claude Desktop是体验MCP最直接的方式。配置Claude Desktop找到Claude Desktop的配置目录。在macOS上通常是~/Library/Application Support/Claude/claude_desktop_config.json在Windows上是%APPDATA%\Claude\claude_desktop_config.json。编辑或创建这个JSON配置文件。添加你的MCP服务器配置{ mcpServers: { my-ts-server: { command: node, args: [ /absolute/path/to/your/ts-mcp-project/dist/index.js ], env: { NODE_ENV: production } } } }重要args中的路径必须是绝对路径指向你编译后的入口文件dist/index.js。在开发时你也可以直接指向src/index.ts并用tsx运行但生产配置建议用编译后的版本。重启Claude Desktop应用配置。进行测试打开Claude Desktop新建一个对话。如果配置成功Claude通常会主动打招呼并提及它可以使用的新工具例如“我可以帮你搜索项目文件了”。尝试输入“你能用search_project_files工具帮我找一下项目里所有用到interface关键词的TypeScript文件吗”Claude应该会理解你的意图调用工具并返回格式化的搜索结果。5. 高级主题、调试与生产部署5.1 实现更复杂的工具调用外部API许多MCP工具需要与外部服务交互。下面以实现一个调用GitHub API查询仓库信息的工具为例展示如何处理异步请求、API密钥和安全问题。// src/tools/github-tool.ts import { Tool } from modelcontextprotocol/sdk/types.js; import fetch from node-fetch; // 需要安装: npm install node-fetch2 // 安全地读取环境变量中的API密钥 const GITHUB_TOKEN process.env.GITHUB_TOKEN; const GetGitHubRepoSchema { type: object, properties: { owner: { type: string, description: The owner (username or organization) of the repository. }, repo: { type: string, description: The name of the repository. } }, required: [owner, repo], additionalProperties: false } as const; export const GetGitHubRepoTool: Tool { name: get_github_repo_info, description: Fetches detailed information about a GitHub repository, including description, stars, forks, and latest commit., inputSchema: GetGitHubRepoSchema }; export async function handleGetGitHubRepo(args: { owner: string; repo: string }): Promiseany { const { owner, repo } args; if (!GITHUB_TOKEN) { throw new Error(GitHub API token is not configured. Please set the GITHUB_TOKEN environment variable.); } const apiUrl https://api.github.com/repos/${owner}/${repo}; try { const response await fetch(apiUrl, { headers: { Authorization: token ${GITHUB_TOKEN}, User-Agent: My-MCP-Server, // GitHub API要求User-Agent Accept: application/vnd.github.v3json } }); if (!response.ok) { const errorText await response.text(); throw new Error(GitHub API request failed: ${response.status} ${response.statusText}. ${errorText}); } const repoData await response.json(); return { content: [{ type: text, text: **Repository:** ${repoData.full_name}\n **Description:** ${repoData.description || No description}\n **⭐ Stars:** ${repoData.stargazers_count}\n ** Forks:** ${repoData.forks_count}\n ** Language:** ${repoData.language}\n ** URL:** ${repoData.html_url}\n **Last Updated:** ${new Date(repoData.updated_at).toLocaleDateString()} }], _meta: repoData // 传递完整数据供AI深度分析 }; } catch (error: any) { // 网络错误或API错误 throw new Error(Failed to fetch repository info: ${error.message}); } }关键点环境变量管理API密钥、令牌等敏感信息必须通过环境变量process.env传入绝对不要硬编码在源码中。可以使用dotenv包在开发时从.env文件加载。完善的错误处理检查HTTP响应状态码并提供有意义的错误信息。区分网络错误、认证错误和业务错误。速率限制和重试对于生产环境需要考虑API的速率限制并实现指数退避等重试机制。5.2 调试技巧与问题排查开发MCP服务器时调试可能有点棘手因为它是通过stdio与客户端通信的。启用详细日志在MCPServer构造函数中将日志级别设置为debug或silly记录所有进出的JSON-RPC消息。this.logger createLogger({ level: debug, // 改为debug级别 // ... 其他配置 });独立测试工具函数在开发时可以暂时修改src/index.ts直接调用handleSearchFiles({ keyword: test })并console.log结果确保工具逻辑本身正确无需通过MCP协议。使用MCP InspectorAnthropic官方提供了一个强大的调试工具叫MCP Inspector。你可以通过它来连接和测试你的MCP服务器像“瑞士军刀”一样查看所有工具、资源并手动触发调用观察原始的协议消息。安装npm install -g modelcontextprotocol/inspector运行mcp-inspector然后按照提示配置你的服务器命令如node /path/to/your/dist/index.js。它会提供一个Web界面让你可以交互式地测试所有功能。常见错误与排查ECONNREFUSED或连接错误确保你的服务器脚本正在运行并且Claude Desktop配置中的命令和路径完全正确特别是绝对路径。工具未出现检查Claude Desktop的配置JSON格式是否正确重启Claude Desktop。查看服务器日志确认工具注册阶段没有抛出异常。“Internal server error”查看服务器日志通常是工具Handler函数中抛出了未捕获的异常。确保所有异步操作都有try-catch并且错误被正确抛出为Error对象。权限问题文件资源确保你的服务器进程有权限读取它试图访问的文件和目录。5.3 生产环境部署考量当你准备将MCP服务器分享给团队或部署到更稳定的环境时需要考虑以下几点构建与打包运行npm run build生成优化后的dist/目录。考虑使用pkg或nexe将你的Node.js项目打包成单个可执行文件简化部署。但要注意这可能会使路径处理如__dirname变得复杂。进程管理使用进程管理器如PM2来保持服务器常驻运行并在崩溃时自动重启。PM2配置示例 (ecosystem.config.js)module.exports { apps: [{ name: my-mcp-server, script: ./dist/index.js, instances: 1, autorestart: true, watch: false, max_memory_restart: 500M, env: { NODE_ENV: production, GITHUB_TOKEN: your_token_here // 在生产环境中通过PM2环境注入 } }] };启动pm2 start ecosystem.config.js配置管理将所有配置如API端点、令牌、允许的文件路径外部化使用环境变量或配置文件如config/production.json。对于团队可以考虑使用dotenv加载不同环境.env.production,.env.staging的变量。安全加固资源访问再次审查所有资源如file-resource.ts的路径安全检查逻辑确保没有任何目录遍历漏洞。工具权限考虑为不同的工具实现简单的权限控制。例如某些危险工具如“执行Shell命令”只能在特定的、受信任的环境下启用。输入验证除了JSON Schema验证在工具Handler内部对输入进行二次验证和清理防止注入攻击。监控与告警集成像Sentry这样的错误跟踪服务捕获生产环境中的未处理异常。使用PM2或系统工具监控进程的CPU和内存使用情况。记录详细的访问日志和工具调用日志便于审计和问题诊断。ts-mcp模板为你提供了一个坚固的起点但将其转化为一个健壮的生产级服务还需要你根据具体的业务逻辑和安全要求在上述方面进行细化和加强。它的价值在于让你跳过了从零搭建框架的繁琐直接进入最有价值的业务逻辑开发阶段。
TypeScript MCP服务器开发指南:从模板到AI工具集成实践
发布时间:2026/5/16 0:59:21
1. 项目概述一个为TypeScript开发者量身定制的MCP服务器如果你是一名TypeScript/JavaScript开发者最近又在关注AI应用开发特别是想给Claude、Cursor这类智能助手“装”上一些自定义工具那么你很可能已经接触过“模型上下文协议”Model Context Protocol 简称MCP。MCP的核心思想很酷它定义了一套标准让AI助手能够安全、可控地访问外部工具和数据源比如你的数据库、文件系统或者第三方API。这就像是给AI配了一个标准化的“插件底座”。然而当你兴冲冲地想去GitHub上找一个现成的TypeScript MCP服务器模板时可能会发现官方SDK虽然提供了Python和JavaScript/TypeScript版本但后者更偏向于库library形态缺少一个开箱即用、结构清晰、集成了最佳实践的项目脚手架。你需要自己从零搭建项目结构、处理依赖、配置构建工具、设计资源Resource和工具Tool的模块化组织方式——这对于想快速验证一个MCP工具创意的开发者来说门槛不低。这就是haneiva1/ts-mcp项目出现的背景。它不是一个功能庞杂的MCP服务器实现而是一个专为TypeScript/Node.js环境设计的、生产就绪的MCP服务器开发模板和最佳实践集合。你可以把它理解为一个“种子项目”Starter Template它已经帮你做好了所有繁琐的基础设施工作从TypeScript配置、ESLint代码规范、到MCP核心服务器的封装、工具类的抽象、以及本地文件系统资源读取的示例。你的任务就是从克隆这个仓库开始专注于实现你自己的业务逻辑和工具。简单来说ts-mcp解决的核心痛点是让TypeScript开发者能够以最快的速度、最规范的方式启动一个高质量的MCP服务器开发项目避免在项目初始化、架构设计和工程化配置上重复踩坑。2. 核心架构与设计哲学拆解2.1 为什么选择TypeScript和Node.js在MCP的生态中Python由于其在AI和数据科学领域的统治地位自然是首选之一。但前端和全栈领域是JavaScript/TypeScript的天下。很多我们希望赋予AI的能力恰恰存在于这个生态中比如操作浏览器Puppeteer、处理前端项目构建Webpack/Vite、调用云服务SDKAWS SDK、Vercel CLI、或者与现有的Node.js后端服务集成。ts-mcp项目坚定地选择了TypeScript这带来了几个显著优势类型安全MCP协议本身有严格的数据结构定义如Tool、Resource、CallToolResult。TypeScript的接口和类型系统能确保你在实现工具时输入输出的数据结构完全符合协议规范极大减少了运行时错误。出色的开发体验现代IDE如VSCode对TypeScript的支持是无与伦比的提供精准的代码补全、跳转和重构能力。这对于开发需要精确匹配协议定义的MCP工具来说效率提升巨大。庞大的npm生态任何你需要的功能几乎都能在npm上找到对应的、带有类型定义的库可以快速集成到你的MCP服务器中。与前端工具链无缝集成如果你的MCP工具是为了辅助前端开发例如读取项目配置、生成组件代码那么用TypeScript来实现是再自然不过的选择。2.2 模板的核心目录结构解析一个优秀的模板其价值首先体现在清晰、可扩展的目录结构上。ts-mcp的目录设计体现了模块化和关注点分离的思想ts-mcp/ ├── src/ │ ├── index.ts # 服务器主入口初始化并启动MCP服务器 │ ├── server/ # MCP服务器核心封装 │ │ └── mcp-server.ts # 继承官方Server类提供增强功能 │ ├── tools/ # 工具Tool实现目录 │ │ ├── index.ts # 集中导出所有工具 │ │ └── example-tool.ts # 示例工具实现 │ ├── resources/ # 资源Resource实现目录 │ │ ├── index.ts # 集中导出所有资源 │ │ └── file-resource.ts # 示例文件资源实现 │ └── types/ # 项目自定义类型定义 │ └── ... ├── scripts/ # 构建、开发脚本 ├── dist/ # TypeScript编译输出目录 ├── package.json ├── tsconfig.json # TypeScript配置 ├── .eslintrc.js # 代码规范检查 └── README.md设计亮点与考量src/index.ts作为单一入口职责清晰只负责初始化服务器、注册工具和资源然后启动。这符合Node.js服务的最佳实践。server/mcp-server.ts的封装项目没有直接使用官方的modelcontextprotocol/sdk中的Server类而是选择封装一层。这样做的好处是可以在这一层统一添加日志、错误处理、生命周期管理或自定义的协议扩展而不会污染具体的工具实现代码。这是面向未来扩展性的设计。工具与资源的模块化将tools/和resources/分离并通过各自的index.ts文件统一导出使得添加新功能变得极其简单你只需要在对应目录新建一个文件实现功能然后在index.ts中导出即可。主入口文件无需频繁修改。独立的types/目录用于存放超出MCP基础协议的自定义类型保持类型定义的集中管理。注意这种结构并非唯一解但它为中小型MCP服务器项目提供了一个非常理想的起点。对于超大型项目你可能需要按领域domain进一步划分子目录但ts-mcp的模块化思想依然适用。2.3 工程化配置开箱即用的现代开发环境模板预置的工程化配置是其“生产就绪”宣称的重要支撑TypeScript配置 (tsconfig.json)针对Node.js环境优化开启了严格模式strict: true确保代码质量。输出目录设置为dist/源码目录为src/结构清晰。ESLint Prettier通常集成模板集成了代码风格检查和自动格式化。统一的代码风格对于团队协作和项目可维护性至关重要。它强制你养成好的编码习惯比如使用一致的引号、分号规则和缩进。开发脚本 (package.json中的 scripts)npm run dev通常配置了类似tsx或ts-node-dev的工具实现源码变更的热重载。你修改src/下的代码服务器会自动重启无需手动编译和重启极大提升开发效率。npm run build将TypeScript编译为纯净的JavaScript到dist/目录用于生产环境部署。npm start运行编译后的生产版本。依赖管理核心依赖modelcontextprotocol/sdk被锁定在一个稳定的版本。模板的package.json通常会将typescript、eslint等工具依赖放在devDependencies中而运行时依赖放在dependencies中区分明确。实操心得很多开发者会忽略这些工程化配置直到项目变得难以维护。ts-mcp模板帮你一步到位地解决了这个问题。你克隆项目后第一件事应该是运行npm install然后尝试npm run dev感受一下这种“开箱即用”的流畅感。这节省了你至少半天的项目初始化时间。3. 核心模块深度解析与实现3.1 增强型MCP服务器封装 (mcp-server.ts)这是模板的“心脏”。让我们深入看看它可能做了哪些增强以下代码为基于常见实践的推测和补充// src/server/mcp-server.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolResult, ErrorCode, ListToolsResult, Tool } from modelcontextprotocol/sdk/types.js; import { createLogger, format, transports } from winston; // 示例添加日志 export class MCPServer { private server: Server; private transport: StdioServerTransport; private logger; constructor(serverName: string) { // 1. 初始化日志系统 this.logger createLogger({ level: info, format: format.combine(format.timestamp(), format.json()), transports: [new transports.Console()], }); // 2. 创建官方Server实例 this.server new Server( { name: serverName, version: 1.0.0 }, { capabilities: { tools: {}, resources: {} } } // 初始能力后续动态注册 ); // 3. 创建传输层标准输入输出适配Claude Desktop等客户端 this.transport new StdioServerTransport(); // 4. 绑定连接和错误处理 this.server.onerror (error) { this.logger.error(MCP Server error:, error); }; // 5. 可选的统一工具调用错误处理中间件 this.setupErrorHandling(); } // 注册工具的统一入口 async registerTool(tool: Tool) { // 这里可以添加工具名冲突检查、日志记录等逻辑 this.logger.info(Registering tool: ${tool.name}); this.server.setRequestHandler(ListToolsRequestSchema, async () { // 动态更新工具列表 return { tools: [tool] }; }); // 更常见的做法是在server初始化后通过某个管理器统一添加 } // 封装启动逻辑 async start() { try { await this.server.connect(this.transport); this.logger.info(MCP Server started successfully.); } catch (error) { this.logger.error(Failed to start MCP Server:, error); process.exit(1); } } // 私有方法设置统一的错误处理 private setupErrorHandling() { // 可以拦截所有工具调用进行try-catch包装返回格式化的错误信息 // 确保任何工具抛出的异常都不会导致服务器崩溃而是返回一个规范的CallToolResult } }关键设计解析日志集成生产级应用必须拥有可观测性。封装层集成Winston或Pino等日志库可以结构化地记录服务器生命周期事件、工具调用请求和结果方便调试和监控。错误处理增强原版SDK的错误处理可能比较基础。封装层可以捕获未预料的异常将其转换为MCP协议规定的错误响应{ error: ... }防止服务器进程因单个工具崩溃而退出。生命周期管理start()方法封装了连接和启动逻辑并进行了错误处理。未来还可以在此处加入健康检查、优雅关闭graceful shutdown的逻辑。中心化的注册点虽然上面的registerTool方法是一个简化示例但思想是提供一个清晰、统一的方法来管理所有工具而不是让工具散落在各处。3.2 工具Tool的实现范式与示例工具是MCP的灵魂是AI助手可以调用的“函数”。模板在src/tools/example-tool.ts中提供了一个典范。// src/tools/example-tool.ts import { Tool } from modelcontextprotocol/sdk/types.js; // 1. 定义工具的参数Schema基于JSON Schema const GetWeatherSchema { type: object, properties: { city: { type: string, description: The name of the city to get weather for, e.g., London or New York. }, units: { type: string, enum: [metric, imperial], description: The unit system for the temperature. Default is metric., default: metric } }, required: [city], additionalProperties: false // 严格模式禁止额外参数 } as const; // 2. 定义工具元数据 export const GetWeatherTool: Tool { name: get_weather, // 工具名通常用蛇形命名 description: Fetches the current weather conditions for a specified city., inputSchema: GetWeatherSchema // 关联输入参数定义 }; // 3. 定义工具的执行函数Handler export async function handleGetWeather(args: { city: string; units?: metric | imperial }): Promiseany { const { city, units metric } args; // 参数验证Schema已做基础验证这里可做业务逻辑验证 if (!city.trim()) { throw new Error(City name cannot be empty.); } // 模拟或真实调用外部API // 在实际项目中这里可能是调用OpenWeatherMap、WeatherAPI等 const mockTemperature units metric ? 22°C : 72°F; const mockCondition Partly Cloudy; // 返回结构化的结果AI助手能很好地解析 return { content: [ { type: text, text: The current weather in ${city} is ${mockCondition} with a temperature of ${mockTemperature}. } ], // 可以附加更多结构化数据供AI后续推理使用 _meta: { city, temperature: mockTemperature, condition: mockCondition, units, fetchedAt: new Date().toISOString() } }; }实现要点与最佳实践清晰的Schema定义使用as const断言确保类型推断准确。description字段至关重要它是AI理解工具用途和参数含义的主要依据应尽可能详细、准确。严格的输入验证additionalProperties: false能防止AI传递未定义的参数避免意外行为。在Handler中可以进行二次业务验证。结构化的输出返回结果不仅包含给用户看的文本content[0].text还可以在_meta等字段中包含原始数据。这给了AI更大的灵活性例如它可以选择用不同的方式向用户呈现或者利用这些数据进行下一步计算。错误处理Handler函数中应使用throw new Error()来抛出错误。这些错误应该在服务器封装层被捕获并转换成规范的错误响应。注意事项工具名 (name) 在整个MCP服务器中必须唯一。建议使用动词开头、蛇形命名法snake_case如search_web,create_file这与AI助手调用工具的自然语言描述方式更匹配。3.3 资源Resource的抽象与文件系统示例资源代表了AI可以读取的“数据源”。ts-mcp模板通常会包含一个文件系统资源的示例因为它是最通用、最直接的需求之一。// src/resources/file-resource.ts import { Resource, ResourceTemplate } from modelcontextprotocol/sdk/types.js; import fs from fs/promises; import path from path; import { fileURLToPath } from url; // 1. 定义资源模板用于动态生成资源URI export const FileResourceTemplate: ResourceTemplate { uriTemplate: file://{path}, name: File from local filesystem, description: Read contents of a file from the local machine., mimeType: text/plain // 默认MIME类型可根据文件扩展名动态判断 }; // 2. 资源列表提供函数告诉客户端有哪些资源可用 export async function listFileResources(basePath: string process.cwd()): PromiseResource[] { // 这是一个简单示例列出当前目录下的所有.txt文件作为资源 // 警告在生产环境中必须严格限制可访问的路径范围 const files await fs.readdir(basePath); const textFiles files.filter(f f.endsWith(.txt)); return textFiles.map(file ({ uri: file://${path.join(basePath, file)}, name: File: ${file}, description: Contents of ${file}, mimeType: text/plain })); } // 3. 资源内容读取函数Handler export async function readFileResource(uri: string): Promise{ contents: Array{ type: string; text: string } } { // 解析URI获取文件路径 const url new URL(uri); if (url.protocol ! file:) { throw new Error(Unsupported protocol: ${url.protocol}); } const filePath fileURLToPath(uri); // 安全地将file:// URL转换为路径 // 重要的安全边界检查防止目录遍历攻击 const resolvedPath path.resolve(filePath); const allowedBase path.resolve(process.cwd()); // 限制在当前工作目录内 if (!resolvedPath.startsWith(allowedBase)) { throw new Error(Access to path ${resolvedPath} is not allowed.); } // 读取文件内容 const content await fs.readFile(resolvedPath, utf-8); return { contents: [ { type: text, text: content } ] }; }安全与设计考量URI模板file://{path}是一个标准的文件URI模板。{path}表示一个路径变量客户端如AI可以请求file:///Users/me/project/README.md。绝对的安全边界这是资源实现中最重要的部分。readFileResource函数中必须进行路径解析和边界检查startsWith。永远不要相信客户端传入的URI路径必须将其解析并限制在预先定义好的安全目录如项目根目录、某个特定数据目录内否则将造成严重的任意文件读取漏洞。动态MIME类型示例中写死了text/plain。更完善的实现应该根据文件扩展名如.json,.md,.html返回对应的MIME类型帮助AI更好地理解内容格式。资源列表的动态性listFileResources展示了如何动态生成资源列表。你可以根据数据库查询结果、API响应等来动态创建资源让AI助手能“看到”不断变化的数据集。4. 从零到一的完整开发与集成流程4.1 环境准备与项目初始化假设你已经安装了Node.js建议LTS版本如18.x或20.x和npm或yarn/pnpm。# 1. 克隆模板仓库这里以假设的仓库地址为例 git clone https://github.com/haneiva1/ts-mcp.git my-mcp-server cd my-mcp-server # 2. 安装依赖 npm install # 3. 探索项目结构 # 打开src/目录熟悉index.ts, server/, tools/, resources/的结构 # 4. 运行开发模式测试示例是否工作 npm run dev运行npm run dev后你的MCP服务器就会在标准输入输出stdio上启动并等待连接。此时它还没有被任何客户端使用。4.2 开发你的第一个自定义工具一个“项目文件搜索”工具让我们基于模板添加一个实用的工具在项目目录中搜索包含特定关键词的文件。步骤一创建工具文件在src/tools/目录下新建search-files.ts。// src/tools/search-files.ts import { Tool } from modelcontextprotocol/sdk/types.js; import fs from fs/promises; import path from path; // 定义Schema const SearchFilesSchema { type: object, properties: { keyword: { type: string, description: The text or keyword to search for within project files. }, fileExtension: { type: string, description: Optional filter by file extension (e.g., .ts, .md). Leave empty to search all files., default: }, maxResults: { type: number, description: Maximum number of results to return. Default is 10., default: 10, minimum: 1, maximum: 50 } }, required: [keyword], additionalProperties: false } as const; // 定义工具 export const SearchFilesTool: Tool { name: search_project_files, description: Searches for a keyword within text files in the current project directory. Useful for finding code snippets, documentation, or specific configurations., inputSchema: SearchFilesSchema }; // 实现Handler export async function handleSearchFiles(args: { keyword: string; fileExtension?: string; maxResults?: number; }): Promiseany { const { keyword, fileExtension , maxResults 10 } args; const searchDir process.cwd(); // 搜索当前工作目录 const results: Array{file: string, line: number, snippet: string} []; // 一个简单的递归文件搜索函数忽略node_modules等目录 async function searchInDirectory(dirPath: string) { const entries await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath path.join(dirPath, entry.name); // 跳过node_modules和.git目录 if (entry.isDirectory()) { if ([node_modules, .git, dist].includes(entry.name)) { continue; } await searchInDirectory(fullPath); // 递归搜索子目录 } else if (entry.isFile()) { // 检查文件扩展名过滤 if (fileExtension !entry.name.endsWith(fileExtension)) { continue; } // 只搜索文本文件简单判断 const textExtensions [.txt, .md, .js, .ts, .json, .yml, .yaml, .html, .css]; if (!textExtensions.some(ext entry.name.endsWith(ext))) { continue; } try { const content await fs.readFile(fullPath, utf-8); const lines content.split(\n); lines.forEach((line, index) { if (line.includes(keyword)) { // 计算相对路径更友好 const relativePath path.relative(searchDir, fullPath); results.push({ file: relativePath, line: index 1, snippet: line.trim().substring(0, 100) // 截取片段 }); } }); } catch (error) { // 忽略无法读取的文件如二进制文件 console.warn(Could not read file ${fullPath}:, error.message); } } } } await searchInDirectory(searchDir); // 排序和限制结果数量 const sortedResults results.slice(0, maxResults); if (sortedResults.length 0) { return { content: [{ type: text, text: No files found containing the keyword ${keyword}${fileExtension ? with extension ${fileExtension} : }. }] }; } // 格式化输出为Markdown表格便于AI和用户阅读 const resultTable | File | Line | Snippet | |------|------|---------| ${sortedResults.map(r | ${r.file} | ${r.line} | ${r.snippet} |).join(\n)}; return { content: [{ type: text, text: Found ${sortedResults.length} result(s) for keyword ${keyword}:\n\n${resultTable} }], _meta: { keyword, totalMatches: results.length, resultsReturned: sortedResults.length, results: sortedResults // 提供结构化数据供AI进一步处理 } }; }步骤二注册新工具修改src/tools/index.ts文件导出新工具。// src/tools/index.ts export { GetWeatherTool, handleGetWeather } from ./example-tool.js; // 注意导入编译后的.js文件或在dev模式下配置ts-node等 export { SearchFilesTool, handleSearchFiles } from ./search-files.js; // 统一导出工具列表和处理器映射 import type { Tool } from modelcontextprotocol/sdk/types.js; export const tools: Tool[] [GetWeatherTool, SearchFilesTool]; // 工具名到处理函数的映射 import type { CallToolRequest } from modelcontextprotocol/sdk/types.js; export const toolHandlers: Recordstring, (args: any) Promiseany { [GetWeatherTool.name]: handleGetWeather, [SearchFilesTool.name]: handleSearchFiles, };步骤三在主入口中集成修改src/index.ts确保新工具被服务器注册。// src/index.ts (部分) import { MCPServer } from ./server/mcp-server.js; import { tools, toolHandlers } from ./tools/index.js; import { listFileResources, readFileResource } from ./resources/index.js; async function main() { const server new MCPServer(my-awesome-mcp-server); // 注册所有工具 for (const tool of tools) { // 这里调用server上自定义的注册方法或直接使用原版server.setRequestHandler // 假设我们的MCPServer类有一个registerToolAndHandler方法 server.registerToolAndHandler(tool, toolHandlers[tool.name]); } // 注册资源相关处理器示例 server.setRequestHandler(ListResourcesRequestSchema, async () { const fileResources await listFileResources(); return { resources: fileResources }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) { if (request.params.uri.startsWith(file://)) { return await readFileResource(request.params.uri); } throw new Error(Unsupported resource URI: ${request.params.uri}); }); await server.start(); } main().catch(console.error);4.3 与Claude Desktop集成测试开发完成后最关键的一步是实际测试。Claude Desktop是体验MCP最直接的方式。配置Claude Desktop找到Claude Desktop的配置目录。在macOS上通常是~/Library/Application Support/Claude/claude_desktop_config.json在Windows上是%APPDATA%\Claude\claude_desktop_config.json。编辑或创建这个JSON配置文件。添加你的MCP服务器配置{ mcpServers: { my-ts-server: { command: node, args: [ /absolute/path/to/your/ts-mcp-project/dist/index.js ], env: { NODE_ENV: production } } } }重要args中的路径必须是绝对路径指向你编译后的入口文件dist/index.js。在开发时你也可以直接指向src/index.ts并用tsx运行但生产配置建议用编译后的版本。重启Claude Desktop应用配置。进行测试打开Claude Desktop新建一个对话。如果配置成功Claude通常会主动打招呼并提及它可以使用的新工具例如“我可以帮你搜索项目文件了”。尝试输入“你能用search_project_files工具帮我找一下项目里所有用到interface关键词的TypeScript文件吗”Claude应该会理解你的意图调用工具并返回格式化的搜索结果。5. 高级主题、调试与生产部署5.1 实现更复杂的工具调用外部API许多MCP工具需要与外部服务交互。下面以实现一个调用GitHub API查询仓库信息的工具为例展示如何处理异步请求、API密钥和安全问题。// src/tools/github-tool.ts import { Tool } from modelcontextprotocol/sdk/types.js; import fetch from node-fetch; // 需要安装: npm install node-fetch2 // 安全地读取环境变量中的API密钥 const GITHUB_TOKEN process.env.GITHUB_TOKEN; const GetGitHubRepoSchema { type: object, properties: { owner: { type: string, description: The owner (username or organization) of the repository. }, repo: { type: string, description: The name of the repository. } }, required: [owner, repo], additionalProperties: false } as const; export const GetGitHubRepoTool: Tool { name: get_github_repo_info, description: Fetches detailed information about a GitHub repository, including description, stars, forks, and latest commit., inputSchema: GetGitHubRepoSchema }; export async function handleGetGitHubRepo(args: { owner: string; repo: string }): Promiseany { const { owner, repo } args; if (!GITHUB_TOKEN) { throw new Error(GitHub API token is not configured. Please set the GITHUB_TOKEN environment variable.); } const apiUrl https://api.github.com/repos/${owner}/${repo}; try { const response await fetch(apiUrl, { headers: { Authorization: token ${GITHUB_TOKEN}, User-Agent: My-MCP-Server, // GitHub API要求User-Agent Accept: application/vnd.github.v3json } }); if (!response.ok) { const errorText await response.text(); throw new Error(GitHub API request failed: ${response.status} ${response.statusText}. ${errorText}); } const repoData await response.json(); return { content: [{ type: text, text: **Repository:** ${repoData.full_name}\n **Description:** ${repoData.description || No description}\n **⭐ Stars:** ${repoData.stargazers_count}\n ** Forks:** ${repoData.forks_count}\n ** Language:** ${repoData.language}\n ** URL:** ${repoData.html_url}\n **Last Updated:** ${new Date(repoData.updated_at).toLocaleDateString()} }], _meta: repoData // 传递完整数据供AI深度分析 }; } catch (error: any) { // 网络错误或API错误 throw new Error(Failed to fetch repository info: ${error.message}); } }关键点环境变量管理API密钥、令牌等敏感信息必须通过环境变量process.env传入绝对不要硬编码在源码中。可以使用dotenv包在开发时从.env文件加载。完善的错误处理检查HTTP响应状态码并提供有意义的错误信息。区分网络错误、认证错误和业务错误。速率限制和重试对于生产环境需要考虑API的速率限制并实现指数退避等重试机制。5.2 调试技巧与问题排查开发MCP服务器时调试可能有点棘手因为它是通过stdio与客户端通信的。启用详细日志在MCPServer构造函数中将日志级别设置为debug或silly记录所有进出的JSON-RPC消息。this.logger createLogger({ level: debug, // 改为debug级别 // ... 其他配置 });独立测试工具函数在开发时可以暂时修改src/index.ts直接调用handleSearchFiles({ keyword: test })并console.log结果确保工具逻辑本身正确无需通过MCP协议。使用MCP InspectorAnthropic官方提供了一个强大的调试工具叫MCP Inspector。你可以通过它来连接和测试你的MCP服务器像“瑞士军刀”一样查看所有工具、资源并手动触发调用观察原始的协议消息。安装npm install -g modelcontextprotocol/inspector运行mcp-inspector然后按照提示配置你的服务器命令如node /path/to/your/dist/index.js。它会提供一个Web界面让你可以交互式地测试所有功能。常见错误与排查ECONNREFUSED或连接错误确保你的服务器脚本正在运行并且Claude Desktop配置中的命令和路径完全正确特别是绝对路径。工具未出现检查Claude Desktop的配置JSON格式是否正确重启Claude Desktop。查看服务器日志确认工具注册阶段没有抛出异常。“Internal server error”查看服务器日志通常是工具Handler函数中抛出了未捕获的异常。确保所有异步操作都有try-catch并且错误被正确抛出为Error对象。权限问题文件资源确保你的服务器进程有权限读取它试图访问的文件和目录。5.3 生产环境部署考量当你准备将MCP服务器分享给团队或部署到更稳定的环境时需要考虑以下几点构建与打包运行npm run build生成优化后的dist/目录。考虑使用pkg或nexe将你的Node.js项目打包成单个可执行文件简化部署。但要注意这可能会使路径处理如__dirname变得复杂。进程管理使用进程管理器如PM2来保持服务器常驻运行并在崩溃时自动重启。PM2配置示例 (ecosystem.config.js)module.exports { apps: [{ name: my-mcp-server, script: ./dist/index.js, instances: 1, autorestart: true, watch: false, max_memory_restart: 500M, env: { NODE_ENV: production, GITHUB_TOKEN: your_token_here // 在生产环境中通过PM2环境注入 } }] };启动pm2 start ecosystem.config.js配置管理将所有配置如API端点、令牌、允许的文件路径外部化使用环境变量或配置文件如config/production.json。对于团队可以考虑使用dotenv加载不同环境.env.production,.env.staging的变量。安全加固资源访问再次审查所有资源如file-resource.ts的路径安全检查逻辑确保没有任何目录遍历漏洞。工具权限考虑为不同的工具实现简单的权限控制。例如某些危险工具如“执行Shell命令”只能在特定的、受信任的环境下启用。输入验证除了JSON Schema验证在工具Handler内部对输入进行二次验证和清理防止注入攻击。监控与告警集成像Sentry这样的错误跟踪服务捕获生产环境中的未处理异常。使用PM2或系统工具监控进程的CPU和内存使用情况。记录详细的访问日志和工具调用日志便于审计和问题诊断。ts-mcp模板为你提供了一个坚固的起点但将其转化为一个健壮的生产级服务还需要你根据具体的业务逻辑和安全要求在上述方面进行细化和加强。它的价值在于让你跳过了从零搭建框架的繁琐直接进入最有价值的业务逻辑开发阶段。