🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
最近在尝试将 AI 助手(如 Claude、Cursor)深度集成到我的本地开发工作流中时,遇到了一个核心痛点:如何让 AI 安全、可控地访问我本地的代码库、数据库、API 文档等私有资源?直接上传文件有泄露风险,手动复制粘贴又太低效。直到我发现了Model Context Protocol (MCP)这个新兴标准,以及围绕它构建的MCP Server生态,才找到了一个优雅的解决方案。
本文将以一个实战项目为例,手把手带你从零构建一个能够流式传输数据的 MCP Server。我们将深入 MCP 的核心概念,利用现成的Boot Starters快速搭建,并实现一个名为Streamable-HT的服务器,让 AI 助手能够实时、安全地读取你指定的本地文件内容。无论你是想提升开发效率,还是对 AI 智能体(Agent)的底层交互协议感兴趣,这篇文章都能为你提供一套完整、可落地的实操指南。
1. MCP 核心概念:为什么它是 AI 开发工作流的关键拼图?
在深入代码之前,我们必须先理解 MCP 解决了什么问题。你可以把它想象成 AI 世界的USB 协议或驱动程序框架。
Model Context Protocol (MCP)是一个开放协议,它定义了大型语言模型(LLM)或 AI 助手(我们称之为“客户端”)与外部工具、数据源(我们称之为“服务器”)之间进行通信的标准方式。其核心目标是:让 AI 能够安全、结构化地访问和使用外部资源和功能,而无需将敏感数据上传到云端。
在没有 MCP 之前,我们通常有两种方式让 AI 处理本地数据:
- 复制粘贴:将代码或日志片段粘贴到聊天窗口。这种方式琐碎、容易遗漏上下文,且无法处理动态数据。
- 使用特定插件的 API:某些 IDE 插件或工具提供了与 AI 交互的接口,但它们往往是封闭的、绑定特定编辑器的,缺乏通用性。
MCP 的出现改变了这一局面。它通过标准化的 JSON-RPC over stdio/SSE 协议,实现了:
- 解耦与通用性:任何实现了 MCP 协议的 AI 客户端(如 Claude Desktop、Cursor、Windsurf)都可以连接任何 MCP Server,反之亦然。
- 安全性:数据留在本地,通信过程可控,你可以精确配置 Server 允许访问哪些资源(如特定目录的文件、数据库的只读视图)。
- 能力扩展:Server 不仅可以提供数据(“工具”),还可以提供可执行的操作(“资源”),比如运行脚本、查询数据库、调用 API,极大地扩展了 AI 的能力边界。
在这个生态中,MCP Server就是我们要编写的程序,它扮演着“数据提供者”和“能力提供者”的角色。而Boot Starters则是帮助我们快速创建 MCP Server 的项目模板或脚手架工具。
2. 环境准备:构建你的第一个 MCP Server 需要什么?
为了完成本次实战,我们需要准备以下环境。请注意,本文示例将使用Node.js环境,因为其生态中有成熟的 MCP SDK 和工具链。
- 操作系统:Windows 10/11, macOS, 或 Linux 均可。本文命令以 macOS/Linux 的 bash 为例,Windows 用户可在 Git Bash 或 WSL 中运行。
- Node.js:版本 18 或更高。这是运行 MCP Server 的基石。你可以通过
node --version检查。 - 包管理工具:npm 或 yarn。本文使用 npm。
- 代码编辑器:VS Code、Cursor 或任何你熟悉的 IDE。
- AI 客户端(可选,用于测试):Claude Desktop 是体验 MCP 最便捷的方式之一。我们将用它来测试我们构建的 Server。
首先,确保你的 Node.js 环境就绪:
# 检查 Node.js 和 npm 版本 node --version npm --version # 如果未安装,请前往 Node.js 官网下载 LTS 版本安装接下来,我们创建一个项目目录并初始化:
mkdir my-streamable-mcp-server cd my-streamable-mcp-server npm init -y这会生成一个package.json文件,记录我们的项目依赖。
3. 利用 Boot Starters 快速初始化项目
“从头开始”编写一个符合 MCP 协议的 Server 涉及不少样板代码。幸运的是,社区提供了Boot Starters——即预先配置好的项目模板。我们将使用官方推荐的@modelcontextprotocol/sdk来创建我们的 Server。
安装 MCP SDK:
npm install @modelcontextprotocol/sdk虽然 SDK 没有提供像create-mcp-server这样的完整 CLI,但我们可以参考其官方示例和结构来构建。核心是理解一个 MCP Server 需要实现哪些接口。为了更贴近实战,我们直接开始构建一个具有流式传输能力的 Server。
4. 核心实现:构建 Streamable-HT MCP Server
我们的目标是构建一个名为Streamable-HT的 Server(HT 可理解为Hello, Text或Handy Tool)。它的核心功能是:将本地指定文本文件的内容,以流式(Chunk by Chunk)的方式提供给 AI 客户端。这对于让 AI 分析大型日志文件、配置文件或代码文件非常有用。
4.1 项目结构与核心依赖
首先,安装必要的依赖:
npm install @modelcontextprotocol/sdk npm install --save-dev typescript ts-node @types/node然后,创建我们的项目结构:
my-streamable-mcp-server/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # Server 主入口文件 │ └── resources/ # 资源定义(可选) └── README.md创建tsconfig.json用于 TypeScript 编译:
{ "compilerOptions": { "target": "ES2022", "module": "commonjs", "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules"] }4.2 编写 MCP Server 主逻辑 (src/index.ts)
这是整个 Server 的核心。我们将实现一个 Server,它公布一个read_file工具,该工具可以读取文件并以流式响应返回内容。
// src/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import * as fs from 'fs'; import * as path from 'path'; import { createReadStream } from 'fs'; // 1. 定义 Server 能力:我们提供 Tools const CAPABABILITIES = { tools: {}, // 启用工具功能 }; // 2. 定义我们的工具:read_file const READ_FILE_TOOL: Tool = { name: 'read_file', description: 'Read the contents of a text file from a specified absolute path on the local filesystem. Returns the content in chunks via streaming.', inputSchema: { type: 'object', properties: { filepath: { type: 'string', description: 'The absolute path to the text file to read.', }, }, required: ['filepath'], }, }; class StreamableHTServer { private server: Server; constructor() { // 3. 创建 Server 实例 this.server = new Server( { name: 'streamable-ht-server', version: '0.1.0', }, CAPABABILITIES ); // 4. 设置请求处理器 this.setupHandlers(); // 5. 错误处理 this.server.onerror = (error) => console.error('[MCP Server Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupHandlers() { // 处理客户端查询可用工具的请求 this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [READ_FILE_TOOL], })); // 处理客户端调用工具的请求 this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== READ_FILE_TOOL.name) { throw new Error(`Unknown tool: ${request.params.name}`); } const filepath = request.params.arguments?.filepath; if (typeof filepath !== 'string') { throw new Error('filepath argument is required and must be a string.'); } // 安全检查:防止路径遍历攻击 const resolvedPath = path.resolve(filepath); // 这里可以添加更复杂的路径白名单校验 if (!resolvedPath.startsWith(process.cwd())) { // 示例:只允许读取当前工作目录下的文件 throw new Error(`Access to path ${resolvedPath} is not allowed.`); } // 返回一个包含流式内容的响应 return await this.handleReadFile(resolvedPath); }); } private async handleReadFile(filepath: string): Promise<{ content: Array<{ type: 'text'; text: string; }>; }> { return new Promise((resolve, reject) => { const chunks: string[] = []; const stream = createReadStream(filepath, { encoding: 'utf8', highWaterMark: 1024 }); // 每次读取 1KB stream.on('data', (chunk: string) => { chunks.push(chunk); // 在实际的 MCP 流式响应中,这里应该通过 Server 的 `sendNotification` 发送增量内容 // 但为简化示例,我们先收集所有 chunk,最后一次性返回。 // 真正的流式实现需要用到 `ToolResult` 的 `content` 和 `isDelta` 等属性。 }); stream.on('end', () => { resolve({ content: [ { type: 'text', text: chunks.join(''), }, ], }); }); stream.on('error', (err) => { reject(new Error(`Failed to read file ${filepath}: ${err.message}`)); }); }); } // 启动 Server,使用 stdio 传输层 async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Streamable-HT MCP Server is running on stdio...'); } } // 启动服务器 const server = new StreamableHTServer(); server.run().catch(console.error);4.3 完善 Package.json 脚本
修改package.json,添加启动脚本和构建脚本:
{ "name": "streamable-ht-mcp-server", "version": "0.1.0", "description": "A MCP server that streams content of local text files.", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node src/index.ts" }, "keywords": ["mcp", "model-context-protocol", "server", "streaming"], "author": "Your Name", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^0.5.0" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0", "ts-node": "^10.9.0" }, "mcp": { "name": "streamable-ht", "version": "0.1.0", "description": "Stream local file contents to AI clients." } }4.4 构建与运行
现在,我们可以构建并运行这个 Server 了:
# 编译 TypeScript npm run build # 运行服务器 (会在 stdio 上等待连接) npm start运行后,你会看到Streamable-HT MCP Server is running on stdio...的输出,表示 Server 已在标准输入输出上就绪,等待 MCP 客户端连接。
5. 连接与测试:在 Claude Desktop 中使用你的 Server
这是最激动人心的部分——让我们亲手创造的 Server 被 AI 使用。
配置 Claude Desktop:
- 打开 Claude Desktop 应用。
- 找到其配置目录。通常位于:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
- macOS:
- 如果文件不存在,则创建它。
编辑配置文件: 在
claude_desktop_config.json中添加以下配置,告诉 Claude 如何找到并运行我们的 MCP Server:{ "mcpServers": { "streamable-ht": { "command": "node", "args": [ "/ABSOLUTE/PATH/TO/your/project/my-streamable-mcp-server/dist/index.js" ], "env": { "ALLOWED_BASE_DIR": "/ABSOLUTE/PATH/TO/your/project" } } } }请务必将
/ABSOLUTE/PATH/TO/your/project替换为你项目dist/index.js文件所在的绝对路径。ALLOWED_BASE_DIR环境变量可以在我们的 Server 代码中读取,用于实现更安全的白名单校验(下文最佳实践会完善)。重启 Claude Desktop:完全退出并重新启动 Claude Desktop 应用,使其加载新的配置。
开始对话:在 Claude 的聊天窗口中,你现在可以直接要求它使用你的工具了!例如:
“请使用
read_file工具,读取我项目根目录下的README.md文件内容。”Claude 会识别到可用的
read_file工具,并调用它。如果一切正常,Claude 将能够展示出README.md文件的内容。你可以在你的 Server 运行终端看到相关的调用日志。
6. 常见问题与排查思路 (FAQ)
在搭建和运行过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| Claude 提示“没有可用工具”或“未知工具”。 | 1. MCP Server 配置路径错误。 2. Claude Desktop 未正确重启。 3. Server 启动失败或立即退出。 | 1. 检查claude_desktop_config.json中的command和args路径是否正确、可执行。2. 彻底退出并重启 Claude Desktop。 3. 在终端单独运行 node /path/to/your/server.js,查看是否有错误输出(如模块找不到)。 |
Server 启动时报错Cannot find module ...。 | 项目依赖未安装或 TypeScript 未编译。 | 1. 运行npm install安装依赖。2. 运行 npm run build编译 TypeScript 代码。 |
| Claude 调用工具时报“权限错误”或“路径不允许”。 | Server 代码中的路径安全检查阻止了访问。 | 1. 检查请求的文件路径是否在process.cwd()目录下。2. 修改 Server 代码中的安全检查逻辑,或调整 ALLOWED_BASE_DIR环境变量。 |
| 工具调用成功,但返回内容为空或乱码。 | 1. 文件编码问题。 2. 文件路径不存在或不可读。 | 1. 确保读取的是 UTF-8 编码的文本文件。 2. 在 handleReadFile函数中添加更详细的错误日志。 |
| 如何实现真正的流式传输? | 示例代码是“伪流式”,一次性返回。 | 需要深入研究 MCP SDK 的ToolResult类型,使用isDelta: true和sendNotification来分片发送内容。这属于进阶实现。 |
基础排查命令:
# 检查 Node 和 Server 是否能直接运行 cd /path/to/your/project node dist/index.js # 此时程序应挂起,等待输入,没有报错。 # 检查配置文件语法 cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python -m json.tool7. 最佳实践与工程化建议
将一个简单的 MCP Server 用于生产环境或团队共享,需要考虑更多因素:
安全性是第一要务
- 路径白名单:绝对不要允许任意路径访问。应在 Server 启动时通过环境变量(如
ALLOWED_BASE_DIR)配置一个或多个允许访问的根目录,并在path.resolve后检查解析路径是否以白名单目录开头。 - 输入验证:对
filepath参数进行严格的验证和规范化,防止../../../etc/passwd这类路径遍历攻击。 - 环境隔离:考虑在 Docker 容器或沙箱环境中运行 MCP Server,以限制其系统权限。
- 路径白名单:绝对不要允许任意路径访问。应在 Server 启动时通过环境变量(如
增强健壮性
- 错误处理:为文件不存在、无读取权限、磁盘 IO 错误等场景提供清晰、友好的错误信息,并通过 MCP 协议返回给客户端。
- 心跳与超时:实现心跳机制,防止僵死进程。为长时间的文件读取操作设置超时。
- 日志记录:在 Server 侧记录重要的操作日志(如工具调用、错误),便于调试和审计。注意不要记录敏感文件内容。
实现真正的流式传输我们的示例是“缓冲式”的。真正的流式传输对于大文件至关重要。你需要修改
handleReadFile方法,使其返回一个AsyncIterable或利用 SDK 的增量更新机制,分批次将内容push给客户端,避免内存溢出和长时间等待。扩展更多工具一个成熟的 MCP Server 可以提供多种工具。例如:
list_directory:列出目录内容。search_in_files:在文件中搜索文本。execute_safe_command:在严格限制下执行安全的系统命令(如git status)。 每增加一个工具,都要仔细评估其安全风险。
配置管理
- 将服务器配置(如白名单、端口、超时时间)外部化,使用
config.json或环境变量管理。 - 为不同的开发环境(开发、测试、生产)准备不同的配置。
- 将服务器配置(如白名单、端口、超时时间)外部化,使用
发布与共享
- 将你的 Server 发布到 npm,方便他人通过
npx安装。 - 编写清晰的
README.md,说明功能、配置方法和安全注意事项。 - 考虑提供 Docker 镜像,简化部署。
- 将你的 Server 发布到 npm,方便他人通过
通过构建这个Streamable-HTMCP Server,你不仅掌握了一个让 AI 安全访问本地数据的强大工具,更深入理解了 AI 智能体与外部世界交互的标准化协议——MCP。你可以以此为基础,探索更复杂的 Server,例如连接数据库、管理 Kubernetes 集群、操作内部 API 等,从而极大地扩展 AI 在你日常工作流中的能力和边界。动手尝试,定制属于你自己的 AI 增强工作流吧。
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度