当前位置: 首页 > news >正文

TypeScript MCP服务器开发指南:从模板到AI工具集成实践

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)、调用云服务SDK(AWS SDK、Vercel CLI)、或者与现有的Node.js后端服务集成。

ts-mcp项目坚定地选择了TypeScript,这带来了几个显著优势:

  1. 类型安全:MCP协议本身有严格的数据结构定义(如ToolResourceCallToolResult)。TypeScript的接口和类型系统能确保你在实现工具时,输入输出的数据结构完全符合协议规范,极大减少了运行时错误。
  2. 出色的开发体验:现代IDE(如VSCode)对TypeScript的支持是无与伦比的,提供精准的代码补全、跳转和重构能力。这对于开发需要精确匹配协议定义的MCP工具来说,效率提升巨大。
  3. 庞大的npm生态:任何你需要的功能,几乎都能在npm上找到对应的、带有类型定义的库,可以快速集成到你的MCP服务器中。
  4. 与前端工具链无缝集成:如果你的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 工程化配置:开箱即用的现代开发环境

模板预置的工程化配置是其“生产就绪”宣称的重要支撑:

  1. TypeScript配置 (tsconfig.json):针对Node.js环境优化,开启了严格模式(strict: true),确保代码质量。输出目录设置为dist/,源码目录为src/,结构清晰。
  2. ESLint + Prettier(通常集成):模板集成了代码风格检查和自动格式化。统一的代码风格对于团队协作和项目可维护性至关重要。它强制你养成好的编码习惯,比如使用一致的引号、分号规则和缩进。
  3. 开发脚本 (package.json中的 scripts)
    • npm run dev:通常配置了类似tsxts-node-dev的工具,实现源码变更的热重载。你修改src/下的代码,服务器会自动重启,无需手动编译和重启,极大提升开发效率。
    • npm run build:将TypeScript编译为纯净的JavaScript到dist/目录,用于生产环境部署。
    • npm start:运行编译后的生产版本。
  4. 依赖管理:核心依赖@modelcontextprotocol/sdk被锁定在一个稳定的版本。模板的package.json通常会将typescripteslint等工具依赖放在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' }): Promise<any> { 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() } }; }

