
30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度最近在尝试将大模型接入本地工具时发现不同模型、不同框架的集成方式五花八门配置起来相当繁琐。尤其是在处理流式输出、工具调用和上下文管理时经常需要为每个项目重复编写大量胶水代码。如果你也遇到过类似问题那么Model Context Protocol (MCP)可能就是那个你一直在寻找的标准化解决方案。本文将深入解析 MCP 的核心概念并手把手带你从零开始基于MCP Server Boot Starters快速构建一个支持流式输出的服务器让你能轻松地将任何本地工具或数据源暴露给大模型使用。1. MCP 核心概念为什么需要它在深入代码之前我们首先要理解 MCP 解决了什么问题。简单来说MCP 是一个开放协议旨在为大模型LLM与外部工具、数据源之间提供标准化的通信桥梁。想象一下这个场景你开发了一个股票查询工具、一个数据库客户端和一个文件搜索工具。现在你想让 ChatGPT、Claude 或本地部署的 Llama 模型能够使用这些工具。在没有 MCP 之前你可能需要为每个模型OpenAI API, Anthropic API, Ollama 等分别编写适配器。处理不同的工具调用格式如 OpenAI 的function calling, Anthropic 的tools。自己管理上下文、状态和错误处理。实现复杂的流式响应机制以提供更好的用户体验。这个过程不仅重复劳动而且难以维护和扩展。MCP 的出现正是为了将工具/数据提供方Server与模型/客户端Client解耦。它定义了一套标准的 JSON-RPC over STDIO/HTTP 接口使得工具开发者只需实现一次 MCP Server其工具就能被任何兼容 MCP 的客户端如 Claude Desktop, Cursor 等使用。客户端/模型侧只需实现一次 MCP Client就能接入无数个 MCP Server 提供的工具和资源。MCP 的核心组件MCP Server: 工具或数据的提供方。它向客户端宣告自己提供了哪些“工具”Tools即可调用的函数和“资源”Resources即可读取的上下文数据。MCP Client: 通常是模型运行时环境如 AI 应用。它发现并调用 Server 提供的工具和资源将结果整合到模型上下文中。传输层: 默认为 JSON-RPC over STDIO标准输入输出也支持 HTTP 和 SSEServer-Sent Events。这保证了跨语言、跨进程的通用性。Streamable-HT 是什么在 MCP 的上下文中“Streamable” 指的是支持流式传输Streaming的能力。这对于需要长时间运行或逐步产生结果的工具至关重要例如执行一个长时间脚本、监控日志文件、生成大量文本。HT可能指代某种特定的处理类型或传输方式在后续实践中我们会看到其具体表现。简单理解一个 Streamable 的 MCP Server 能够将工具执行结果以“数据块”的形式实时推送给客户端而不是等待全部执行完毕再一次性返回。接下来我们将聚焦于如何快速构建一个这样的 MCP Server。2. 环境准备与项目初始化我们将使用TypeScript/JavaScript生态来构建 MCP Server这是目前 MCP 社区最活跃、工具链最成熟的选择。确保你的开发环境满足以下要求基础环境Node.js: 版本 18 或更高推荐 LTS 版本如 20.x。这是运行 JavaScript 代码的引擎。npm或yarn或pnpm: 包管理工具。本文示例使用npm。代码编辑器: VS Code 或其他你熟悉的 IDE。初始化项目首先创建一个新的项目目录并初始化一个 Node.js 项目。# 1. 创建项目目录并进入 mkdir my-mcp-streaming-server cd my-mcp-streaming-server # 2. 初始化 package.json一路回车或按需填写 npm init -y # 3. 安装 TypeScript 及相关类型定义我们将用 TypeScript 获得更好的开发体验 npm install typescript types/node --save-dev # 4. 初始化 TypeScript 配置 npx tsc --init生成的tsconfig.json文件需要调整以适配现代 Node.js 开发。一个推荐的配置如下{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, lib: [ES2022], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, declarationMap: true }, include: [src/**/*], exclude: [node_modules, dist] }安装 MCP 核心依赖MCP 的核心 SDK 由 Anthropic 官方维护。我们还需要安装modelcontextprotocol/sdk。npm install modelcontextprotocol/sdk现在项目的基础骨架已经搭建完成。接下来我们将引入能极大提升开发效率的利器——MCP Server Boot Starters。3. 认识 MCP Server Boot Starters手动从零开始实现一个 MCP Server 需要处理 JSON-RPC 通信、生命周期管理、工具注册等底层细节虽然可行但效率不高。MCP Server Boot Starters 可以理解为 MCP Server 的“脚手架”或“快速启动模板”它封装了这些通用逻辑让开发者能专注于实现工具Tools和资源Resources的业务逻辑。目前社区已有一些优秀的 Boot Starters 项目。一个典型的例子是modelcontextprotocol/server-boot请注意这是一个假设的包名用于说明概念。实际开发中你可能需要寻找或参考类似mcp-server-starter这样的开源模板。其核心价值在于标准化入口: 提供了统一的 Server 类继承后只需重写initialize等方法。生命周期管理: 自动处理 Server 的启动、停止、与客户端的握手协议。工具/资源注册简化: 提供清晰的 API 来声明工具和资源。传输层抽象: 简化了 STDIO、HTTP 等传输方式的配置。类型安全: 配合 TypeScript提供完整的类型提示减少错误。为什么使用 Starter对于新手它能帮你跳过复杂的协议实现直接进入业务开发。对于有经验的开发者它能保证项目结构符合最佳实践便于维护和协作。在本文中我们将基于一个类似 Starter 的理念构建一个结构清晰、支持流式输出的 MCP Server。4. 构建支持流式输出的 MCP Server我们将创建一个模拟的“系统监控”服务器它提供两个工具get_cpu_usage: 获取当前 CPU 使用率一次性调用。tail_logs: “尾随”查看一个模拟日志文件的内容流式输出。4.1 项目结构设计首先创建以下目录和文件结构my-mcp-streaming-server/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # 服务器主入口 │ ├── server/ # 服务器核心逻辑 │ │ ├── index.ts │ │ ├── StreamingMonitorServer.ts # 我们的 MCP 服务器类 │ │ └── tools/ # 工具实现 │ │ ├── index.ts │ │ ├── CpuTool.ts │ │ └── LogTailTool.ts │ └── types/ # 类型定义 │ └── index.ts └── dist/ # TypeScript 编译输出目录4.2 定义工具参数类型在src/types/index.ts中我们定义工具调用时需要的参数类型这有助于 TypeScript 进行类型检查。// src/types/index.ts /** * tail_logs 工具的参数类型 */ export interface TailLogsArgs { /** * 要监控的日志文件路径模拟 */ filePath: string; /** * 要输出的日志行数 */ lines?: number; } /** * get_cpu_usage 工具的参数类型此工具无需参数但保留结构 */ export interface GetCpuUsageArgs { // 目前没有参数但保持接口定义以备扩展 [key: string]: never; } /** * 所有工具参数的联合类型 */ export type ToolArgs TailLogsArgs | GetCpuUsageArgs;4.3 实现流式日志工具这是本文的核心我们将展示如何实现一个支持流式输出的工具。关键在于使用 MCP SDK 提供的ToolResult和Content类型并返回一个Promise该 Promise 解析为一个包含isDelta标志的结果。// src/server/tools/LogTailTool.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { CallToolRequest } from modelcontextprotocol/sdk/types.js; import { TailLogsArgs } from ../../types/index.js; /** * 模拟尾随日志文件的工具 * 这是一个支持流式输出的工具示例。 */ export class LogTailTool { // 工具的名称客户端通过此名称调用 static toolName tail_logs; // 工具的描述帮助模型理解其用途 static toolDescription Tail (follow) a log file and stream its new contents in real time.; // 工具的参数模式定义JSON Schema static inputSchema { type: object, properties: { filePath: { type: string, description: Path to the log file to tail (simulated)., }, lines: { type: number, description: Number of historical lines to output first (default: 10)., default: 10, }, }, required: [filePath], }; /** * 处理工具调用 * param server MCP Server 实例 * param request 工具调用请求 * returns 返回一个 Promise解析为流式结果 */ static async handler( server: Server, request: CallToolRequest ) { const args request.params.arguments as unknown as TailLogsArgs; const filePath args.filePath; const lines args.lines || 10; // 模拟读取文件并流式返回 // 在实际应用中这里可能是 fs.createReadStream, 子进程 stdout 等。 return new Promise(async (resolve) { // 1. 首先发送一个初始消息包含文件信息和开始提示 resolve({ content: [ { type: text, text: 开始监控日志文件: ${filePath}\n先显示最后 ${lines} 行\n${-.repeat(50)}\n, }, ], // isDelta: true 表明这是一个流式响应的开始后续还会有更多数据块。 isDelta: true, }); // 2. 模拟流式发送历史日志行快速发送 const historicalLogs this.generateLogs(lines, HISTORICAL); for (const log of historicalLogs) { // 使用 server 的 sendNotification 或其他机制发送增量数据。 // 注意标准 MCP 工具调用结果返回后通常通过独立的 notifications 或后续调用来发送流数据。 // 这里为了简化演示我们模拟在 handler 返回后异步发送数据。 // 更精确的实现可能需要使用 Server-Sent Events (SSE) 或 WebSocket具体取决于传输层。 // 以下代码演示了概念实际流式传输需要客户端和服务器端共同支持。 setTimeout(() { // 在实际流式传输中你会通过一个持久的连接发送这些数据块。 // 例如response.write(data: ${JSON.stringify({ delta: log })}\n\n); console.log([Stream Delta], log); // 此处模拟发送数据块 }, Math.random() * 100); // 随机延迟模拟网络波动 } // 3. 模拟持续产生新的日志行间隔发送 let counter 0; const intervalId setInterval(() { counter; const newLog this.generateLogLine(STREAMING, counter); console.log([Stream Delta], newLog); // 模拟发送新的数据块 // 模拟流式传输 5 条新日志后停止 if (counter 5) { clearInterval(intervalId); // 发送流结束标记在实际协议中可能有特定消息 console.log([Stream End], \n${-.repeat(50)}\n日志流监控结束。); } }, 1000); // 每秒一条新日志 // 注意实际的 MCP 流式传输需要更精细的控制例如使用 ReadableStream 或类似的抽象。 // 上述 setTimeout 和 setInterval 仅用于演示流式数据生成的逻辑。 }); } /** * 生成模拟的历史日志 */ private static generateLogs(count: number, type: string): string[] { const logs: string[] []; for (let i 1; i count; i) { logs.push(this.generateLogLine(type, i)); } return logs; } /** * 生成单条模拟日志行 */ private static generateLogLine(type: string, index: number): string { const timestamps [2023-10-27 08:00:01, 2023-10-27 08:00:05, 2023-10-27 08:00:12]; const messages [ User login from IP 192.168.1.100, Database query executed successfully., Cache miss for key: user_profile_12345, Scheduled task cleanup_temp started., API /api/v1/data responded in 156ms., ]; const randomTime timestamps[Math.floor(Math.random() * timestamps.length)]; const randomMsg messages[Math.floor(Math.random() * messages.length)]; return [${randomTime}] [INFO] (${type}_${index}) ${randomMsg}; } }关键点解析isDelta: true: 在返回的ToolResult中设置此标志是向客户端表明此工具的响应是流式的初始返回的内容只是第一部分。异步数据推送: 真正的流式数据需要在工具调用返回后通过其他通道如 Server-Sent Events, WebSocket或 MCP 协议中定义的特定通知持续推送给客户端。上面的console.log模拟了这种推送。在实际的 HTTPSSE 传输层中你会直接向响应流写入数据。模式定义 (inputSchema): 使用 JSON Schema 清晰地定义工具参数这对客户端尤其是大模型理解如何调用工具至关重要。4.4 实现普通 CPU 工具作为对比我们实现一个普通的、一次性返回结果的工具。// src/server/tools/CpuTool.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { CallToolRequest } from modelcontextprotocol/sdk/types.js; import { GetCpuUsageArgs } from ../../types/index.js; /** * 获取 CPU 使用率的工具 * 这是一个普通非流式工具示例。 */ export class CpuTool { static toolName get_cpu_usage; static toolDescription Get the current system CPU usage percentage (simulated).; static inputSchema { type: object, properties: { // 这个工具目前不需要参数但 schema 仍需是一个对象 }, required: [], }; static async handler( server: Server, request: CallToolRequest ) { // 模拟获取 CPU 使用率 const usage Math.floor(Math.random() * 30) 10; // 10% 到 40% const timestamp new Date().toISOString(); return { content: [ { type: text, text: CPU Usage Report (Simulated):\nTimestamp: ${timestamp}\nUsage: ${usage}%\nStatus: ${usage 70 ? High Load : Normal}, }, ], // 对于非流式工具不设置 isDelta 或设置为 false默认 }; } }4.5 创建主服务器类现在我们将工具整合到一个 MCP Server 类中并处理服务器的初始化。// src/server/StreamingMonitorServer.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { ListToolsRequestSchema, CallToolRequestSchema, } from modelcontextprotocol/sdk/types.js; import { CpuTool } from ./tools/CpuTool.js; import { LogTailTool } from ./tools/LogTailTool.js; /** * 我们的自定义 MCP 服务器支持流式工具。 */ export class StreamingMonitorServer { private server: Server; constructor() { // 1. 创建 MCP Server 实例 this.server new Server( { name: streaming-monitor-server, version: 0.1.0, }, { capabilities: { // 声明服务器支持的工具 tools: {}, }, } ); // 2. 设置请求处理器 this.setupHandlers(); // 3. 连接传输层这里使用 STDIO适用于 Claude Desktop 等客户端 this.setupTransport(); } /** * 设置 JSON-RPC 请求处理器 */ private setupHandlers() { // 处理客户端查询可用工具的请求 this.server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: CpuTool.toolName, description: CpuTool.toolDescription, inputSchema: CpuTool.inputSchema, }, { name: LogTailTool.toolName, description: LogTailTool.toolDescription, inputSchema: LogTailTool.inputSchema, }, ], }; }); // 处理客户端调用工具的请求 this.server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; console.error([Server] Tool called: ${name}, args); // 日志输出到 stderr避免干扰 stdio 协议 switch (name) { case CpuTool.toolName: return await CpuTool.handler(this.server, request); case LogTailTool.toolName: return await LogTailTool.handler(this.server, request); default: throw new Error(Unknown tool: ${name}); } }); } /** * 设置传输层使用标准输入输出 */ private setupTransport() { const transport new StdioServerTransport(); this.server.connect(transport).catch((error) { console.error([Server] Failed to connect transport:, error); process.exit(1); }); } /** * 启动服务器 */ async run() { // 服务器在 connect 后会自动开始监听 STDIO console.error([Server] Streaming Monitor MCP Server is running on STDIO...); // 保持进程运行 process.on(SIGINT, () { this.server.close(); process.exit(0); }); } }4.6 创建程序入口最后在src/index.ts中启动我们的服务器。// src/index.ts import { StreamingMonitorServer } from ./server/StreamingMonitorServer.js; // 创建并运行服务器 const server new StreamingMonitorServer(); server.run().catch((error) { console.error([Server] Fatal error:, error); process.exit(1); });5. 构建、运行与测试5.1 构建 TypeScript 项目首先我们需要将 TypeScript 代码编译成 JavaScript。# 在项目根目录执行 npx tsc如果编译成功你会在dist/目录下看到生成的.js文件。5.2 通过 Node.js 直接运行测试为了快速测试服务器逻辑不涉及完整的 MCP 客户端我们可以创建一个简单的测试脚本。但请注意真正的 MCP 交互需要通过 STDIO 协议。一个更直接的测试方法是使用MCP 客户端调试工具例如modelcontextprotocol/sdk包中可能提供的工具或者第三方 CLI 工具如mcp-cli。这里我们演示如何通过修改入口文件让服务器在启动时模拟一个工具调用仅用于开发调试。创建src/test.ts记得在tsconfig.json的include中添加src/test.ts// src/test.ts - 开发环境下的自检 import { StreamingMonitorServer } from ./server/StreamingMonitorServer.js; const server new StreamingMonitorServer(); console.log(服务器初始化完成。); console.log(请使用兼容 MCP 的客户端如 Claude Desktop连接到此服务器。); console.log(确保在客户端配置中指定服务器命令为node /path/to/dist/index.js); // 保持进程运行 process.stdin.resume();编译并运行它确保没有启动错误npx tsc node dist/test.js你应该看到服务器等待连接的提示。5.3 与 MCP 客户端集成以 Claude Desktop 为例这是 MCP Server 的真正用途。以 Claude Desktop 为例找到 Claude Desktop 的配置目录macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.jsonLinux:~/.config/Claude/claude_desktop_config.json编辑配置文件添加你的 MCP Server{ mcpServers: { my-monitor: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/dist/index.js ], env: { NODE_ENV: production } } } }重要将/ABSOLUTE/PATH/TO/YOUR/PROJECT替换为你项目的绝对路径。重启 Claude Desktop。在 Claude 的聊天界面你应该能直接使用定义的工具例如“请帮我查看一下 CPU 使用率”或“尾随一下/var/log/system.log这个文件”。6. 常见问题与排查思路在开发和运行 MCP Server 时你可能会遇到以下问题问题现象可能原因排查思路与解决方案Claude Desktop 无法连接服务器1. 配置文件路径错误。2. Node.js 命令路径错误。3. 服务器脚本存在语法错误启动即崩溃。4. 端口或 STDIO 冲突。1. 检查claude_desktop_config.json格式和路径。2. 在终端中直接运行node /path/to/dist/index.js看是否有错误输出。3. 查看 Claude Desktop 的日志文件位置因系统而异。4. 确保服务器代码正确处理了 STDIO 传输。工具列表不显示或调用失败1.ListToolsRequest处理器未正确设置或返回格式错误。2. 工具的名称、描述、参数 Schema 不符合规范。3.CallToolRequest处理器中未正确匹配工具名。1. 在setupHandlers中检查ListToolsRequestSchema的处理逻辑。2. 使用简单的 JSON 验证工具检查inputSchema。3. 在CallToolRequest的switch语句中添加默认的console.error日志。流式输出不工作1. 客户端不支持流式传输。2. 服务器返回的ToolResult中isDelta设置不正确。3. 流式数据推送的机制未实现或实现有误如未使用正确的传输通道。1. 确认你的客户端如 Claude Desktop版本是否支持流式工具。2. 确保流式工具的 handler 返回{ content: [...], isDelta: true }。3.深入研究 MCP 协议对于流式传输的具体定义。当前示例中的setInterval是模拟真正的实现可能需要基于 SSE 或 WebSocket 的传输层。查阅官方 SDK 和示例。服务器进程意外退出1. 未捕获的异常。2. 传输层连接断开。3. 进程收到终止信号。1. 在server.run()和connect()外包裹try-catch。2. 监听process的uncaughtException和unhandledRejection事件。3. 实现server.close()的优雅关闭逻辑。TypeScript 编译错误1. 类型不匹配。2.tsconfig.json配置问题。3. 模块导入路径错误。1. 仔细阅读错误信息修正类型。2. 确保使用NodeNext的模块解析策略。3. 检查import语句中的文件扩展名.js或.ts。7. 最佳实践与工程建议构建用于生产环境的 MCP Server 时请考虑以下建议清晰的工具定义名称使用snake_case清晰表达动作和对象如search_web,execute_sql_query。描述一句话准确描述功能让 LLM 能准确判断何时调用。参数 Schema务必详细。使用description字段解释每个参数的用途和示例。对于枚举值使用enum列出所有选项。健壮的错误处理在工具handler内部使用try-catch返回结构化的错误信息而不是让异常冒泡导致整个请求失败。错误信息应对用户友好同时包含可供调试的细节可记录在服务器日志中。try { // ... 业务逻辑 } catch (error: any) { return { content: [{ type: text, text: 操作失败: ${error.message} }], isError: true, // MCP 协议中可能用于标识错误结果 }; }资源管理对于文件操作、数据库连接、网络请求等确保在工具执行完毕后正确关闭资源避免泄漏。考虑为长时间运行的工具如tail_logs实现超时和取消机制。安全性输入验证永远不要信任客户端传入的参数。即使有 Schema也要在 handler 中再次进行边界检查和清理防止路径遍历../../../、命令注入等攻击。权限控制如果服务器提供敏感操作应实现认证和授权机制。MCP 协议本身不强制要求但可以在 Server 层面或通过传输层如 HTTPS添加。环境隔离考虑在沙箱或容器中运行工具尤其是执行任意代码或命令时。性能与可观测性日志记录记录工具调用、参数、耗时和结果注意脱敏。将日志输出到stderr或文件避免污染与客户端的协议通信stdio。指标监控对于高频工具可以考虑添加简单的指标如调用次数、平均延迟。流式传输优化对于真正的流式工具注意缓冲区管理避免内存溢出。考虑使用StreamAPI 或类似的异步迭代器。配置化将工具列表、服务器元数据名称、版本等提取到配置文件如config.json中便于不同环境部署。测试为每个工具编写单元测试模拟CallToolRequest。进行集成测试使用一个简单的 MCP 客户端脚本实际连接你的服务器并调用工具。通过遵循以上步骤和最佳实践你不仅可以构建出演示中的监控服务器更能将其模式应用到任何你想暴露给大模型的本地能力上例如代码库搜索、内部 API 调用、数据分析脚本等。MCP 的生态正在快速发展掌握其 Server 端开发意味着你能够为自己和团队打造出强大、可复用的 AI 原生工具链。 30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度