Agent学习笔记(三)——工具调用篇之MCP
文章目录
- Agent学习笔记(三)——工具调用篇之MCP
- 前言
- 一、MCP是什么?
-
- 1.1 MCP基础定义
- 1.2 为什么需要MCP
- 二、MCP核心原理
-
- 2.1 基本架构
-
- 三个核心角色
- 核心能力暴露
- 2.2 工作流程
-
- 流程步骤详解
- 关键特性
- 三、实战
-
- 3.1 MCP使用
- 3.2 实战案例:编写一个天气查询 MCP 服务
-
- 3.2.1 项目结构与依赖
- 3.2.2 核心代码实现
- 3.2.3 代码详解
-
- 1. 初始化 FastMCP Server
- 2. 工具函数装饰器 `@mcp.tool()`
- 3. 工具定义与文档
- 4. 错误处理
- 5. 启动 Server
- 3.2.4 配置到 Cline
- 3.2.5 核心要点总结
- 3.3 MCP内部交互情况
-
- 3.3.1 client与server间的交互
- 交互情况分析
-
- 1. 初始化阶段(Initialization)
- 2. 能力发现阶段(Capability Discovery)
- 3. 工具调用阶段(Tool Execution)
- 4. 交互特点
- 5. 通信流程
- 6. 总结
- 3.3.2 client与LLM间的交互
-
- client总结:
- 四、总结
-
- 4.1 核心认知
- 4.2 架构与流程
- 4.3 实战要点
- 4.4 交互机制
前言
在上一篇文章中,我们介绍了大模型如何通过 Function Calling 机制调用外部工具,从而打通 LLM 与外部世界的连接,完成复杂任务,并最终实现 Agent 的落地应用。然而,Function Calling 并非大模型调用工具的唯一方式。今天,我们将介绍另一种重要的工具调用协议——MCP(Model Context Protocol,模型上下文协议)。 本文内容大量参考了B站up主:马克的技术工作坊 的MCP系列视频,视频链接:https://www.bilibili.com/video/BV1uronYREWR,有需要的可以自行观看,一定会更为透彻地了解MCP的内核。
一、MCP是什么?
1.1 MCP基础定义
MCP是由 Anthropic 发布的开源、开放式AI通信协议与集成规范。其核心作用是标准化大语言模型(LLM)与外部数据源、工具、应用系统的双向交互逻辑,统一模型获取上下文、调用外部能力、读写外部资源的通用规则。该协议的作用类似于 USB-C 接口对于硬件设备的意义——它为人工智能应用提供了一种统一的标准接口,使得不同的数据源和工具能够无缝连接至 AI 模型。 这样说起来可能还是比较抽象,后面我们会进一步拆解MCP系统内部交互情况,从而帮助理解MCP的本质
1.2 为什么需要MCP
在上一章节我们介绍了Function Calling所解决的痛点问题,MCP和Function Calling一样,也使大模型具备调用工具的能力,那么既然已经有了Function Calling,为什么还需要MCP呢?
Function Calling 虽然强大,但存在以下固有局限性:
MCP 正是在这些痛点之上应运而生,其核心优势包括:
| 协议性质 | 模型API内置机制,非标准化协议 | 开源、开放的标准化协议 |
| 工具发现 | 需手动传入所有工具定义 | 支持动态发现与注册(MCP Server 自动暴露能力) |
| 上下文丰富度 | 仅支持函数调用 | 支持资源读取、提示词模板、工具调用等多种能力 |
| 集成成本 | 每个工具单独适配,切换模型需重写 | 统一协议,一次接入,多模型复用 |
| 生态扩展性 | 工具与模型强耦合 | 工具与模型解耦,Server 可独立部署维护 |
具体来说,MCP 带来了以下关键价值:
- 标准化与互操作性:MCP 定义了统一的通信规范,任何遵循该协议的 MCP Server 都可以被任意 MCP Client(如 Claude Desktop、自定义应用)直接使用,无需为每个工具单独编写适配代码。
- 动态工具发现:MCP Server 启动后会自动向 Client 注册其支持的工具、资源和提示词模板,Client 无需预知所有工具信息,实现了即插即用的效果。
- 丰富的上下文类型:除了工具调用,MCP 还支持资源(Resources,如文件内容、数据库记录)和提示词模板(Prompts),让模型能获取更全面的上下文信息,做出更准确的决策。
- 解耦与可维护性:工具提供方只需维护一个 MCP Server,所有 Client 共享同一接入方式;工具升级、新增能力时,Client 无需修改代码。
如果说 Function Calling 是为每个设备定制专用数据线,那么 MCP 就是为所有设备定义了统一的 USB-C 接口标准——后者显著降低了生态系统的连接复杂度,让工具与模型的组合更加灵活高效。
二、MCP核心原理
2.1 基本架构
MCP 采用经典的 客户端-服务器(Client-Server) 架构,由三个核心角色组成:
#mermaid-svg-aFDSvaABVRCMPgSQ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aFDSvaABVRCMPgSQ .error-icon{fill:#552222;}#mermaid-svg-aFDSvaABVRCMPgSQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aFDSvaABVRCMPgSQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aFDSvaABVRCMPgSQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aFDSvaABVRCMPgSQ .marker.cross{stroke:#333333;}#mermaid-svg-aFDSvaABVRCMPgSQ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aFDSvaABVRCMPgSQ p{margin:0;}#mermaid-svg-aFDSvaABVRCMPgSQ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster-label text{fill:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster-label span{color:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster-label span p{background-color:transparent;}#mermaid-svg-aFDSvaABVRCMPgSQ .label text,#mermaid-svg-aFDSvaABVRCMPgSQ span{fill:#333;color:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ .node rect,#mermaid-svg-aFDSvaABVRCMPgSQ .node circle,#mermaid-svg-aFDSvaABVRCMPgSQ .node ellipse,#mermaid-svg-aFDSvaABVRCMPgSQ .node polygon,#mermaid-svg-aFDSvaABVRCMPgSQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aFDSvaABVRCMPgSQ .rough-node .label text,#mermaid-svg-aFDSvaABVRCMPgSQ .node .label text,#mermaid-svg-aFDSvaABVRCMPgSQ .image-shape .label,#mermaid-svg-aFDSvaABVRCMPgSQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-aFDSvaABVRCMPgSQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aFDSvaABVRCMPgSQ .rough-node .label,#mermaid-svg-aFDSvaABVRCMPgSQ .node .label,#mermaid-svg-aFDSvaABVRCMPgSQ .image-shape .label,#mermaid-svg-aFDSvaABVRCMPgSQ .icon-shape .label{text-align:center;}#mermaid-svg-aFDSvaABVRCMPgSQ .node.clickable{cursor:pointer;}#mermaid-svg-aFDSvaABVRCMPgSQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aFDSvaABVRCMPgSQ .arrowheadPath{fill:#333333;}#mermaid-svg-aFDSvaABVRCMPgSQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aFDSvaABVRCMPgSQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aFDSvaABVRCMPgSQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aFDSvaABVRCMPgSQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aFDSvaABVRCMPgSQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aFDSvaABVRCMPgSQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster text{fill:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ .cluster span{color:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aFDSvaABVRCMPgSQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aFDSvaABVRCMPgSQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-aFDSvaABVRCMPgSQ .icon-shape,#mermaid-svg-aFDSvaABVRCMPgSQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aFDSvaABVRCMPgSQ .icon-shape p,#mermaid-svg-aFDSvaABVRCMPgSQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aFDSvaABVRCMPgSQ .icon-shape .label rect,#mermaid-svg-aFDSvaABVRCMPgSQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aFDSvaABVRCMPgSQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aFDSvaABVRCMPgSQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aFDSvaABVRCMPgSQ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
自然语言交互
MCP 协议(JSON-RPC)
调用
📦 MCP Server
🔧 Tools(工具)
📄 Resources(资源)
💬 Prompts(提示词模板)
🏠 Host(宿主应用)
MCP Client
🧠 LLM(大模型)
🌐 外部系统(数据库/API/文件系统)
三个核心角色
| MCP Host | 发起请求的宿主应用,通常是用户直接交互的界面 | Claude Desktop、VS Code 插件、自定义 Web 应用 |
| MCP Client | 内嵌于 Host 中的协议客户端,负责与 MCP Server 建立连接、发送请求、接收响应 | 每个 Host 内部持有一个或多个 Client 实例 |
| MCP Server | 提供具体能力的服务端,向 Client 暴露工具、资源和提示词模板 | 文件系统 Server、数据库查询 Server、GitHub API Server |
###MCP 使用 JSON-RPC 2.0 作为底层通信协议,目前支持三种传输方式:
- stdio 传输:Client 以子进程方式启动 Server,通过标准输入/输出进行通信,适合本地部署。
- SSE(Server-Sent Events)传输:通过 HTTP 长连接进行通信,适合远程 Server 或分布式部署。
- Streamable HTTP 传输:MCP 协议演进中引入的新传输方案,通过标准 HTTP POST 请求完成交互,同时支持流式响应。相比传统 SSE,它无需维护长连接,更易于在现有 HTTP 基础设施(如负载均衡、反向代理)上部署,兼容性更好。
| stdio | 本地部署、CLI 工具、子进程通信 | 零网络开销,适合单机场景 |
| SSE | 远程 Server、需要持续推送的场景 | 长连接,实时性好 |
| Streamable HTTP | 远程 Server、云原生部署 | 标准 HTTP,部署简单,支持流式响应 |
核心能力暴露
每个 MCP Server 启动后,会向 Client 暴露三类能力:
这种架构设计的核心优势在于:工具提供方只需维护一个 MCP Server,所有遵循协议的 Client 都能即插即用,真正实现了工具与
2.2 工作流程
一次完整的 MCP 交互遵循以下典型流程,以用户通过 MCP Client 查询数据库为例:
LLM
🌐 外部系统
📦 MCP Server
📡 MCP Client
🏠 MCP Host
👤 用户
LLM
🌐 外部系统
📦 MCP Server
📡 MCP Client
🏠 MCP Host
👤 用户
#mermaid-svg-b3LFtyUNRhiHxuFE{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b3LFtyUNRhiHxuFE .error-icon{fill:#552222;}#mermaid-svg-b3LFtyUNRhiHxuFE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b3LFtyUNRhiHxuFE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b3LFtyUNRhiHxuFE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b3LFtyUNRhiHxuFE .marker.cross{stroke:#333333;}#mermaid-svg-b3LFtyUNRhiHxuFE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b3LFtyUNRhiHxuFE p{margin:0;}#mermaid-svg-b3LFtyUNRhiHxuFE .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b3LFtyUNRhiHxuFE text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-b3LFtyUNRhiHxuFE .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-b3LFtyUNRhiHxuFE .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-b3LFtyUNRhiHxuFE #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-b3LFtyUNRhiHxuFE .sequenceNumber{fill:white;}#mermaid-svg-b3LFtyUNRhiHxuFE #sequencenumber{fill:#333;}#mermaid-svg-b3LFtyUNRhiHxuFE #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-b3LFtyUNRhiHxuFE .messageText{fill:#333;stroke:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b3LFtyUNRhiHxuFE .labelText,#mermaid-svg-b3LFtyUNRhiHxuFE .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .loopText,#mermaid-svg-b3LFtyUNRhiHxuFE .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-b3LFtyUNRhiHxuFE .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-b3LFtyUNRhiHxuFE .noteText,#mermaid-svg-b3LFtyUNRhiHxuFE .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-b3LFtyUNRhiHxuFE .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b3LFtyUNRhiHxuFE .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b3LFtyUNRhiHxuFE .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-b3LFtyUNRhiHxuFE .actorPopupMenu{position:absolute;}#mermaid-svg-b3LFtyUNRhiHxuFE .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-b3LFtyUNRhiHxuFE .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-b3LFtyUNRhiHxuFE .actor-man circle,#mermaid-svg-b3LFtyUNRhiHxuFE line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-b3LFtyUNRhiHxuFE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
输入自然语言查询
转发用户请求
1. 初始化连接(Initialize)
返回 Server 能力声明(工具、资源、提示词)
2. 列出可用工具(tools/list)
返回工具列表(如 query_database)
将工具信息传递给 LLM
LLM 决策是否调用工具
返回工具调用指令(query_database,参数:SQL)
3. 发起工具调用(tools/call)
转发工具调用请求
4. 执行实际操作(查询数据库)
返回查询结果
5. 返回工具执行结果
将结果传递给 LLM
LLM 总结工具输出
生成自然语言回复
6. 展示最终回复给用户
流程步骤详解
步骤 1:初始化连接(Initialize)
MCP Client 启动后,首先与 MCP Server 建立连接。双方通过 JSON-RPC 2.0 协议交换基础信息:
- Client 发送初始化请求,包含自身名称、版本号、支持的协议版本。
- Server 响应其能力声明,包括支持的协议版本、暴露的工具列表、资源列表和提示词模板列表。
步骤 2:能力发现(List Capabilities)
初始化完成后,Client 通过以下方法获取 Server 暴露的能力:
- tools/list:获取所有可用工具的定义(函数名、描述、参数 Schema)。
- resources/list:获取所有可读资源的 URI 列表。
- prompts/list:获取所有提示词模板的名称和参数。
这些能力信息会被传递给 Host 中的 LLM,作为模型决策的依据。
步骤 3:工具调用(tools/call)
当 LLM 根据用户输入判断需要调用某个工具时,Host 将模型返回的工具调用指令(工具名 + 参数)传递给 Client,Client 通过 tools/call 方法向 Server 发起调用请求。
步骤 4:执行实际操作
MCP Server 收到调用请求后,执行对应的实际操作,例如:
- 查询数据库并返回结果。
- 调用外部 API 获取实时数据。
- 读取文件系统中的文件内容。
- 执行计算或数据处理任务。
步骤 5:返回执行结果
Server 将执行结果以结构化格式返回给 Client,Client 再传递给 Host 中的 LLM。
步骤 6:生成最终回复
LLM 根据工具返回的结果,结合用户原始问题,生成自然语言回复,最终展示给用户。
关键特性
- 动态发现:Server 启动后自动注册能力,Client 无需预知所有工具信息。
- 标准化通信:所有交互基于 JSON-RPC 2.0,格式统一,易于扩展。
- 解耦设计:Server 只负责提供能力,不关心 Client 如何使用;Client 只负责转发请求,不关心 Server 如何实现。
- 支持多轮交互:在一次对话中,LLM 可以多次调用不同工具,逐步完成任务。
这种工作流程使得 MCP 能够像 USB-C 接口一样,实现工具的即插即用——开发者只需编写一个 MCP Server,所有遵循协议的 Client 都能自动发现并使用其能力,无需额外适配
三、实战
3.1 MCP使用
这里以Cline为例,介绍一下MCP的使用(Cline就是一个MCP Host)。 你可以在VS Code或Pycharm等IDE中的插件广场自行下载Cline插件
步骤 1:配置大模型 请自行在Cline设置中设置模型提供商和API-KEY等相应设置。这里以阿里云千问为例,配置了Plan Mode和Act Mode的相关信息。
步骤 2:MCP运行环境安装 要在本地运行 MCP Server,需要先准备好相应的运行环境。根据 MCP Server 的实现语言不同,可能需要安装以下环境:
- Node.js 环境:如果 MCP Server 是用 JavaScript/TypeScript 编写的,需要安装 Node.js(建议使用 LTS 版本),然后通过 npx 命令运行。
- Python 环境:如果 MCP Server 是用 Python 编写的,需要安装 Python(建议 3.8+ 版本),可以通过 uvx 工具运行(需先安装 uv),也可以直接使用 python 命令运行本地脚本。
- 其他语言:部分 MCP Server 可能使用 Rust、Go 等其他语言编写,需要安装对应的运行时环境。
环境验证方法:
node –version
npx –version
# 检查 Python
python –version # 或 python3 –version
pip –version
# 检查 uv(如果使用 uvx)
uv –version
常用的MCP资源网站有很多,可自行搜索,这里以魔搭社区的MCP广场为例:https://www.modelscope.cn/mcp 点开一个MCP详情页,可以看到右边的服务配置:
点到stdio,这就是需要写到MCP配置项的具体内容。值得注意的是,不少MCP网站给出的直接是Linux/mac系统的配置内容,如果是Windows系统不能直接使用,示例中的必应搜索中文给的比较全面,列出了不同系统下的不同配置: Linux/mac:{
"mcpServers": {
"bing-search": {
"args": [
"-y",
"bing-cn-mcp"
],
"command": "npx"
}
}
}
Windows:
{
"mcpServers": {
"bing-search": {
"args": [
"/c",
"npx",
"-y",
"bing-cn-mcp"
],
"command": "cmd"
}
}
}
因此,我们需要根据自身情况判断如何配置 先将上述配置复制下来,回到Cline,点击此处:
进入MCP设置:
点击Configure MCP Servers,即会跳转到配置文件:
我们将刚才在网页复制的内容粘贴到此处即可(已有MCP配置,则把网页"mcpServers": 后的内容增加到这个文件内即可,相信不必多说。)。保存后就会在刚才的MCP设置页发现服务启动了。 接下来进入Cline对话页面正常对话,大模型就会根据需要调用已经启用的MCP服务了。
3.2 实战案例:编写一个天气查询 MCP 服务
上一节我们介绍了如何配置和使用现成的 MCP 服务,体验了 MCP 的“即插即用”特性。接下来,让我们更进一步——自己动手编写一个 MCP 服务。通过实际开发,您将深入理解 MCP Server 的内部工作机制,并掌握如何将自定义能力封装成标准化的MCP工具。 让我们通过一个完整的实战案例,亲手编写一个 MCP 服务。我们将创建一个天气查询服务,它能够:
这个服务将使用 Python 的 mcp 库(特别是其高级封装 FastMCP)来快速构建。
3.2.1 项目结构与依赖
首先,创建一个新的 Python 项目,并安装必要的依赖:
# 创建项目目录
mkdir weather-mcp-server
cd weather-mcp-server
# 创建虚拟环境(可选但推荐)
python -m venv venv
source venv/bin/activate # Linux/macOS
# 或 venv\\Scripts\\activate # Windows
# 安装依赖
pip install mcp httpx
注意:mcp 库是开发 MCP Server 的核心库,httpx 用于异步 HTTP 请求。
3.2.2 核心代码实现
创建一个名为 weather_server.py 的文件,并写入以下代码:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# 初始化 FastMCP Server,设置日志级别为 ERROR 以减少控制台输出
mcp = FastMCP("weather", log_level="ERROR")
# 常量定义
NWS_API_BASE = "https://api.weather.gov" # 美国国家气象局 API 基础地址
USER_AGENT = "weather-app/1.0" # 自定义 User-Agent,符合 API 要求
async def make_nws_request(url: str) –> dict[str, Any] | None:
"""向 NWS API 发起请求,并处理异常"""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status() # 检查 HTTP 状态码
return response.json()
except Exception:
# 简化错误处理,实际项目中可记录日志
return None
def format_alert(feature: dict) –> str:
"""将警报数据格式化为易读的字符串"""
props = feature["properties"]
return f"""
事件: {props.get('event', 'Unknown')}
地区: {props.get('areaDesc', 'Unknown')}
严重程度: {props.get('severity', 'Unknown')}
描述: {props.get('description', 'No description available')}
应对指南: {props.get('instruction', 'No specific instructions provided')}
"""
@mcp.tool()
async def get_alerts(state: str) –> str:
"""获取美国指定州的天气警报
Args:
state: 美国州的两个字母代码(例如:CA, NY, TX)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "无法获取警报信息或未找到相关警报。"
if not data["features"]:
return "该州当前没有活跃的天气警报。"
alerts = [format_alert(feature) for feature in data["features"]]
return "\\n—\\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) –> str:
"""获取指定经纬度位置的天气预报
Args:
latitude: 纬度(例如:37.7749)
longitude: 经度(例如:-122.4194)
"""
# 第一步:获取该坐标点的元数据
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data:
return "无法获取该位置的天气预报数据。"
# 第二步:从元数据中提取预报接口 URL
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "无法获取详细的天气预报。"
# 第三步:格式化预报数据
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # 只显示接下来 5 个时段
forecast = f"""
{period['name']}:
温度: {period['temperature']}°{period['temperatureUnit']}
风速: {period['windSpeed']} {period['windDirection']}
详细预报: {period['detailedForecast']}
"""
forecasts.append(forecast)
return "\\n—\\n".join(forecasts)
if __name__ == "__main__":
# 启动 MCP Server,使用 stdio 传输方式
mcp.run(transport='stdio')
3.2.3 代码详解
1. 初始化 FastMCP Server
mcp = FastMCP("weather", log_level="ERROR")
- FastMCP 是 mcp 库提供的高级封装,简化了 Server 的创建过程。
- "weather" 是 Server 的名称,会在初始化时告知 Client。
- log_level="ERROR" 将日志级别设置为 ERROR,减少不必要的控制台输出。
2. 工具函数装饰器 @mcp.tool()
这是 FastMCP 的核心特性。只需在异步函数上添加 @mcp.tool() 装饰器,该函数就会自动注册为 MCP 工具,其函数名、文档字符串和参数信息会自动暴露给 Client。
3. 工具定义与文档
每个工具函数都包含详细的文档字符串(Docstring),MCP Client 会读取这些信息并传递给 LLM,帮助模型理解工具的功能和参数。
- get_alerts(state: str): 查询州级天气警报。
- get_forecast(latitude: float, longitude: float): 查询经纬度位置的天气预报。
4. 错误处理
make_nws_request 函数统一处理网络请求和 JSON 解析异常,返回 None。工具函数据此返回用户友好的错误信息,而不是抛出异常导致 Server 崩溃。
5. 启动 Server
if __name__ == "__main__":
mcp.run(transport='stdio')
- transport='stdio' 指定使用标准输入/输出进行通信,这是本地开发最常用的方式。
- Server 启动后,会等待来自 MCP Client 的连接和请求。
3.2.4 配置到 Cline
Linux/macOS:
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/path/to/your/weather_server.py"]
}
}
}
Windows:(我这里直接使用conda创建了一个虚拟环境mcp,然后使用mcp环境下的python运行服务)
{
"mcpServers": {
"weather": {
"disabled": false,
"timeout": 60,
"type": "stdio",
"command": "D:/miniconda3/envs/mcp/python.exe",
"args": [
"D:/project/stage2/MCP-stage2/weather/weather.py"
]
}
}
}
- “加州现在有天气警报吗?”
- “旧金山(37.7749, -122.4194)的天气怎么样?”
Cline(作为 MCP Host)会自动发现 weather Server 提供的两个工具,并由 LLM 决定何时调用它们。
3.2.5 核心要点总结
通过这个实战案例,我们可以总结出编写 MCP Server 的几个关键点:
提示:实际部署时,可以考虑使用 uvx 来运行 Python 脚本,避免全局 Python 环境问题。命令示例:uvx mcp run weather_server.py。
3.3 MCP内部交互情况
在2.2节中,我们介绍了MCP的完整工作流程,这一节,我们将进一步深扒整个工作流程中,模型、client、server之间的具体交互信息。 我们通过编写一个日志程序,截获client和server,LLM和client之间的信息交互详情。
3.3.1 client与server间的交互

mcp_logger.py 的作用是作为一个代理/包装器(wrapper),截获并记录通过标准输入输出(STDIN/STDOUT/STDERR)传递的数据流。 相应的mcp配置修改为(根据个人情况修改):
"weather": {
"disabled": false,
"timeout": 60,
"type": "stdio",
"command": "D:/miniconda3/envs/mcp/python.exe",
"args": [
"D:/project/stage2/MCP-stage2/weather/mcp_logger.py",
"D:/miniconda3/envs/mcp/python.exe",
"D:/project/stage2/MCP-stage2/weather/weather.py"
]
}
日志内容:
输入: {"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"Cline","version":"3.82.0"}},"jsonrpc":"2.0","id":0}
输出: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-11-25","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.27.2"}}}
输入: {"method":"notifications/initialized","jsonrpc":"2.0"}
输入: {"method":"tools/list","jsonrpc":"2.0","id":1}
输出: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\\n\\nArgs:\\n state: Two-letter US state code (e.g. CA, NY)\\n","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_alertsOutput","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\\n\\nArgs:\\n latitude: Latitude of the location\\n longitude: Longitude of the location\\n","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_forecastOutput","type":"object"}}]}}
输入: {"method":"resources/list","jsonrpc":"2.0","id":2}
输出: {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}
输入: {"method":"resources/templates/list","jsonrpc":"2.0","id":3}
输出: {"jsonrpc":"2.0","id":3,"result":{"resourceTemplates":[]}}
输入: {"method":"prompts/list","jsonrpc":"2.0","id":4}
输出: {"jsonrpc":"2.0","id":4,"result":{"prompts":[]}}
输入: {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":5}
输出: {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"\\nOvernight:\\nTemperature: 65°F\\nWind: 6 mph SW\\nForecast: Clear, with a low around 65. Southwest wind around 6 mph.\\n\\n—\\n\\nThursday:\\nTemperature: 79°F\\nWind: 3 to 9 mph SW\\nForecast: Sunny. High near 79, with temperatures falling to around 76 in the afternoon. Southwest wind 3 to 9 mph.\\n\\n—\\n\\nThursday Night:\\nTemperature: 66°F\\nWind: 5 to 9 mph SW\\nForecast: Partly cloudy. Low around 66, with temperatures rising to around 68 overnight. Southwest wind 5 to 9 mph.\\n\\n—\\n\\nFriday:\\nTemperature: 86°F\\nWind: 3 to 13 mph W\\nForecast: Mostly sunny. High near 86, with temperatures falling to around 83 in the afternoon. West wind 3 to 13 mph.\\n\\n—\\n\\nFriday Night:\\nTemperature: 70°F\\nWind: 7 to 12 mph SW\\nForecast: Partly cloudy. Low around 70, with temperatures rising to around 73 overnight. Southwest wind 7 to 12 mph.\\n"}],"structuredContent":{"result":"\\nOvernight:\\nTemperature: 65°F\\nWind: 6 mph SW\\nForecast: Clear, with a low around 65. Southwest wind around 6 mph.\\n\\n—\\n\\nThursday:\\nTemperature: 79°F\\nWind: 3 to 9 mph SW\\nForecast: Sunny. High near 79, with temperatures falling to around 76 in the afternoon. Southwest wind 3 to 9 mph.\\n\\n—\\n\\nThursday Night:\\nTemperature: 66°F\\nWind: 5 to 9 mph SW\\nForecast: Partly cloudy. Low around 66, with temperatures rising to around 68 overnight. Southwest wind 5 to 9 mph.\\n\\n—\\n\\nFriday:\\nTemperature: 86°F\\nWind: 3 to 13 mph W\\nForecast: Mostly sunny. High near 86, with temperatures falling to around 83 in the afternoon. West wind 3 to 13 mph.\\n\\n—\\n\\nFriday Night:\\nTemperature: 70°F\\nWind: 7 to 12 mph SW\\nForecast: Partly cloudy. Low around 70, with temperatures rising to around 73 overnight. Southwest wind 7 to 12 mph.\\n"},"isError":false}}
交互情况分析
从上述日志可以看出,Cline(客户端)与weather MCP服务(服务端)之间的交互遵循标准的JSON-RPC 2.0协议,整个过程可以分为以下几个阶段:
1. 初始化阶段(Initialization)
- 客户端请求:Cline发送initialize请求,携带协议版本("protocolVersion":"2025-11-25")和客户端信息("clientInfo":{"name":"Cline","version":"3.82.0"})
- 服务端响应:weather服务返回初始化结果,包含服务端信息("serverInfo":{"name":"weather","version":"1.27.2"})和自身能力声明
2. 能力发现阶段(Capability Discovery)
- 工具列表查询:Cline发送tools/list请求(id=1),服务端返回两个可用工具:
- get_alerts:获取美国各州天气警报
- get_forecast:获取指定经纬度的天气预报
- 资源列表查询:Cline发送resources/list请求(id=2),服务端返回空数组,表示不提供资源
- 资源模板查询:Cline发送resources/templates/list请求(id=3),服务端返回空数组
- 提示词列表查询:Cline发送prompts/list请求(id=4),服务端返回空数组
3. 工具调用阶段(Tool Execution)
- 天气预报请求:Cline发送tools/call请求(id=5),调用get_forecast工具,参数为纽约市的经纬度(纬度40.7128,经度-74.006)
- 天气预报响应:服务端返回详细的天气预报数据,包含:
- 未来几天的温度、风速、天气状况
- 结构化数据(structuredContent)和文本格式(content)
- 成功标志("isError":false)
4. 交互特点
5. 通信流程
Cline客户端 → weather MCP服务
1. 初始化连接(initialize)
2. 发送初始化完成通知(notifications/initialized)
3. 查询可用工具列表(tools/list)
4. 查询资源列表(resources/list)
5. 查询资源模板(resources/templates/list)
6. 查询提示词列表(prompts/list)
7. 调用具体工具(tools/call)
8. 获取工具执行结果
这种交互模式使得Cline能够动态发现weather服务的能力,并根据用户需求调用相应的
6. 总结
通过上述分析,我们可以清晰地看到,Client 与 Server 之间的交互过程是完全模型无关的。整个通信完全遵循标准的 JSON-RPC 2.0 协议进行,这正是 MCP(Model Context Protocol)的核心设计理念。这一机制深刻地揭示了 MCP 的本质:通过构建一个标准化的通信层,将 AI 模型(作为决策者)与外部工具/数据(作为执行者)解耦,从而打造一个开放、可扩展的工具生态系统。
换句话说,只要我们启动了一个 MCP 服务,并且遵循这套通信协议,我们就可以直接与该服务进行交互(此时我们自身就扮演了 Client 的角色)。
下面让我们通过一个实际测试来验证这一点(以我的本地环境为例):
步骤 1:运行 MCP 服务代码
cd D:\\project\\stage2\\MCP-stage2\\weather # 进入项目文件夹
conda activate mcp # 激活虚拟环境
python weather.py # 启动服务

步骤 2:与 MCP 服务直接交互
复制上述日志中的初始化请求:
{"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"Cline","version":"3.82.0"}},"jsonrpc":"2.0","id":0}
服务响应: 
可以看到,这与之前的日志内容完全一致。后续的请求与响应也遵循相同的模式: 
相信通过这个完整的流程演示,您已经对 MCP 的本质有了更深入的理解。
3.3.2 client与LLM间的交互
在理清了client与server之间的交互机制后,我们进一步探究client与LLM(大语言模型)之间的交互过程。与上一节类似,我们需要在Cline的模型服务层面进行观察。
Cline支持OpenAI兼容的模型服务,因此我们可以在本地启动一个符合OpenAI兼容规范的大模型服务。为此,我使用大模型帮助编写了一个本地模型服务代码,该服务不仅能在Cline中正常使用,还能记录Cline与LLM之间的所有交互信息,并将这些信息保存到llm.log文件中。本次实验使用的是阿里云的qwen3.6-plus模型。
在Cline中配置相应的模型信息后,即可使用该本地服务进行交互。详细的日志内容请参考本文档附件。
注:由于Cline的系统提示词较长,此处不作展示。有兴趣的读者可以下载本文档绑定的资源进行查阅。
在Cline的系统提示词中详细介绍了各种规则,如输出的格式。我们所开启的MCP服务也会被注入到提示词中,供模型选择。这里主要看MCP工具的使用方式,提示词要求当大模型需要使用MCP工具时,按照以下xml格式输出内容(其他包括自带工具的使用,思考过程,结束对话等,均类似该格式):
<use_mcp_tool>
<server_name>server name here</server_name>
<tool_name>tool name here</tool_name>
<arguments>
{
"param1": "value1",
"param2": "value2"
}
</arguments>
<task_progress>Checklist here (optional)</task_progress>
</use_mcp_tool>
这样,当模型发送指定 XML 格式的工具调用请求时,Cline 便能解析出 MCP 工具的具体调用需求,并将其转换为前文所述的 JSON-RPC 2.0 固定 JSON Schema,进而向 Server 发送请求。
用户提问
↓
MCP Client(注入工具列表 + 格式规则 → 发给LLM)
↓
LLM 返回 <use_mcp_tool> XML 格式指令
↓
MCP Client 解析 XML → 转为 JSON-RPC 2.0
↓
MCP Server 执行工具 → 返回结果
↓
MCP Client 把结果回传给 LLM
↓
LLM 生成最终答案
以weather服务为例,当我们询问天气时,模型的回复是这样的:
是不是完全符合Cline在系统提示词中所约定的格式,做个小实验,我们直接把这整段提示词+用户提问丢给DeepSeek,它的回复是这样的:
如出一辙。
然后以下是Cline与大模型的整个交互情况:
相信理解了Cline与模型的交互逻辑后,我们也就会自己写一个MCP client了。
client总结:
MCP Client = 格式翻译官 + 协议转发器
MCP Client 是连接大模型与 MCP Server 的中间协调层,它负责教模型怎么调用工具,并把模型的自然语言指令翻译成 MCP Server 能听懂的标准协议。
核心职责如下: 1. 上下文注入与格式约定 MCP Client 会自动将已挂载的 MCP 服务列表、工具名称、工具入参描述,以及固定的输出格式规范(例如XML 标签) 注入到大模型的系统提示词中,告诉模型:
“如果需要调用工具,请严格使用 <use_mcp_tool> XML 结构返回。”
2. 请求转发与响应解析 接收用户输入,转发给大模型;接收大模型返回的文本结果,识别并提取其中的工具调用指令。 3. 协议格式转换(核心动作) 将模型输出的XML 格式工具调用,解析并转换为 JSON-RPC 2.0 协议规定的固定 JSON Schema,实现 “模型易读格式” 到 “机器易读格式” 的翻译。 4. 工具调用与结果回传 把标准 JSON-RPC 请求发送给对应 MCP Server 执行,接收执行结果后,再将结果回填到大模型上下文,让模型继续生成最终回答。
四、总结
本文从理论到实战,系统梳理了 MCP(Model Context Protocol)的核心概念与使用方式,主要要点如下:
4.1 核心认知
- MCP 是什么:MCP 是一种开放协议,为 AI 模型(LLM)提供标准化的工具调用接口,让模型能够安全、可控地访问外部工具和数据源。
- 为什么需要 MCP:解决了传统 Function Calling 方案中工具定义分散、格式不统一、难以复用的问题,实现了工具与模型的解耦。
4.2 架构与流程
- 三层架构:MCP Host(LLM 应用)、MCP Client(协议客户端)、MCP Server(工具提供方)各司其职。
- 标准化交互:通过 JSON-RPC 协议完成初始化、能力发现、工具调用等流程,通信过程清晰可追踪。
4.3 实战要点
- 快速上手:使用 FastMCP 框架可大幅降低开发门槛,几行代码即可暴露一个工具。
- 工具定义:通过 @mcp.tool() 装饰器配合类型注解和文档字符串,即可完成工具注册与 Schema 自动生成。
- 配置集成:将 MCP Server 配置到 Cline 等客户端后,LLM 即可在对话中按需调用工具。
4.4 交互机制
- Client ↔ Server:通过标准化的初始化、能力发现、工具调用流程,实现工具的动态注册与调用。
- Client ↔ LLM:Client 将工具 Schema 注入 LLM 的 system prompt,LLM 根据用户意图自主决定是否调用工具,Client 负责执行调用并返回结果。




