当前位置: 首页 > java >正文

MCP简介和应用

1. MCP 简解:

你可以把 MCP 想象成 AI 应用的 USB-C 接口。就像 USB-C 数据线标准化了笔记本电脑连接各种外设(如外置硬盘、显示器或充电器)的方式一样,MCP 为 LLM 连接不同的数据源和工具提供了一个标准化的接口。它是一个开放协议,定义了应用程序如何为 LLM 提供上下文,本质上是赋予了它们“手脚”,让它们能够伸向现实世界并执行各种操作。

在这里插入图片描述
MCP 整个运作流程的主要组成部分有客户端、LLM、和 MCP 服务端三部分组成。

  1. 客户端: 这是你与之交互的界面,通常是集成 LLM 的 AI 助手或应用程序。例如,Claude 的官方客户端,或者 Cherry-Studio 等第三方工具。客户端扮演着视觉载体和整体用户体验的整合者角色。
  2. LLM(大语言模型): 这是“大脑”——AI 模型的核心。目前主流的大模型,如 DeepSeek、千问 3 等,都提供了 MCP 标准扩展能力。虽然技术上 LLM 通过“主机”(Host)与外部交互,为了便于理解,我们在此统称为 LLM。
  3. MCP 服务器: 这是实现 LLM 扩展能力的基础。LLM 本身并不具备这些能力,MCP 服务器通过实现这些能力(比如读取桌面文件、查询天气),并将调用方式抽象成接口暴露给 LLM。本质上,它将 LLM 的请求转化为现实世界中的行动。

整个 MCP 流程是通过 LLM 与 MCP 服务器的交互来实现的。客户端则负责提供可视化的载体和工程上的整合。。

在这里插入图片描述

现在这种 MCP 流程根据 LLM 和 MCP 服务器在不同的位置有不同的组合

  • 本地客户端 + 本地 MCP + 云LLM: 这是常见的设置,例如 Claude 官方建议下载其客户端并集成 MCP 服务器。在这种模式下,客户端和 MCP 服务器都在你的本地设备上运行,处理与本地系统的交互(如访问本地文件),而 LLM 本身则部署在云端。

  • 云LLM+云MCP+Web:而在阿里云百练的官网,官方给出了云LLM+云MCP+Web的组合(这是官方主推的方式,他们显然不想把客户端市场拱手让给 Cherry-Studio 、Cline等第三方客户端)。这种 LLM 和 MCP 在一端的理论上延迟很低。但是目前 MCP 处于蓬勃发展期间,这种不自由的方式显然不是各位玩家的热爱(毕竟云托管要付一份额外的钱)。
    在这里插入图片描述
    在这里插入图片描述

值的注意的是,虽然阿里云嘴上说本地不支持,实际上预留了标准的MCP调用规范,这为本地化操作提供了更多可能性。

2. MCP实践

2.1 MCP客户端

MCP客户端,往往来说目前市面上相对较少,目前除了各个大厂,免费的第三方客户端集成较好的有 Cherry-Studio 、Cline

他们在实现集成各种第三方API的同时也实现了MCP的一个中继器,可以转发 LLM 和 MCP 服务器之间的调用操作。

以 Cherry-Studio 为例,客户端在收到 MCP 包含 MCP 命令的消息的时候,会调用 MCP服务器,并发 MCP 服务器反馈的结果组合成新的信息后发送给 LLM
·1

当大语言模型(LLM)需要使用 MCP 工具时,您的客户端与 LLM 之间的通信通常涉及一个两步过程。这就是为什么它通常比简单查询消耗更多 Token 的原因。让我们通过手动模拟一个虚假的 MCP 工具注册和观察,来详细解析这个流程。

  1. LLM 决定并指令
    客户端发送: 你的问题 + 可用的 MCP 工具定义(比如你的“calculate”工具)。
    LLM 回复: LLM 分析后,如果需要工具,它会直接返回工具调用指令(例如,调用“calculate”并带参数“2 + 2”)。

