三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从零搭建MCP Server:连接AI与外部系统的标准化协议实践

从零搭建MCP Server:连接AI与外部系统的标准化协议实践

1. 项目概述:为什么我们需要从零搭建一个MCP?

如果你最近在AI开发或者智能体(Agent)的圈子里混,一定频繁听到“MCP”这个词。它不是什么新出的芯片,也不是某个神秘组织,而是Model Context Protocol的缩写,中文可以理解为“模型上下文协议”。简单来说,MCP是一个标准化的“插座”协议,它定义了AI模型(比如Claude、GPT)如何与外部工具、数据源和服务进行安全、高效的对话。

想象一下,你有一个能力超强的AI助手(Claude Code或Cursor里的AI),但它天生只能“思考”,无法直接操作你的文件系统、查询数据库、调用API或者控制浏览器。传统的做法是,每个AI应用都要自己写一套复杂的、定制化的插件系统来连接这些外部能力,这就像给每个电器都定制一个独特的插头,混乱且低效。MCP的出现,就是为了解决这个问题。它制定了一套统一的“插座”标准(协议),任何符合MCP标准的工具(称为MCP Server)都可以被任何支持MCP的AI客户端(称为MCP Client)即插即用。

所以,“从0开始搭建MCP”这个标题,其核心价值在于:让你亲手打造一个能让AI模型安全、可控地访问特定外部资源的桥梁。无论是想让你本地的Claude Desktop读取Obsidian笔记库,还是让Cursor里的AI帮你操作Figma设计稿,亦或是连接公司内部的数据库,你都需要理解并实现一个MCP Server。这个过程不仅能让你深度理解AI与工具集成的未来形态,更能让你获得一项构建下一代AI应用基础设施的核心技能。它适合所有对AI应用开发、自动化工具链构建感兴趣的开发者、技术爱好者和效率追求者。

2. MCP核心概念与生态全景解析

在动手之前,我们必须把几个关键概念和它们之间的关系彻底理清。这能帮助你在后续的搭建和调试中,清楚地知道每一步在全局中的位置。

2.1 MCP、Skill、Function Calling:概念辨析

网络上经常看到MCP、Skill、Agent Skill这些词混用,甚至和传统的Function Calling(函数调用)对比,让人一头雾水。我们来做个清晰的拆解:

  1. MCP(Model Context Protocol): 这是最底层的通信协议标准。它由Anthropic公司牵头制定并开源,定义了一套JSON-RPC格式的消息规范。MCP协议规定了Client和Server之间“如何说话”,比如如何发现工具(Tools)、如何调用工具、如何传递资源(Resources)等。MCP本身不实现任何具体功能,它只是一本“通信手册”

  2. MCP Server: 这是协议的服务端实现。一个MCP Server就是一个独立的进程或服务,它“会说MCP协议”,并且封装了对某个特定外部系统或能力的访问。例如:

    • filesystem-mcp: 一个提供本地文件读写能力的Server。
    • sqlite-mcp: 一个可以连接并查询SQLite数据库的Server。
    • brave-search-mcp: 一个提供网络搜索能力的Server。
    • 你自己写的my-internal-api-mcp: 连接你公司内部系统的Server。 Server向Client宣告自己提供了哪些“工具”(Tools)和“资源”(Resources)。
  3. MCP Client: 这是协议的客户端实现。通常是AI应用本身,比如Claude Desktop、Cursor Editor、Windsurf等。Client负责启动和管理一个或多个MCP Server进程,并从这些Server中获取工具列表和资源信息,在合适的时机(如用户提问涉及相关能力时)调用这些工具。

  4. Skill(技能)与 Agent Skill: 这是一个更上层的、偏向用户功能的概念。在一些AI平台(如Dify、Coze)中,“Skill”指的是一个封装好的、可复用的AI能力模块。一个Skill内部可能通过调用一个或多个MCP Server来实现其功能。你可以认为Skill是面向业务的“产品功能”,而MCP Server是面向技术的“基础设施组件”。至于“Agent Skill”,通常是指在智能体(Agent)框架中,一个可被智能体规划和调用的子能力单元,其底层同样可能由MCP驱动。

  5. Function Calling(函数调用): 这是大语言模型(LLM)本身提供的一种基础能力。模型在对话中,可以输出一个结构化的请求,表示它想调用某个预定义好的函数。MCP可以看作是Function Calling的“标准化和外部化”。传统的Function Calling需要开发者在每次与AI对话时,手动在代码里定义好函数列表(schema)并传给模型。而MCP将函数的定义、发现和调用过程标准化,并且将这些函数的执行体(Server)与AI应用(Client)解耦,部署在独立的进程中,带来了更好的安全性、可扩展性和可维护性。