实现要点与最佳实践:

  1. 清晰的Schema定义:使用as const断言确保类型推断准确。description字段至关重要,它是AI理解工具用途和参数含义的主要依据,应尽可能详细、准确。
  2. 严格的输入验证additionalProperties: false能防止AI传递未定义的参数,避免意外行为。在Handler中可以进行二次业务验证。
  3. 结构化的输出:返回结果不仅包含给用户看的文本(content[0].text),还可以在_meta等字段中包含原始数据。这给了AI更大的灵活性,例如它可以选择用不同的方式向用户呈现,或者利用这些数据进行下一步计算。
  4. 错误处理: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()): Promise<Resource[]> { // 这是一个简单示例:列出当前目录下的所有.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; }): Promise<any> { 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: Record<string, (args: any) => Promise<any>> = { [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最直接的方式。

  1. 配置Claude Desktop

    • 找到Claude Desktop的配置目录。在macOS上通常是~/Library/Application Support/Claude/claude_desktop_config.json,在Windows上是%APPDATA%\Claude\claude_desktop_config.json
    • 编辑(或创建)这个JSON配置文件。
  2. 添加你的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运行,但生产配置建议用编译后的版本。

  3. 重启Claude Desktop:应用配置。

  4. 进行测试

    • 打开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-fetch@2 // 安全地读取环境变量中的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 }): Promise<any> { 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.v3+json' } }); 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与客户端通信的。

  1. 启用详细日志:在MCPServer构造函数中,将日志级别设置为debugsilly,记录所有进出的JSON-RPC消息。

    this.logger = createLogger({ level: 'debug', // 改为debug级别 // ... 其他配置 });
  2. 独立测试工具函数:在开发时,可以暂时修改src/index.ts,直接调用handleSearchFiles({ keyword: 'test' })console.log结果,确保工具逻辑本身正确,无需通过MCP协议。

  3. 使用MCP Inspector:Anthropic官方提供了一个强大的调试工具叫MCP Inspector。你可以通过它来连接和测试你的MCP服务器,像“瑞士军刀”一样查看所有工具、资源,并手动触发调用,观察原始的协议消息。

    • 安装:npm install -g @modelcontextprotocol/inspector
    • 运行:mcp-inspector,然后按照提示配置你的服务器命令(如node /path/to/your/dist/index.js)。它会提供一个Web界面,让你可以交互式地测试所有功能。
  4. 常见错误与排查:

    • ECONNREFUSED或连接错误:确保你的服务器脚本正在运行,并且Claude Desktop配置中的命令和路径完全正确(特别是绝对路径)。
    • 工具未出现:检查Claude Desktop的配置JSON格式是否正确,重启Claude Desktop。查看服务器日志,确认工具注册阶段没有抛出异常。
    • “Internal server error”:查看服务器日志,通常是工具Handler函数中抛出了未捕获的异常。确保所有异步操作都有try-catch,并且错误被正确抛出为Error对象。
    • 权限问题(文件资源):确保你的服务器进程有权限读取它试图访问的文件和目录。

5.3 生产环境部署考量

当你准备将MCP服务器分享给团队或部署到更稳定的环境时,需要考虑以下几点:

  1. 构建与打包

    • 运行npm run build生成优化后的dist/目录。
    • 考虑使用pkgnexe将你的Node.js项目打包成单个可执行文件,简化部署。但要注意,这可能会使路径处理(如__dirname)变得复杂。
  2. 进程管理

    • 使用进程管理器如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
  3. 配置管理

    • 将所有配置(如API端点、令牌、允许的文件路径)外部化,使用环境变量或配置文件(如config/production.json)。
    • 对于团队,可以考虑使用dotenv加载不同环境(.env.production,.env.staging)的变量。
  4. 安全加固

    • 资源访问:再次审查所有资源(如file-resource.ts)的路径安全检查逻辑,确保没有任何目录遍历漏洞。
    • 工具权限:考虑为不同的工具实现简单的权限控制。例如,某些危险工具(如“执行Shell命令”)只能在特定的、受信任的环境下启用。
    • 输入验证:除了JSON Schema验证,在工具Handler内部对输入进行二次验证和清理,防止注入攻击。
  5. 监控与告警

    • 集成像Sentry这样的错误跟踪服务,捕获生产环境中的未处理异常。
    • 使用PM2或系统工具监控进程的CPU和内存使用情况。
    • 记录详细的访问日志和工具调用日志,便于审计和问题诊断。

ts-mcp模板为你提供了一个坚固的起点,但将其转化为一个健壮的生产级服务,还需要你根据具体的业务逻辑和安全要求,在上述方面进行细化和加强。它的价值在于,让你跳过了从零搭建框架的繁琐,直接进入最有价值的业务逻辑开发阶段。

http://www.jsqmd.com/news/825095/

相关文章:

  • 别用“中式美学”的遮羞布,掩盖《给阿嬷的情书》里的血与泪
  • 从零打造STM32G070RBT6核心板:原理图、PCB到焊接调试全流程复盘
  • 2026年元宝优化服务商TOP3权威测评:谁是品牌元宝优化的最佳合作伙伴? - 博客湾
  • 玻璃双边磨边机供应商技术对比分析
  • Vue项目实战:基于Highcharts与Canvas构建高性能实时频谱瀑布图
  • mysql如何利用内置聚合函数统计数据_mysql group_concat应用
  • 用Python和MATLAB仿真对比:一阶低通滤波器的截止频率到底怎么选?(附完整代码)
  • 告别裸机点灯:用STM32F103+TM1650打造一个可调亮度、带按键的智能数码管显示模块
  • 抖音无水印视频下载器:从入门到精通的完整指南
  • 宝塔面板如何禁止PHP执行文件_在特定目录设置禁止脚本运行
  • FAT文件系统
  • Ansys Lumerical | FDTD 与 INTERCONNECT 协同:构建光栅耦合器高效设计流程
  • 从零到一:用vue-drawing-canvas打造现代化绘图应用的实战指南
  • 车载电子系统电源与端口设计实战:从原理到EMC防护的完整方案
  • GC-LSTM实战:基于PyG Temporal的动态网络链路预测全流程解析
  • 【MySQL 数据库】视图
  • 世界风景名胜区必去的十大自然奇观有哪些
  • Neovim集成Gemini AI:CLI插件配置与自动化编程实践
  • 企业内统一管理多个项目的AI模型密钥与访问审计日志
  • 行业首个支持18语种双向实时同传的AI翻译系统,企业级部署需避开这7个隐蔽兼容性陷阱
  • 贪心算法的核心基石:选择与结构的艺术
  • 基于RAG架构的智能FAQ系统:从传统文档到智能对话的实战指南
  • 2026年Deepseek搜索结果优化服务商TOP3权威测评:谁能让品牌在DeepSeek中脱颖而出? - 博客湾
  • FL Studio 2025.2.5.5319中文安装激活安装激活图文教程
  • 基于CircuitPython与CLUE开发板的桌面自动浇花机器人DIY指南
  • 用8050三极管和FR107二极管,手把手教你搭建一个简易ZVS振荡电路(附实测波形)
  • 告别龟速!手把手教你用Motrix+Chrome插件免费提速下载百度网盘文件
  • 别再乱搜了!BitLocker恢复密钥对不上?可能是你的微软账户登录错了(附正确备份姿势)
  • 继承不是“拿来用“:is-a 关系与组合
  • 2026年文心一言GEO推广服务商TOP3权威测评:谁能让品牌在百度AI搜索中实现增长突破? - 博客湾