尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零实现Agent搜索MCP Server:构建、接入与生产实践

从零实现Agent搜索MCP Server:构建、接入与生产实践 在实际的 MCP 项目里搜索 AI Agent 并不只是一条查询接口那么简单。MCP Server 既要定义清楚工具的入参和返回也要考虑数据源、传输方式、客户端发现机制以及当客户端发起工具调用时服务端如何稳定地把 Agent 元数据返回给大模型。Buy My Agent 这类以 Agent 目录为核心的 MCP Server核心价值就是让任何支持 MCP 的客户端Claude Desktop、Cursor、自研 Host都能通过统一协议搜索 Agent而不是为每个客户端单独开发接口。这篇博客会从 MCP 的 Server、Client、Host 关系开始逐步实现一个可运行的 Agent 搜索 MCP Server然后接入常见客户端并给出验证、排错和生产化建议。读完以后你可以直接复制这套结构把“搜索 AI Agent”替换成你自己的 Agent 市场、工具列表或内部服务目录。1. 先搞清楚 MCP 下的 Agent 搜索场景1.1 什么是 MCP Server、MCP Client 和 MCP HostMCPModel Context Protocol可以理解为给大模型和外部数据、工具之间建立的一套标准接口协议。它解决的核心问题是大模型不能只靠训练数据里的知识去回答所有人它还需要在运行时读取外部数据、调用外部服务而 MCP 用统一的协议把“数据读取”和“工具调用”抽象成了标准能力。在这个协议里有三个角色需要区分清楚。MCP Host运行大模型应用的主程序负责决定什么时候调用工具、如何把工具结果交给模型。比如 Claude Desktop、Cursor 这类应用。MCP Client在 Host 内部负责与 MCP Server 通信的连接器它负责发送 JSON-RPC 请求、接收响应。MCP Server真正提供工具、资源、提示词的服务端。Buy My Agent 的 Server 部分就是这个角色它把“搜索 Agent”能力包装成一个 MCP 工具供其他客户端消费。对开发者来说最值得注意的一点是只要 Server 遵循 MCP 协议任何实现了 MCP Client 的 Host 都能连接它。这也是“Search AI Agents from Any MCP Client”这句话背后的技术基础而不是每个客户端都要单独写适配层。1.2 为什么 Agent 目录要暴露成 MCP 服务现在 AI Agent 数量增长很快但缺少一个统一入口来检索。常见方式有三种方式优点缺点在网站浏览器里手动搜索展示效果好适合人看大模型无法直接访问流程断裂给大模型提供 REST API 文档后端能调用但需要开发 Prompt 和函数调用逻辑需要单独封装工具、鉴权、参数定义做成 MCP Server标准协议工具定义、参数校验、调用结果都规范化需要理解 MCP SDK 和传输方式当 Agent 目录被暴露成 MCP Server 后用户可以在聊天窗口里直接说“帮我找一个能写 SQL 的 Agent”Host 会调用search_agents工具把搜索结果返回给模型模型再结合结果回答用户。这个体验比“让用户自己复制链接”要顺畅得多。1.3 Buy My Agent 的 Server 能力设计以标题里的 Buy My Agent 这类 Agent 目录服务为例一个最小可用的 MCP Server 至少要提供两类能力。搜索 Agent通过关键字、分类、标签过滤出候选 Agent 列表。查看 Agent 详情根据 ID 返回描述、发布者、使用成本、链接等完整信息。在 MCP 协议里这两类能力通常对应两个工具search_agents和get_agent_detail。工具的好处是参数由 Schema 定义Host 和大模型都能理解什么时候该调用、传什么参数。客户端发起对话 - Host 决定调用 search_agents - MCP Client 发送工具调用请求 - Buy My Agent MCP Server 处理并返回 Agent 列表 - Host 把结果交给大模型生成最终回答2. 环境准备用 TypeScript 搭建最小 MCP Server2.1 技术栈选择和版本确认实现 MCP Server 可以使用官方 SDK常见有两种语言JavaScript/TypeScript 和 Python。如果最终要把 Server 嵌入到 Node 生态、Electron 应用或桌面客户端里TypeScript 更合适如果团队已经以 Python 为主SDK 也有对应的mcpPython 包。下面以 TypeScript 为例因为 MCP Inspector 和 Claude Desktop 的调试文档里都经常出现 Node 命令。开始前需要确认本机环境至少包含环境建议版本用途Node.js18 或 20 LTS 以上运行 MCP Servernpm9 以上安装依赖TypeScript5.x编译类型代码tsx4.x开发时直接运行 TS 文件注意MCP SDK 的版本迭代比较快不同小版本的 API 可能有差异。落地前不要照抄旧代码应该先执行npm view modelcontextprotocol/sdk version查看当前版本再对照官方示例调整 import 位置。2.2 项目结构和初始化命令创建一个名为buy-my-agent-mcp-server的目录并在里面初始化 Node 项目。mkdir buy-my-agent-mcp-server cd buy-my-agent-mcp-server npm init -y npm install modelcontextprotocol/sdk zod npm install -D typescript tsx types/node这里安装两个核心依赖modelcontextprotocol/sdk官方 MCP 开发包负责处理 JSON-RPC 协议、传输层和 Server 生命周期。zod用来声明工具参数 Schema做运行时参数校验。安装完成后的项目结构如下buy-my-agent-mcp-server/ package.json tsconfig.json src/ index.ts agents.ts dist/ index.js agents.jssrc/agents.ts存放 Agent 数据源src/index.ts创建 MCP Server。dist目录由 TypeScript 编译生成最终客户端连接的是dist/index.js。2.3 package.json 与调试脚本打开package.json补充bin、type和脚本配置。{ name: buy-my-agent-mcp-server, version: 1.0.0, description: MCP server for searching AI agents from any MCP client, type: module, bin: { buy-my-agent-mcp: ./dist/index.js }, scripts: { build: tsc, start: node dist/index.js, dev: tsx src/index.ts, inspect: npm run build npx modelcontextprotocol/inspector node dist/index.js }, dependencies: { modelcontextprotocol/sdk: ^1.x.x, zod: ^3.x.x }, devDependencies: { types/node: ^20.x.x, typescript: ^5.x.x, tsx: ^4.x.x } }type: module表示使用 ESM 模块代码里的import会正常生效。bin可以把编译后的命令放到全局但本地调试时直接在客户端配置里写node /绝对路径/dist/index.js更可控。inspect脚本会在编译后启动官方调试工具 MCP Inspector后面会用到。再创建一个简单的tsconfig.json保证编译产物能在 Node 环境运行。{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: dist, rootDir: src, strict: true, esModuleInterop: true, skipLibCheck: true, resolveJsonModule: true }, include: [src] }这里module和moduleResolution使用 NodeNext可以正确处理 ESM 下的.js后缀导入。3. 核心实现让客户端能搜索 Agent3.1 先设计 Agent 数据源在实现工具之前先定义 Agent 的数据结构。不需要一开始就接数据库先用本地常量数组跑通流程后面再替换成远程 API。// src/agents.ts export interface AgentInfo { id: string; name: string; description: string; category: string; publisher: string; tags: string[]; url: string; cost: string; rating?: number; } export const AGENTS: AgentInfo[] [ { id: code-reviewer, name: Code Reviewer, description: Automatically review pull requests and suggest improvements., category: development, publisher: example-studio, tags: [code-review, github, pr], url: https://example.com/agents/code-reviewer, cost: free, rating: 4.5 }, { id: sql-writer, name: SQL Writer, description: Convert natural language into SQL queries for common databases., category: data, publisher: example-studio, tags: [sql, database, nl2sql], url: https://example.com/agents/sql-writer, cost: freemium, rating: 4.2 } ];这个结构里的id会作为get_agent_detail的入参。字段需要稳定因为 MCP 工具返回的是 JSON 文本客户端和大模型都要靠这些字段理解结果。不要在设计阶段把字段写得太随意否则后面改字段名会影响所有客户端体验。3.2 用 SDK 创建 Server 并注册工具核心文件在src/index.ts。先用McpServer创建服务端实例然后注册search_agents工具。// src/index.ts import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; import { AGENTS } from ./agents.js; const server new McpServer({ name: buy-my-agent, version: 1.0.0 }); server.tool( search_agents, Search published AI agents by keyword, category or tag., { keyword: z.string().optional().describe(Keyword matched against name, description or tags.), category: z.string().optional().describe(Agent category, e.g. development, writing, data.), limit: z.number().int().min(1).max(20).optional().describe(Maximum number of results.) }, async ({ keyword, category, limit 10 }) { let items AGENTS; if (keyword) { const k keyword.toLowerCase(); items items.filter((agent) ${agent.name} ${agent.description} ${agent.tags.join( )}.toLowerCase().includes(k) ); } if (category) { items items.filter((agent) agent.category category); } const agents items.slice(0, limit); return { content: [ { type: text, text: JSON.stringify({ total: agents.length, agents }, null, 2) } ] }; } );这里有几个关键点。server.tool的三个参数分别是工具名、工具描述和参数 Schema。工具描述是大模型判断“什么时候该调用”的依据所以不要写得太抽象。参数 Schema 里用zod的.optional()表示可选limit限制最大返回条数防止一次返回太多数据撑爆上下文。返回结构必须是 MCP 要求的content数组。这里用text类型内容是 JSON 字符串。实际项目中也可以返回图片、资源引用等类型但文本是最通用的。3.3 再添加 Agent 详情工具搜索返回的是列表用户进一步询问某个 Agent 时需要详情工具。server.tool( get_agent_detail, Get detailed information about a specific AI agent., { id: z.string().describe(Unique agent id.) }, async ({ id }) { const agent AGENTS.find((item) item.id id); if (!agent) { return { isError: true, content: [ { type: text, text: JSON.stringify({ error: Agent not found: ${id} }) } ] }; } return { content: [ { type: text, text: JSON.stringify(agent, null, 2) } ] }; } );找不到 Agent 时返回isError: true这是 MCP 工具中重要的错误语义。客户端看到isError后会把结果标记为工具执行失败大模型就不容易把错误文本当成正常结果。3.4 启动 stdio 传输MCP Server 需要绑定一个传输层。最小实现是 stdio也就是通过标准输入输出和客户端通信。async function main() { const transport new StdioServerTransport(); await server.connect(transport); } main().catch((error) { console.error(Failed to start MCP server:, error); process.exit(1); });StdioServerTransport会监听process.stdin并把响应写入process.stdout。因此本地调试时不要随意在代码里写console.log否则会污染标准输出导致客户端解析 JSON-RPC 失败。需要打印日志时使用console.error或专门的日志文件。如果后续要部署到远程可以让云端 Client 通过 HTTP 访问可以使用官方 SDK 里的StreamableHTTPServerTransport。这里先用 stdio 保证开发环境最快跑通。4. 在任意 MCP Client 中接入 Buy My Agent4.1 MCP Client 配置的本质无论使用 Claude Desktop、Cursor 还是自研 Host接入方式本质上都是告诉客户端两件事用哪个命令启动 MCP Server以及启动参数是什么。对 stdio 类型的 Server 来说客户端配置通常长这样{ mcpServers: { buy-my-agent: { command: node, args: [/absolute/path/to/buy-my-agent-mcp-server/dist/index.js] } } }command是启动命令args是参数列表。客户端会通过子进程方式启动这个 Server然后通过 stdin/stdout 通信。注意这里必须是绝对路径否则客户端启动的进程可能找不到文件。4.2 在 Claude Desktop 中配置Claude Desktop 的 MCP 配置通常放在claude_desktop_config.json中。点击应用设置里的 MCP Servers选择 Edit Config就可以打开配置文件。{ mcpServers: { buy-my-agent: { command: node, args: [/Users/yourname/dev/buy-my-agent-mcp-server/dist/index.js] } } }保存后重启 Claude Desktop在聊天界面能看到工具列表中出现search_agents和get_agent_detail。如果列表里没有说明进程启动失败需要查看客户端日志。4.3 在 Cursor、VS Code 等客户端中配置不同客户端的配置位置不一样但格式大同小异。客户端配置入口说明Claude Desktopclaude_desktop_config.json桌面应用进程由客户端托管Cursor~/.cursor/mcp.json或 UI 配置需要填写 command 和 argsVS Code.vscode/mcp.json或扩展配置适合仓库级配置自研 Host自己实现 MCP Client 代码用 SDK 的 client 连接例如 Cursor 的配置{ mcpServers: { buy-my-agent: { command: node, args: [/path/to/dist/index.js] } } }如果你在本地开发建议先用npm run build编译再在客户端里配置编译后的 JS 文件。直接用tsx src/index.ts在客户端里启动不仅启动速度慢而且一旦开发机缺少tsx依赖就会失败。4.4 “任何 MCP Client”到底意味着什么当一个 Server 严格遵循 MCP 协议时它不关心上游是大模型聊天软件还是自动化脚本只要对方实现了 MCP Client就能完成以下动作拿到 Server 的能力列表。看到工具有哪些参数。发起工具调用。读取结构化返回内容。这也是“Search AI Agents from Any MCP Client”的价值Agent 搜索能力不再被锁在某个网站里而是可以被嵌入到多种生产流程中比如自动化运维、客服机器人和内部知识库系统。5. 运行、调试与结果验证5.1 先本地运行 Server在终端执行npm run build node dist/index.js程序启动后看起来很“安静”没有输出这是正常的。MCP Server 通过 stdio 通信只有在收到客户端请求时才会输出 JSON-RPC 响应。如果直接运行会有大量日志刷屏反而要对日志输出方式产生怀疑。如果只是快速启动开发模式可以执行npm run dev生产环境建议只使用编译后的dist/index.js。5.2 使用 MCP Inspector 验证工具官方提供的 MCP Inspector 是调试 MCP Server 的最佳工具。它可以启动 Server并暴露一个本地 Web 页面让开发者在浏览器里手动测试工具。运行npm run inspect然后打开终端提示的本地地址通常是http://localhost:6274。在 Inspector 的工具列表里可以看到search_agents和get_agent_detail点击工具并填写参数就能看到返回结果。{ total: 1, agents: [ { id: sql-writer, name: SQL Writer, description: Convert natural language into SQL queries for common databases., category: data, publisher: example-studio, tags: [sql, database, nl2sql], url: https://example.com/agents/sql-writer, cost: freemium, rating: 4.2 } ] }到这里Server 本身已经验证通过。5.3 从客户端发起搜索后整个链路是什么在 Claude Desktop 里输入“帮我找一个 SQL Agent”Host 会经历以下步骤把用户输入交给大模型。大模型判断需要调用search_agents。Host 让 MCP Client 发送工具调用请求。Buy My Agent MCP Server 执行过滤逻辑返回 JSON。Host 把 JSON 结果交回给大模型。大模型根据 JSON 生成自然语言回答。因此工具返回的 JSON 字段名和结构要尽量稳定。如果字段名一会儿是name一会儿是title大模型的判断精度会下降。5.4 验证异常分支除了正常搜索还要验证至少三种异常情况。传不存在的 Agent ID应返回isError: true。传超范围的limit如 100SDK 应触发参数校验错误。数据源内部抛出异常Server 应返回错误而不是直接崩溃。这些验证可以在 MCP Inspector 里手动执行。如果某个异常分支把进程搞崩了要立即定位到对应的 catch 处理。6. 常见问题排查6.1 客户端提示找不到命令或进程立即退出现象配置 MCP Server 后客户端工具列表为空或者日志显示进程退出。可能原因command或args使用了相对路径。当前 Node 版本和项目依赖不兼容。编译后的dist/index.js不存在。检查方式node /absolute/path/to/buy-my-agent-mcp-server/dist/index.js手动执行后程序应保持运行不退出。如果直接报模块找不到说明node_modules没有安装或者路径写错。处理建议使用绝对路径。在项目目录执行npm install确认node_modules存在。重新执行npm run build。6.2 工具返回空结果或过滤结果不对现象搜索关键字后结果为空但数据里明明有匹配项。可能原因关键字没有做小写转换大小写不匹配。标签字段没有参与搜索。中文 Agent 描述使用了不合理的分词方式。检查方式node dist/index.js连接到 MCP Inspector直接输入keyword: SQL和keyword: sql对比结果。如果结果不同一定是过滤逻辑里的大小写处理问题。处理建议在过滤逻辑中统一toLowerCase()。把 name、description、tags 拼接后再搜索。中文场景下不要依赖空格分词先用子串匹配后续再引入更高级的搜索引擎。6.3 SDK 版本导致的 API 不存在现象编译报错例如server.tool is not a function或者某个 import 路径不存在。可能原因本地安装的 SDK 主版本和代码使用的 API 不一致。官方 SDK 在 0.x 和 1.x 之间调整了接口名称。package.json 锁定了旧版本依赖。检查方式npm list modelcontextprotocol/sdk查看实际版本号并对照官方 README 中的示例。处理建议使用npm update modelcontextprotocol/sdk升级到当前稳定版。如果项目对版本敏感至少固定主版本号例如^1.0.0避免偶然升级到不兼容版本。不要盲目复制旧博客里的代码先确认版本。6.4 JSON 序列化错误或中文乱码现象返回内容里有undefined或者中文变成转义字符客户端无法读取。可能原因Agent 数据里含有undefined字段被 JSON.stringify 忽略。返回的 content 不是字符串而是对象。检查方式const text JSON.stringify(agent); console.error(text.length);处理建议在数据结构上避免undefined可选字段统一用null。返回的text字段必须是字符串不要直接传对象。如果需要客户端可读性更好可以在序列化后把结果重新格式化。6.5 其它 MCP Server 常见问题速查表问题现象常见原因检查方式处理建议客户端连接时卡住stdio 没有正确连接或程序启动后立即退出手动执行 node dist/index.js检查路径、依赖和执行权限工具调用超时数据源远程请求太慢看服务端日志耗时增加超时时间返回局部结果返回内容过大limit 设得太大或返回完整 Agent 文档查看返回 JSON 长度限制返回字段和条数配置修改后不生效没有重启客户端检查进程是否残留重启客户端或清理被杀掉的进程7. 生产环境落地与最佳实践7.1 从本地 JSON 换成远程数据源本地常量数组只适合演示。生产环境的 Agent 数据通常存在数据库、CMS 或内部 API 中。建议在服务启动时加载一次并通过缓存降低上游压力。let cache: { updatedAt: number; agents: AgentInfo[] } | null null; const CACHE_TTL_MS 60_000; async function getAgents(): PromiseAgentInfo[] { if (cache Date.now() - cache.updatedAt CACHE_TTL_MS) { return cache.agents; } const agents await fetchAgentListFromRemote(); cache { updatedAt: Date.now(), agents }; return agents; }缓存不是越久越好。Agent 列表更新频率高时TTL 可以缩短到 10 秒如果数据很稳定TTL 可以延长到 5 分钟。search_agents工具内部改为调用getAgents()这样数据源变化不会影响工具接口。7.2 日志、错误处理与资源限制生产环境还需要考虑以下几点。使用日志框架输出到文件避免污染 stdout。捕获工具执行过程中的异常转换为isError: true返回而不是让 Server 崩溃。对limit设置硬上限防止一次调用返回过多内容。如果同时服务多个客户端需要评估进程模型是每个客户端一个进程还是通过 HTTP 流式通道共享服务。不要在高频调用路径里执行耗时较长的同步操作。MCP 工具的回调是异步的但也要避免无限制地等待远程服务。远程请求必须设置超时和重试策略。7.3 安全与权限设计Agent 搜索服务本身可能不涉及敏感数据但只要接入了外部数据源就要考虑权限边界。不要把数据库连接字符串写在代码里使用环境变量或配置文件。对返回的 Agent 链接做协议校验只允许http或https。如果工具允许用户传入外部 URL要做 SSRF 防护避免服务端去请求内网地址。记录谁调用了哪些工具方便审计异常访问。在 MCP 协议中工具权限通常由 Host 决定。Server 能做的是在工具内部再次校验入参不要轻信上层的规范约束。7.4 可复用检查清单在把 MCP Server 交付给客户端之前可以按这个清单检查一遍。[ ]npm run build是否能正常编译。[ ] 直接执行node dist/index.js是否保持运行。[ ] 用 MCP Inspector 调用每个工具是否正常。[ ] 入参非法时是否返回明确错误。[ ] 找不到数据时是否返回isError: true。[ ] stdout 是否只输出 JSON-RPC不包含调试日志。[ ] 远程数据源是否有超时和缓存。[ ] 客户端配置是否使用绝对路径。[ ] 是否验证过大小写、中文、特殊字符等搜索场景。[ ] 是否对返回结果大小做了上限控制。8. 从 Demo 到 Agent 市场的扩展方向8.1 增加 Resource 暴露 Agent 详情MCP 除了工具还有资源和提示词两种能力。资源适合暴露只读数据比如agent://sql-writer这样的 URI。客户端可以读取资源内容而不一定要通过工具调用。server.resource( agent-detail, agent://{id}, async (uri) { const id uri.pathname.replace(/, ); const agent AGENTS.find((item) item.id id); return { contents: [ { uri: uri.href, text: JSON.stringify(agent, null, 2) } ] }; } );资源适合描述稳定的知识型内容工具适合需要计算、查询和写操作的动作。如果一个能力既可以用工具又可以用资源表示优先选择工具因为工具参数更灵活也更容易被大模型按需调用。8.2 增加 Prompt 模板可以在 Server 里注册一个提示词模板比如“帮我比较两个 Agent”。这样客户端使用该模板时大模型会按照预设结构生成回答减少用户重复描述问题。server.prompt( compare-agents, Compare two AI agents from the catalog., { firstAgentId: z.string(), secondAgentId: z.string() }, ({ firstAgentId, secondAgentId }) ({}) );这只是其中一个方向。实际落地时Prompt 模板可以结合工具返回结果一起使用让大模型在对比回答中带上链接、成本和适用场景。8.3 组合检索和推荐策略当前示例的搜索逻辑比较简单只做了关键字、分类和标签匹配。如果要支持更丰富的能力可以按以下顺序扩展支持多标签 AND/OR 查询。对结果按评分、热门度、更新时间排序。把 SQLite 或 Redis 作为索引减少全量遍历。接入向量检索让描述相近的 Agent 也能被搜索到。增加收藏、订阅和推荐接口但这类写操作要单独评估权限。MCP Server 的好处在于这些扩展都不需要修改客户端。只要你保持工具名和参数结构稳定客户端侧会自动获得新能力。不要频繁改工具名否则会让大模型和已有客户端都感到困惑。从实践角度看先完成一个基于本地 JSON 数据的搜索 MCP Server再逐步替换数据源、增加缓存、完善错误处理和远程访问是最稳妥的推进方式。Search AI Agents from Any MCP Client 这个目标并不需要一开始就接入复杂的 Agent 市场后台先把协议链路和调试手段跑通后续的自然语言搜索、分类推荐、Agent 详情展示都是在同一套 MCP 能力边界内迭代。
返回列表