注意: 很多教程里说的“给Claude添加MCP”,准确来说是“为Claude Desktop配置MCP Server”。Claude Desktop作为一个MCP Client,本身已经内置了对MCP协议的支持,你需要做的只是告诉它去运行哪些Server。

2.2 MCP 核心组件:Tools 与 Resources

MCP协议主要围绕两大核心组件来组织能力,理解它们是你设计Server的关键。

  1. Tools(工具): 这是主动操作的接口。你可以把它类比为编程中的“函数”或“方法”。一个Tool有名称、描述、参数列表(输入schema)。当AI模型认为需要执行某个操作时(比如“搜索网络”、“创建文件”),它会通过Client调用对应的Tool。

    • 示例search_web(query: string)工具,接收一个查询字符串,返回搜索结果。
  2. Resources(资源): 这是被动提供信息的接口。你可以把它类比为“只读的URI”或“数据源”。一个Resource有唯一的URI(如file:///path/to/note.md)和MIME类型。AI模型可以“读取”这些资源来获取上下文信息,但通常不能直接修改它们(修改需要通过Tool)。

    • 示例: Server可以声明它提供了file:///home/user/project/README.md这个资源。当用户对话中提到“看看我的README文件”时,Client可以主动读取这个Resource的内容,并将其作为上下文提供给AI模型,而无需模型显式调用一个“read_file”工具。

一个典型的交互流程

  1. Client(如Claude Desktop)启动你配置的sqlite-mcpServer。
  2. Server启动后,立即通过MCP协议向Client发送一个list_toolslist_resources的响应,告知Client:“我这里有query_database工具,可以查询db://sales/data这个资源”。
  3. 用户在Claude Desktop中输入:“帮我查一下上个月的销售数据。”
  4. Claude Desktop的AI模型分析请求,发现需要用到“销售数据”,它知道有一个db://sales/data资源可用,于是通过Client读取该资源的结构信息(如表schema)。
  5. 模型可能进一步决定需要执行一个查询,于是通过Client调用query_database工具,并生成SQL查询参数。
  6. Client将调用请求转发给Server,Server执行SQL,将结果返回给Client,Client再呈现给用户。

3. 从零搭建MCP Server:环境与设计

现在,我们进入实战环节。我们将选择一个最常见的场景来构建我们的第一个MCP Server:一个能够读取和搜索指定目录下Markdown笔记内容的Server。这模拟了连接Obsidian、Logseq等知识库的需求。我们将使用MCP官方推荐的TypeScript/JavaScript SDK进行开发,这是目前生态最完善、文档最清晰的方式。

3.1 开发环境准备与项目初始化

首先,确保你的系统已经安装了Node.js(版本18或以上)和npm/yarn/pnpm等包管理器。

# 1. 创建一个新的项目目录 mkdir my-note-mcp-server cd my-note-mcp-server # 2. 初始化Node.js项目,推荐使用TypeScript以获得更好的类型提示 npm init -y npm install typescript @types/node tsx --save-dev # tsx用于运行TypeScript代码,比ts-node更轻量 # 3. 初始化TypeScript配置 npx tsc --init # 编辑生成的tsconfig.json,确保 `"module": "ESNext"` 和 `"target": "ES2022"` # 4. 安装MCP官方SDK npm install @modelcontextprotocol/sdk

接下来,创建项目的基本结构:

my-note-mcp-server/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # Server主入口文件 │ └── note-store.ts # 笔记读取与搜索的逻辑模块 └── README.md

3.2 Server核心逻辑设计

在动手写MCP协议相关的代码前,我们先设计好核心的业务逻辑。在src/note-store.ts中:

// src/note-store.ts import fs from 'fs/promises'; import path from 'path'; import { glob } from 'glob'; // 需要安装: npm install glob export interface Note { uri: string; // 例如:file:///notes/hello.md title: string; content: string; lastModified: Date; } export class NoteStore { private notesDir: string; constructor(notesDir: string) { this.notesDir = path.resolve(notesDir); // 解析为绝对路径 } // 扫描目录,获取所有Markdown文件 async scanNotes(): Promise<Note[]> { const pattern = path.join(this.notesDir, '**/*.md'); const files = await glob(pattern, { nodir: true }); const notes: Note[] = []; for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); const stats = await fs.stat(file); // 简单从文件内容第一行提取标题,如果没有则使用文件名 const firstLine = content.split('\n')[0] || ''; const titleMatch = firstLine.match(/^#\s+(.+)/); const title = titleMatch ? titleMatch[1] : path.basename(file, '.md'); notes.push({ uri: `file://${file}`, // MCP Resource URI title, content, lastModified: stats.mtime, }); } catch (err) { console.error(`Failed to read note ${file}:`, err); // 可以选择跳过错误文件,或根据需求处理 } } return notes; } // 根据关键词搜索笔记内容 async searchNotes(keyword: string): Promise<Note[]> { const allNotes = await this.scanNotes(); const lowerKeyword = keyword.toLowerCase(); return allNotes.filter(note => note.title.toLowerCase().includes(lowerKeyword) || note.content.toLowerCase().includes(lowerKeyword) ); } // 根据URI获取特定笔记 async getNoteByUri(uri: string): Promise<Note | undefined> { // 将 file:// 开头的URI转换回本地文件路径 if (!uri.startsWith('file://')) { return undefined; } const filePath = uri.slice('file://'.length); // 安全检查:确保请求的文件在notesDir目录下 if (!filePath.startsWith(this.notesDir)) { throw new Error('Access denied: File outside of allowed directory'); } try { const content = await fs.readFile(filePath, 'utf-8'); const stats = await fs.stat(filePath); const firstLine = content.split('\n')[0] || ''; const titleMatch = firstLine.match(/^#\s+(.+)/); const title = titleMatch ? titleMatch[1] : path.basename(filePath, '.md'); return { uri, title, content, lastModified: stats.mtime, }; } catch { return undefined; } } }

这个NoteStore类封装了所有与笔记文件系统交互的底层逻辑,它与MCP协议无关,只是纯粹的业务代码。这样做的好处是逻辑清晰,便于测试,也方便未来替换数据源(比如从数据库读取笔记)。

实操心得: 在文件路径处理上,一定要做好规范化安全性检查。使用path.resolve()获取绝对路径,避免相对路径导致的歧义。在getNoteByUri中,我们必须检查请求的文件是否在我们声明的notesDir目录下,这是防止Server被恶意利用读取系统任意文件的关键安全措施。MCP协议本身不强制这一点,但作为Server开发者,你必须考虑到。

4. 实现MCP Server:协议对接与工具暴露

有了核心逻辑,我们现在需要创建一个MCP Server,将NoteStore的能力通过MCP协议暴露出去。

4.1 创建Server实例与定义Tools

我们在src/index.ts中创建Server:

// src/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { NoteStore } from './note-store.js'; // 1. 初始化Server和NoteStore const server = new Server( { name: 'my-note-mcp-server', version: '0.1.0', }, { capabilities: { resources: {}, // 声明我们支持Resources tools: {}, // 声明我们支持Tools }, } ); const NOTES_DIR = process.env.NOTES_DIR || './notes'; // 通过环境变量配置笔记目录 const noteStore = new NoteStore(NOTES_DIR); // 2. 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'search_notes', description: '在笔记库中搜索包含特定关键词的笔记。', inputSchema: { type: 'object', properties: { keyword: { type: 'string', description: '要搜索的关键词', }, }, required: ['keyword'], }, }, { name: 'get_note_statistics', description: '获取笔记库的统计信息,如笔记总数、最近更新等。', inputSchema: { type: 'object', properties: {}, // 此工具不需要参数 }, }, ], }; }); // 3. 处理资源列表请求 server.setRequestHandler(ListResourcesRequestSchema, async () => { // 我们可以选择动态返回资源列表,例如返回最近修改的5篇笔记作为资源 // 但为了简单起见,我们先返回一个空列表,或者一个根资源。 // 更动态的做法是在ReadResource请求时再按需列出。 return { resources: [ { uri: `note://root`, name: '笔记库根目录', description: `位于 ${NOTES_DIR} 的Markdown笔记库`, mimeType: 'text/plain', // 或 application/json }, ], }; }); // 4. 处理读取资源请求 server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; if (uri === 'note://root') { // 当请求根资源时,我们返回一个笔记列表的摘要 const notes = await noteStore.scanNotes(); const summary = notes.map(note => `- ${note.title} (${note.uri})`).join('\n'); return { contents: [ { uri: uri, mimeType: 'text/plain', text: `笔记库总览 (共${notes.length}篇笔记):\n${summary}`, }, ], }; } // 如果请求的是具体的file:// URI,我们委托给NoteStore处理 if (uri.startsWith('file://')) { const note = await noteStore.getNoteByUri(uri); if (note) { return { contents: [ { uri: uri, mimeType: 'text/markdown', // 标记为Markdown格式,AI客户端可能进行特殊渲染 text: note.content, }, ], }; } else { throw new Error(`Note not found: ${uri}`); } } throw new Error(`Unsupported resource URI: ${uri}`); }); // 5. 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === 'search_notes') { const keyword = args?.keyword; if (typeof keyword !== 'string') { throw new Error('Keyword must be a string'); } const results = await noteStore.searchNotes(keyword); // 将结果格式化为AI易于理解的内容 const content = results.length > 0 ? `找到 ${results.length} 篇相关笔记:\n` + results.map(n => `### ${n.title}\n**URI:** ${n.uri}\n**摘要:** ${n.content.slice(0, 150)}...`).join('\n\n') : `未找到包含“${keyword}”的笔记。`; return { content: [ { type: 'text', text: content, }, ], }; } if (name === 'get_note_statistics') { const notes = await noteStore.scanNotes(); const total = notes.length; const latest = notes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime())[0]; return { content: [ { type: 'text', text: `**笔记库统计**\n- 笔记总数: ${total}\n- 最新笔记: ${latest?.title || '无'}\n- 最新更新时间: ${latest?.lastModified.toLocaleDateString() || 'N/A'}`, }, ], }; } throw new Error(`Unknown tool: ${name}`); }); // 6. 启动Server(使用stdio传输,这是与Claude Desktop等客户端通信的标准方式) async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('My Note MCP Server is running on stdio...'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); });

4.2 关键实现细节剖析

  1. Server初始化: 创建Server实例时,需要提供元数据(name, version)和声明支持的Capabilities(这里是resourcestools)。这类似于HTTP服务器的路由声明。

  2. 请求处理器(Request Handler): SDK的核心是为一类MCP请求设置处理器。我们处理了四种核心请求:

    • ListToolsRequestSchema: 客户端询问“你有什么工具?”。我们返回search_notesget_note_statistics两个工具的定义,包括名称、描述和输入参数的JSON Schema。
    • ListResourcesRequestSchema: 客户端询问“你有什么资源?”。这里我们返回了一个静态的根资源note://root。更复杂的实现可以动态扫描文件系统并列出所有笔记文件作为资源。
    • ReadResourceRequestSchema: 客户端请求读取某个资源的内容。我们根据URI进行路由:如果是根URI,返回摘要;如果是file://开头的具体笔记URI,则读取文件内容返回。注意返回的mimeType,设置为text/markdown可以帮助AI客户端更好地理解内容格式。
    • CallToolRequestSchema: 客户端调用某个工具。我们根据工具名name分派到不同的业务逻辑函数,并处理输入参数arguments
  3. 传输层(Transport)StdioServerTransport是MCP Server最常用的传输方式。Server通过标准输入(stdin)接收JSON-RPC请求,通过标准输出(stdout)发送响应。这使得任何能启动子进程并与之进行标准IO通信的程序(如Claude Desktop、Cursor)都能轻松集成MCP Server。

  4. 错误处理: 在工具调用和资源读取中,我们对参数进行了基础校验,并对未找到的资源或未知工具抛出了错误。这些错误会被SDK捕获并格式化为标准的MCP错误响应返回给客户端。

注意事项: 在ReadResourceRequestSchema处理器中,我们直接返回了笔记的原始内容。对于大型文件,这可能会消耗大量上下文令牌。在生产环境中,你可能需要实现更智能的策略,比如只返回文件的前N行,或者提供一个summarize_note工具来让AI主动请求摘要。

5. 构建、测试与配置客户端

5.1 构建与运行独立测试

首先,我们需要编译TypeScript并创建一个可直接运行的脚本。在package.json中添加脚本:

{ "name": "my-note-mcp-server", "version": "0.1.0", "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsx watch src/index.ts" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "glob": "^11.0.0" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0", "tsx": "^4.0.0" } }

创建一个简单的测试笔记目录和文件:

mkdir -p notes echo '# 项目计划\n这是关于MCP服务器搭建的项目计划。' > notes/project-plan.md echo '# 学习笔记\nMCP协议的核心是Tools和Resources。' > notes/learning.md

现在,我们可以用开发模式运行Server,手动模拟客户端发送JSON-RPC请求来测试。但这比较繁琐。更高效的方法是使用MCP SDK自带的测试工具或编写一个简单的测试客户端。这里我们介绍一个实用的手动测试技巧

  1. 在一个终端运行Server:
    NOTES_DIR=$(pwd)/notes npm run dev
  2. 由于Server使用stdio,它会等待输入。我们可以编写一个简单的Node.js脚本作为测试客户端,或者使用像nc(netcat) 这样的工具进行简单交互(但这需要处理JSON-RPC帧)。更推荐的方法是使用官方提供的@modelcontextprotocol/sdk中的测试工具,或者直接将其配置到Claude Desktop中进行真实环境测试。

5.2 配置到Claude Desktop

这是最直接的集成测试方式。Claude Desktop是Anthropic官方提供的、天然支持MCP的客户端。

  1. 找到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
  2. 编辑配置文件: 如果文件不存在,就创建它。我们需要在mcpServers字段下添加我们的Server配置。

    { "mcpServers": { "my-note-server": { "command": "node", "args": [ "/ABSOLUTE/PATH/TO/YOUR/my-note-mcp-server/dist/index.js" ], "env": { "NOTES_DIR": "/ABSOLUTE/PATH/TO/YOUR/my-note-mcp-server/notes" } } // 你可以在这里继续添加其他MCP Server,如sqlite, brave-search等 } }

    关键点

    • command: 必须是node,因为我们的Server是Node.js脚本。
    • args: 第一个参数必须是编译后的JS文件(dist/index.js)的绝对路径。相对路径很可能导致启动失败。
    • env: 设置环境变量NOTES_DIR,这样我们的Server就知道去哪里找笔记。同样,必须使用绝对路径
    • my-note-server是这个Server实例的别名,可以自定义。
  3. 重启Claude Desktop: 保存配置文件后,完全关闭并重新打开Claude Desktop应用程序。

  4. 验证连接: 重启后,Claude Desktop会在后台启动你配置的MCP Server进程。你可以通过查看Claude Desktop的日志(通常在上述配置文件的同级目录或系统标准日志位置)来检查是否有启动错误。更直观的方式是,直接在Claude的聊天框中询问:“你现在可以使用哪些工具?” 或者 “搜索一下关于‘项目’的笔记”。如果配置成功,Claude会调用你的search_notes工具并返回结果。

5.3 配置到Cursor Editor

Cursor是另一个深度集成MCP的流行代码编辑器。配置方式类似,但入口不同。

  1. 打开Cursor,进入设置(Settings)。
  2. 找到“MCP Servers”“AI”设置部分(具体位置可能随版本更新变化,通常在设置搜索栏输入MCP即可找到)。
  3. 点击“Add New MCP Server”。
  4. 在弹出的配置界面中,填写信息:
    • Name:my-note-server(任意名称)
    • Command:node
    • Arguments:/ABSOLUTE/PATH/TO/YOUR/my-note-mcp-server/dist/index.js
    • Environment Variables: 点击添加,键为NOTES_DIR,值为你的笔记目录绝对路径。
  5. 保存并重启Cursor。

重启后,你可以在Cursor的AI聊天界面中测试工具调用。

6. 进阶优化与生产环境考量

一个能跑通的Demo只是第一步。要让你的MCP Server稳定、可用,还需要考虑以下方面。

6.1 性能、安全与错误处理增强

  1. 资源列表的动态性与分页: 我们的ListResourcesRequestSchema处理器目前只返回一个静态根资源。当笔记库很大时,更好的做法是支持动态列出和分页。MCP协议支持在ListResourcesRequest中传递cursor参数来实现分页。你可以修改逻辑,每次返回一部分笔记的URI,并提供一个nextCursor

  2. 内容采样与摘要: 直接返回整个大文件的内容会浪费大量AI模型的上下文窗口。可以在ReadResourceRequestSchema处理器中实现内容采样,例如只返回文件的前1000个字符,或者提供一个get_note_summary工具,利用AI模型或本地摘要算法生成摘要。

  3. 更严格的安全边界: 除了检查文件路径是否在NOTES_DIR下,还应考虑:

    • 符号链接(Symlink)攻击: 使用fs.realpath()解析符号链接,确保最终路径仍在安全目录内。
    • 命令注入: 如果你的工具涉及执行系统命令(例如调用外部程序处理文件),必须对输入参数进行严格的过滤和转义,避免命令注入漏洞。
    • 环境隔离: 考虑在Docker容器或沙箱环境中运行Server,尤其是处理不可信输入时。
  4. 完善的日志与监控: 在生产环境中,需要记录Server的运行日志、工具调用次数、错误信息等。可以使用winstonpino等日志库,并将日志输出到文件或日志收集系统。在Server的各个请求处理器开头和结尾添加详细的调试日志,对于排查问题至关重要。

  5. 健壮的错误处理: 当前的错误处理比较基础。应该为不同类型的错误(如文件不存在、权限错误、参数无效)定义清晰的错误码和用户友好的信息,并通过MCP协议的错误响应返回。

6.2 扩展更多工具与能力

我们的Server目前只有搜索和统计两个工具。你可以根据需求轻松扩展:

  • create_note: 创建新笔记。需要处理文件名冲突、内容写入。
  • update_note: 更新现有笔记内容。注意并发修改的问题。
  • tag_notes: 为笔记添加标签。这可能需要引入一个额外的元数据存储(如一个JSON索引文件)。
  • vector_search_notes: 集成向量数据库(如Chroma、LanceDB),实现基于语义的相似性搜索,而不仅仅是关键词匹配。

每添加一个工具,只需在ListToolsRequestSchema处理器中增加其定义,并在CallToolRequestSchema处理器中添加对应的分支逻辑即可。

6.3 调试技巧与常见问题排查

在开发过程中,你肯定会遇到各种问题。以下是一些实用的调试技巧:

  1. 查看客户端日志: Claude Desktop和Cursor通常都有开发者控制台或日志文件。启动时加上--verbose--debug标志(如果支持)可以输出更详细的MCP通信日志。这是定位连接和协议问题的第一手资料。

  2. 独立运行并模拟请求: 编写一个简单的测试脚本,模拟MCP客户端向你的Server发送JSON-RPC请求。这可以帮助你隔离问题,确定是Server逻辑错误还是客户端集成错误。

    // test-client.mjs import { spawn } from 'child_process'; const serverProcess = spawn('node', ['dist/index.js'], { env: { ...process.env, NOTES_DIR: './notes' }, stdio: ['pipe', 'pipe', 'inherit'] // 接管 stdin/stdout }); // 手动构造一个 list_tools 请求并写入 serverProcess.stdin...
  3. 使用MCP Inspector工具: Anthropic提供了一个名为MCP Inspector的调试工具。它是一个图形化界面,可以连接到任何MCP Server,查看其暴露的工具和资源,并手动调用工具,是开发和调试的利器。可以通过npm install -g @modelcontextprotocol/inspector安装,然后运行mcp-inspector

  4. 常见问题速查表

问题现象可能原因解决方案
Claude Desktop/Cursor 启动后无新工具1. 配置文件路径错误。
2. Server启动失败。
3. Server未正确声明工具。
1. 检查配置文件路径和JSON格式。
2. 查看客户端/系统日志,看Server进程是否报错退出。
3. 用MCP Inspector连接Server,验证工具列表。
调用工具时报“Unknown tool”工具名称拼写不一致。ListTools返回的名称与CallTool处理器中判断的名称不匹配。仔细检查两处的name字段是否完全一致(大小写敏感)。
读取资源返回空或错误1. URI格式不正确。
2. 文件路径权限问题。
3. 安全检查阻止了访问。
1. 确保URI以file://开头,且路径是绝对路径。
2. 确保Node.js进程有权限读取目标文件。
3. 调试getNoteByUri中的路径检查和解析逻辑。
Server进程立即退出1. 代码中存在未捕获的异常。
2. 依赖未安装。
3. TypeScript未编译,直接运行了ts文件。
1. 在main()函数外包裹try-catch,打印错误。
2. 运行npm install
3. 确保运行的是dist/index.js或使用tsx
通信超时或无响应Server的请求处理器是异步的,但没有正确返回Promise或发生了死循环。确保所有async请求处理器都使用了await,并且最终会returnthrow。使用调试器检查执行流。

7. 生态集成与未来展望

成功搭建并运行你自己的MCP Server后,你就获得了连接AI世界与真实世界数据/服务的一把钥匙。但这仅仅是开始。

融入MCP生态: 你可以将你的Server开源,发布到npm上,并提交到官方的 MCP Server Registry (如果存在)或社区列表。这样,其他开发者就可以通过一行配置轻松使用你的my-note-mcp-server。在发布时,记得编写清晰的README.md,说明功能、配置方法和注意事项。

探索更复杂的Server: 本文的笔记Server是一个文件系统类Server。你可以尝试更复杂的类型:

  • 数据库Server: 连接MySQL/PostgreSQL,让AI直接安全地查询业务数据。
  • API聚合Server: 封装公司内部多个API,提供统一的、自然语言可访问的接口。
  • 浏览器自动化Server: 集成Playwright或Puppeteer,让AI可以控制浏览器完成网页操作、数据抓取等任务(需要极其注意安全边界)。

Skill与MCP的协同: 在Dify、Coze等平台上,你可以将你的MCP Server作为一个后端能力,在其上构建更面向业务的Skill。例如,一个“周报生成Skill”可以调用你的笔记Server搜索本周工作笔记,调用数据库Server查询任务数据,再调用LLM生成周报草稿。

MCP协议本身的演进: MCP协议仍在快速发展中。关注其官方GitHub仓库,了解新特性,如更丰富的资源类型、流式响应(用于长内容生成)、双向通信(Server主动推送通知给Client)等。作为Server开发者,及时跟进协议更新能让你的工具保持兼容性和先进性。

从我个人的实践经验来看,MCP的价值在于它提供了一种标准化、解耦、安全的扩展AI能力的方式。它降低了为不同AI客户端重复开发适配层的成本,让开发者可以专注于实现核心业务逻辑。虽然初期搭建会遇到配置路径、环境变量、协议细节等“坑”,但一旦跑通,你会发现为AI构建工具变得前所未有的清晰和高效。未来的AI应用,很可能就是一个强大的MCP Client,配合一个由无数专业MCP Server组成的“工具网络”。而你现在所做的,正是在为这个网络添加一个节点。

← 返回列表