注意: 此时 LLM 不会给出任何自然语言的答案或额外信息,它只是发出指令。

  1. 客户端执行并反馈,LLM 给出答案
    客户端行动: 客户端收到指令后,会真正调用你的 MCP 服务器(“calculate”服务)执行操作,并获取结果(例如,“4”)。
    客户端发送: 客户端将工具的执行结果添加到对话历史中,并再次发送给 LLM。
    LLM 回复: 收到工具结果后,LLM 才能理解并给出最终的、完整的自然语言回答(例如,“2 + 2 等于 4。”)。
    为什么两次?
    LLM 自身不能执行代码,它只负责“决策”(决定用哪个工具)和“语言生成”(整合结果)。客户端负责“执行”和“协调”。这种“决策-执行-整合”的模式,导致了两次与 LLM 的通信,也因此消耗了更多 Token。
    在这里插入图片描述
{"Response": {"choices": [{"message": {"content": "","role": "assistant","tool_calls": [{"index": 0,"id": "call_13b96f2bb72d4b54afd0b4","type": "function","function": {"name": "calculate","arguments": "{\"expression\": \"2 + 2\"}"}}]},"finish_reason": "tool_calls","index": 0,"logprobs": null}],"object": "chat.completion","usage": {"prompt_tokens": 190,"completion_tokens": 19,"total_tokens": 209,"prompt_tokens_details": {"cached_tokens": 0}},"created": 1749265696,"system_fingerprint": null,"model": "qwen-plus","id": "chatcmpl-219316df-c150-9053-816b-123437e432bf"}
}
package client_demoimport ("bytes""encoding/json""fmt""io/ioutil""net/http""testing"
)// 定义请求数据结构
type Message struct {Role      string     `json:"role"`Content   string     `json:"content,omitempty"` // Content can be omitted for tool callsToolCalls []ToolCall `json:"tool_calls,omitempty"`
}type ToolCall struct {ID       string       `json:"id"`Type     string       `json:"type"`Function FunctionCall `json:"function"`
}type FunctionCall struct {Name      string                 `json:"name"`Arguments map[string]interface{} `json:"arguments"`
}type Request struct {Model    string    `json:"model"`Messages []Message `json:"messages"`// Add tools definition to the requestTools []Tool `json:"tools,omitempty"`
}type Tool struct {Type     string   `json:"type"`Function Function `json:"function"`
}type Function struct {Name        string                 `json:"name"`Description string                 `json:"description,omitempty"`Parameters  map[string]interface{} `json:"parameters,omitempty"`
}func TestClient(t *testing.T) {// 获取API Key(确保已设置环境变量DASHSCOPE_API_KEY)apiKey := "" // Replace with your actual API Key// Define your MCP tool "calculate"// This describes the tool to the LLMcalculateTool := Tool{Type: "function",Function: Function{Name:        "calculate",Description: "A tool to perform mathematical calculations.",Parameters: map[string]interface{}{"type": "object","properties": map[string]interface{}{"expression": map[string]interface{}{"type":        "string","description": "The mathematical expression to evaluate.",},},"required": []string{"expression"},},},}// Construct the request body with the tool definitionrequestBody := Request{Model: "qwen-plus",Messages: []Message{{Role:    "system",Content: "You are a helpful assistant with access to a calculator tool.",},{Role:    "user",Content: "计算 2 + 2 等于多少?", // This is the user's query that might trigger the tool},},Tools: []Tool{calculateTool}, // Crucial: Register your tool with the LLM}// Serialize request bodyjsonBody, err := json.Marshal(requestBody)if err != nil {panic(fmt.Sprintf("Error marshaling JSON: %v", err))}fmt.Printf("Request Body: %s\n", jsonBody) // Print the request body for debugging// Create HTTP requestreq, err := http.NewRequest("POST","https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",bytes.NewBuffer(jsonBody))if err != nil {panic(fmt.Sprintf("Error creating request: %v", err))}// Set request headersreq.Header.Set("Authorization", "Bearer "+apiKey)req.Header.Set("Content-Type", "application/json")// For DashScope, adding X-DashScope-Api-Sequence might be useful for certain features,// but for basic tool calls, it's often not strictly necessary.// Send requestclient := &http.Client{}resp, err := client.Do(req)if err != nil {panic(fmt.Sprintf("Error sending request: %v", err))}defer resp.Body.Close()// Read responsebody, err := ioutil.ReadAll(resp.Body)if err != nil {panic(fmt.Sprintf("Error reading response: %v", err))}// Handle responseif resp.StatusCode != http.StatusOK {panic(fmt.Sprintf("API returned status %d: %s", resp.StatusCode, body))}fmt.Printf("Response: %s\n", body)}

2.2 MCP服务器

上面说了客户端如何执行 MCP 指令的,下面我们来介绍一下如何使用 Go 创建一个MCP 服务,MCP服务器本质来说和GRPC的接口一致,一般来说 MCP 服务器没有状态,所以说也不需要常驻运行。

MCP 通信目前的基本逻辑还是跨进行通信(IPC),和普通引用跨进程通信没有什么区别。
目前主流的交互方式有三种:

  1. stdio(标准输入输出流)
  2. sse(服务器发送事件)
  3. 可流式传输 HTTP

目前运用较多的还是标准输入输出流,我们这里也以这个为主。


这里的实现就是对照上文的计算函数的真实实现。

由于 Go 官方没有 MCP 服务端的 SDK,我们使用这个包实现github.com/mark3labs/mcp-go,本质来说就是通过输入输出流定义交互接口,和普通跨进程通信无异。

package mainimport ("context""fmt""github.com/mark3labs/mcp-go/mcp""github.com/mark3labs/mcp-go/server"
)func main() {// Create a new MCP servers := server.NewMCPServer("Calculator Demo","1.0.0",server.WithToolCapabilities(false),server.WithRecovery(),)// Add a calculator toolcalculatorTool := mcp.NewTool("calculate",mcp.WithDescription("Perform basic arithmetic operations"),mcp.WithString("operation",mcp.Required(),mcp.Description("The operation to perform (add, subtract, multiply, divide)"),mcp.Enum("add", "subtract", "multiply", "divide"),),mcp.WithNumber("x",mcp.Required(),mcp.Description("First number"),),mcp.WithNumber("y",mcp.Required(),mcp.Description("Second number"),),)// Add the calculator handlers.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {// Using helper functions for type-safe argument accessop, err := request.RequireString("operation")if err != nil {return mcp.NewToolResultError(err.Error()), nil}x, err := request.RequireFloat("x")if err != nil {return mcp.NewToolResultError(err.Error()), nil}y, err := request.RequireFloat("y")if err != nil {return mcp.NewToolResultError(err.Error()), nil}var result float64switch op {case "add":result = x + ycase "subtract":result = x - ycase "multiply":result = x * ycase "divide":if y == 0 {return mcp.NewToolResultError("cannot divide by zero"), nil}result = x / y}return mcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil})// Start the serverif err := server.ServeStdio(s); err != nil {fmt.Printf("Server error: %v\n", err)}
}

Go 可以很方便的把 MCP 编译成二进制文件,编译后可以直接把文件放置 /user/local/bin 或者 c:/windows/System32下。

在Cherry Studio可以如此设置
在这里插入图片描述

在Cherry Studio中就可以很方便的使用了。
在这里插入图片描述

如果想自己体检更好的联动效果,更多更复杂的 MCP 服务器必不可少,可以去现在使用免费的 MCP 服务器,

  1. MCP.so:https://mcp.so/
  2. DeepNLP Open MCP Marketplace Plugin:https://github.com/AI-Agent-Hub/mcp-marketplace

如果开发自然少不了调试,在最新版本的 PostMan中,已经集成了 MCP 服务器的调试功能。
在这里插入图片描述

3. 当前 MCP 的主要挑战:

  1. 多工具调用的不准确性与复杂接口的冲突:
    目前,LLM 在进行**多工具调用(Multi-tool Calling)**时,准确性仍然是老大难问题。尽管 LLM 能理解何时需要外部工具,但当一个复杂任务需要调用多个工具、甚至按特定顺序或条件进行时,LLM 往往难以精准编排。

    • 冲突点: 为了让 LLM 能够处理复杂功能,开发者不得不向其暴露(expose)更多的接口(API)。然而,暴露的接口越多,LLM 选择和组合工具的难度就越大,出现误判或调用失败的概率也随之增加。这形成了一个矛盾:要实现复杂功能,就需要更多接口;接口越多,调用的准确性就越难以保证。
  2. MCP 能力暴露效率低下,制约 Agent 终极形态:
    另一个核心痛点是,当前 MCP 暴露能力的方式效率极其低下。现在,如果 LLM 需要一个新能力,通常就意味着要为其暴露一个独立的接口

    • 高 Token 消耗: 想象一下,一个像人类一样具备广泛技能的 AI Agent,需要成千上万个细粒度的“能力”(比如“读取文件”、“发送邮件”、“日程安排”、“预定机票”等等)。如果每一个能力都对应一个独立的 MCP 接口声明,那么这些接口的声明本身就会占据大量的上下文 Token 空间。这不仅大大增加了每次调用的成本,更严重的是,它会迅速耗尽 LLM 的上下文窗口(Context Window),使得 LLM 无法处理更长、更复杂的对话或任务,因为其大部分“记忆”都被接口声明占据了。
    • 与理想 Agent 的差距: 这种“一个能力一个接口”的模式,与我们期望的 LLM 最终能完全模仿人类行为的 Agent 形态相去甚远。人类学习一项新技能,并不会为每个微小动作都生成一个独立的“接口说明书”。理想的 Agent 应该能够通过更高级的**抽象(abstraction)泛化(generalization)**能力,以更高效的方式理解和利用工具,而不是被庞大的接口声明所束缚。

展望与挑战:

显然,要实现真正强大的 AI Agent,MCP 协议和其实现方式需要进行根本性的革新。未来的发展方向可能包括:

  • 更高级的工具抽象: 从细粒度的单一接口转向更通用、可组合的“超级工具”或“工作流工具”。
  • 智能的工具发现与选择: LLM 能够更智能地理解何时需要工具,以及如何高效地从庞大的工具库中筛选和调用最合适的工具,而无需所有工具的详细声明都常驻上下文。
  • 动态的接口加载: 根据任务需求,动态地加载和卸载所需的接口声明,而不是一次性全部提供。

总而言之,目前的 MCP 在拓展 LLM 能力方面迈出了重要一步,但要实现 LLM 模仿人类行为的最终 Agent 形态,其在工具调度准确性能力暴露效率上的瓶颈是亟待解决的。

http://www.xdnf.cn/news/14163.html

相关文章:

  • 第十七章 Linux之大数据定制篇——Shell编程
  • ES知识合集(四):高级篇
  • 20250614让NanoPi NEO core开发板在Ubuntu core16.04系统下使用耳机播音测试
  • 「Linux文件及目录管理」目录结构及显示类命令
  • Python虚拟环境的使用
  • SpringBoot源码解析(十一):条件注解@ConditionalOnClass的匹配逻辑
  • 如何调优Kafka
  • LeetCode 第71题 简化路径(繁琐)
  • thinkphp8提升之查询
  • Nature Machine Intelligence 北京通研院朱松纯团队开发视触觉传感仿人灵巧手,实现类人自适应抓取
  • 开心灿烂go开发面试题
  • 如何自动化测试 DependencyMatcher 规则效果(CI/CD 集成最佳实践)
  • 免费OCPP协议测试工具
  • FreeRTOS定时器
  • C++/OpenCV地砖识别系统结合 Libevent 实现网络化 AI 接入
  • 如何写出优秀的单元测试?
  • 17.vue.js响应式和dom更新
  • java33
  • Java重构实战:小步快跑的高效策略分析
  • 【嵌入式硬件实例】-555定时器实现烟雾和易燃气体泄露检测
  • JAVA-springboot 异常处理
  • 15故障排查
  • CAD中DWG到DXF文件解析(一)
  • ELK日志文件分析系统——E(Elasticsearch)
  • 【算法深练】二分答案:从「猜答案」到「精准求解」的解题思路
  • RT-Thread Studio SDK管理器安装资源包失败
  • 考研好?还是找工作好?
  • 灵界猫薄荷×贴贴诱发机制详解
  • 深度学习——基于卷积神经网络的MNIST手写数字识别详解
  • 【AS32系列MCU调试教程】驱动开发:AS32驱动库的集成与应用实例