MCP客户端开发实战:手把手教你打造一个MCP客户端,保姆级教程!

发布时间:2026/9/27 20:37:40
MCP客户端开发实战:手把手教你打造一个MCP客户端,保姆级教程! 1. 从零写一个 MCP 客户端到底难在哪MCPModel Context Protocol是 Anthropic 开源的一套协议它把「大模型怎么调用外部工具和数据源」这件事标准化了。你可以把它理解成 AI 世界的 USB-C 接口以前每接一个系统就要写一套适配代码现在只要双方都遵守 MCP就能即插即用。MCP 客户端MCP Client就是那个「插头」——它负责跟 MCP Server 建立 1:1 连接把 Server 暴露的工具Tools、资源Resources、提示模板Prompts翻译成大模型能理解的函数定义再把模型的调用意图转发回去执行。这篇面向的是想自建 MCP 客户端的开发者你可能已经用过 Claude Desktop、Cursor 这类现成 Host但想在自己的应用里嵌入 MCP 能力或者想搞清楚「工具发现—调用—回显」这条链路到底怎么跑通。我会带你从初始化连接开始一步步搭出一个可运行的最小客户端骨架包含 config.toml 与 settings.json 配置、CC Switch 与 Cline 的接入示例以及连接验证和工具调用回显的完整动作。全程可复制、可跟做踩过的坑我也会标出来。先说清楚三个角色不然后面容易绕晕。MCP Host 是面向用户的程序比如 Claude Desktop、ClineMCP Client 是 Host 内部跟 Server 保持 1:1 连接的协议客户端MCP Server 是轻量级程序通过标准化协议公开特定功能。我们要写的就是中间那个 Client。传输层目前主流两类Stdio本地进程走标准输入输出和 HTTP SSE远程服务走 Server-Sent Events。本地工具用 Stdio部署在服务端的用 SSE这个选择会直接影响你的配置结构。2. 前置准备用 TaoToken 拿到可用的模型与 KeyMCP 客户端本身只负责协议转发真正决定「模型能不能正确选择工具」的是背后的大模型。所以第一步不是写代码而是先把模型通道准备好。我这边习惯用 TaoToken 做统一接入它的官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个地址不加 UTM 参数直接填进配置即可。你需要做的动作很简单登录后进入控制台创建一个 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建完把 Key 复制出来后面所有配置里的OPENAI_API_KEY或ANTHROPIC_API_KEY都填它。注意Key 只显示一次建议先存进环境变量再写代码别硬编码进仓库。我一般用export TAOTOKEN_API_KEYsk-xxx然后在代码里读process.env。如果你只是想先验证模型对话是否通可以直接用模型对话页试一句 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。确认能正常返回再往下写客户端能省掉一半「到底是模型问题还是代码问题」的排查时间。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面把 base_url 和鉴权头写得很清楚照着填不会错。3. 可复制配置config.toml 与 settings.json 骨架配置是整个客户端的入口。我把它拆成两份一份描述「有哪些 Server」一份描述「客户端自身行为」。前者用 config.toml后者用 settings.json职责分开改起来不容易乱。3.1 config.toml声明 MCP Server 列表# config.toml # 每个 [[servers]] 块描述一个 MCP Server [[servers]] name demo-stdio type stdio command node args [/Users/you/mcp/build/demo-stdio.js] enabled true timeout_ms 15000 [[servers]] name weather-stdio type stdio command node args [/Users/you/mcp/build/weather-stdio.js] enabled true timeout_ms 15000 [[servers]] name demo-sse type sse url http://localhost:3001/sse enabled false timeout_ms 20000type只有stdio和sse两种。enabled控制是否在启动时连接调试阶段建议只开一个减少干扰。timeout_ms是单次工具调用的超时本地脚本给 15 秒够用远程 SSE 给 20 秒更稳。3.2 settings.json客户端行为与模型参数{ client: { name: my-mcp-client, version: 1.0.0, logLevel: debug, retry: { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 4000 } }, model: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, modelName: gpt-4-turbo-preview, temperature: 0.2 }, tools: { autoApprove: false, maxRounds: 5 } }retry是错误重试的核心maxAttempts是最大尝试次数baseDelayMs是首次退避延迟后面按指数增长但不超过maxDelayMs。maxRounds限制「模型调用工具—拿结果—再调用」的循环轮数防止死循环烧 token。autoApprove设 false 时每次工具调用前会打印确认调试期强烈建议保持 false。3.3 CC Switch 接入示例CC Switch 用来在多个模型通道之间切换配置里指定 base_url 和 Key 即可{ providers: [ { name: taotoken, baseUrl: https://taotoken.net/api, apiKey: ${TAOTOKEN_API_KEY}, models: [gpt-4-turbo-preview, claude-3-5-sonnet] } ], active: taotoken }3.4 Cline 接入示例Cline 作为 Host 时它自己就是 MCP Client你只需要在它的 MCP 配置里声明 Server{ mcpServers: { demo-stdio: { command: node, args: [/Users/you/mcp/build/demo-stdio.js], disabled: false }, demo-sse: { url: http://localhost:3001/sse, disabled: true } } }Cline 会自动完成连接、工具发现和调用适合你不想自己写 Client 时快速验证 Server 是否可用。但如果你要嵌入自己的应用还是得回到下面的自建 Client 路线。4. 客户端核心链路连接、发现、调用、回显4.1 初始化连接连接分两步读配置、建 transport。Stdio 用StdioClientTransportSSE 用SSEClientTransport。关键点是 Stdio 的env要把当前进程环境变量透传进去否则 Server 里读不到 Key。import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; import { SSEClientTransport } from modelcontextprotocol/sdk/client/sse.js; async function connect(serverCfg) { let transport; if (serverCfg.type stdio) { transport new StdioClientTransport({ command: serverCfg.command, args: serverCfg.args, env: Object.fromEntries( Object.entries(process.env).filter(([, v]) v ! undefined) ), }); } else { transport new SSEClientTransport(new URL(serverCfg.url)); } const client new Client( { name: my-mcp-client, version: 1.0.0 }, { capabilities: { prompts: {}, resources: {}, tools: {} } } ); await client.connect(transport); return client; }capabilities里声明你支持哪些能力声明了 tools 才能调listTools。这一步如果报Connection closed九成是 command 路径写错或 Node 版本不匹配。4.2 工具发现连接成功后立刻拉一次工具列表这是「工具发现」的标准动作async function discoverTools(client, serverName) { const res await client.listTools(); return res.tools.map((tool) ({ type: function, function: { name: ${serverName}__${tool.name}, description: [${serverName}] ${tool.description}, parameters: tool.inputSchema, }, })); }注意工具名要加serverName__前缀。多个 Server 可能有同名工具不加前缀会冲突而且回显时你分不清是哪个 Server 执行的。4.3 工具调用与结果回显把发现到的工具塞进模型的tools参数模型返回tool_calls后解析出 serverName 和 toolName找到对应 client 执行async function callTool(sessions, toolCall) { const [serverName, toolName] toolCall.function.name.split(__); const client sessions.get(serverName); if (!client) throw new Error(Server ${serverName} not connected); const args JSON.parse(toolCall.function.arguments); const result await client.callTool({ name: toolName, arguments: args }); const text result.content.map((c) c.text).join(\n); console.log([tool] ${serverName}.${toolName} - ${text}); return text; }回显这一步别省。把serverName.toolName - 结果打出来出问题时一眼能看出是模型选错工具、参数解析失败还是 Server 执行报错。4.4 错误重试网络抖动、Server 冷启动、SSE 断连都会让单次调用失败。加一层指数退避async function withRetry(fn, { maxAttempts, baseDelayMs, maxDelayMs }) { let lastErr; for (let i 0; i maxAttempts; i) { try { return await fn(); } catch (err) { lastErr err; const delay Math.min(baseDelayMs * 2 ** i, maxDelayMs); console.warn(attempt ${i 1} failed: ${err.message}, retry in ${delay}ms); await new Promise((r) setTimeout(r, delay)); } } throw lastErr; }只对「可重试错误」重试超时、连接断开、5xx。参数校验失败4xx重试没意义直接抛给用户改参数。4.5 日志排查日志分三级debug打协议帧和工具列表info打连接和调用摘要error打异常堆栈。调试期把logLevel设成debug能看到完整的 JSON-RPC 往返。生产环境降到info避免日志爆炸。我习惯在每次callTool前后各打一条中间夹一条耗时排查慢调用特别有用。5. 验证请求确认连接与工具回显成功写完别急着接模型先单独验证协议层。第一步只连接不调模型看工具列表能不能拉出来node build/client.js --server demo-stdio --list-tools期望输出类似Connected to server demo-stdio with tools: [ add, echo ]第二步直接调一个工具绕过模型验证执行链路node build/client.js --server demo-stdio --call add --args {a:2,b:3}期望回显[tool] demo-stdio.add - 5第三步接上模型做端到端验证。输入「帮我算一下 2 加 3」观察日志里是否出现tool_calls、是否命中demo-stdio__add、回显是否为 5。三步都过说明连接、发现、调用、回显四条链路全通。如果第二步过、第三步不过问题在模型侧去模型对话页确认模型是否支持 function calling。6. 本篇常见错排查报错Connection closedStdio 的 command 路径不对或 Node 版本低于 18。用绝对路径别用~SDK 不会自动展开。报错Server not connected工具名前缀解析错了。检查listTools返回的 name 和tool_calls里的 name 是否都带了serverName__。工具调用返回空Server 的content数组里 type 不是text或者你只取了第一个元素。用map(c c.text).join(\n)兜底。SSE 连不上先确认 Server 的/sse和/messages两个端点都在且sessionId透传正确。用 curl 直接打/sse看有没有事件流返回。模型不调用工具tool_choice设成auto时模型可能选择直接回答。把工具描述写清楚参数 schema 用标准 JSON Schema别用自定义格式。重试把 4xx 也重试了在withRetry里判断err.status4xx 直接抛只重试 5xx 和超时。日志里看不到协议帧logLevel没设成debug或者 SDK 的 logger 没接上。检查 settings.json 是否被正确加载。排障时优先看 API Keys 和接入文档Key 权限和 base_url 是最容易配错的两处 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 和 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果怀疑是模型本身的问题去模型对话页单独试一句最快 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。7. 长期编码与 Agent 场景的接入建议如果你打算把这个客户端用在长期编码或 Agent 场景单次对话的临时 Key 不够用建议走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它更适合高频、长会话的调用模式配合上面的重试和日志能撑住连续几小时的 Agent 循环。另外两个实战建议。第一把maxRounds设成 5 到 8Agent 场景下模型可能连续调多个工具轮数太小会中途截断太大又容易失控。第二工具发现结果做本地缓存Server 不重启就不用每次重拉能省掉不少握手开销。Claude Code 相关的接入细节可以参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-codeutm_campaignrewrite 里面把 Anthropic 风格的接入方式讲得比较细。最后一句实在话MCP 客户端最难的不是写代码是搞清楚「模型选工具」和「协议传工具」是两件事。协议层用上面的三步验证法能快速定位模型层则要靠工具描述和 schema 质量。把这两层分开调比混在一起 debug 快得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询