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_demo

import (
	"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 calls
	ToolCalls []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 request
	Tools []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 LLM
	calculateTool := 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 definition
	requestBody := 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 body
	jsonBody, 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 request
	req, 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 headers
	req.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 request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(fmt.Sprintf("Error sending request: %v", err))
	}
	defer resp.Body.Close()

	// Read response
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(fmt.Sprintf("Error reading response: %v", err))
	}

	// Handle response
	if 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 main

import (
	"context"
	"fmt"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func main() {
	// Create a new MCP server
	s := server.NewMCPServer(
		"Calculator Demo",
		"1.0.0",
		server.WithToolCapabilities(false),
		server.WithRecovery(),
	)

	// Add a calculator tool
	calculatorTool := 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 handler
	s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// Using helper functions for type-safe argument access
		op, 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 float64
		switch op {
		case "add":
			result = x + y
		case "subtract":
			result = x - y
		case "multiply":
			result = x * y
		case "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 server
	if 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 形态,其在工具调度准确性能力暴露效率上的瓶颈是亟待解决的。

### MCP 技术的应用场景 MCP(Model Context Protocol)作为一种新兴的技术协议,在多个领域展现了其强大的潜力。以下是几个主要的应用场景: #### 1. 复杂自动化场景中的技术融合 结合 Planning Pattern 多智能体协作,MCP 可能推动更复杂的自动化场景实现,例如全链路项目管理跨领域协作[^1]。这种技术能够显著提高生产效率并减少人为干预的需求。 #### 2. 动态能力扩展支持主流框架 MCP 能够与主流框架互补工作,比如 LangChain 工具库可以通过 MCP 扩展动态能力[^2]。这使得开发者可以灵活调整增强现有系统的功能而无需大规模重构代码基础架构。 #### 3. 数据查询与任务协同处理 从简单的数据查询到复杂的工作流协调,MCP 提供了一种标准化的方式来进行这些操作。它不仅限于单一平台内部的任务执行,还可以跨越不同系统边界完成交互式作业调度服务调用等功能[^3]。 #### 4. 用户友好型桌面应用程序集成 对于非技术人员来说,利用像 Fleur 这样的 macOS 应用程序可以直接简化他们在 Claude 中部署 MCP Servers 的过程[^4]。这意味着即使是没有深厚技术背景的人也可以轻松上手使用这项先进技术来满足自己的业务需求。 ### 实战案例分析 假设在一个企业环境中需要构建一套完整的供应链管理系统,则可以采用如下方法论实施基于 MCP 的解决方案设计思路: ```python class SupplyChainManager: def __init__(self, mcp_server_url): self.mcp_client = MCPClient(mcp_server_url) def manage_inventory(self, inventory_data): response = self.mcp_client.send_request('inventory_management', inventory_data) return response['status'] def track_shipments(self, shipment_ids): results = [] for id in shipment_ids: result = self.mcp_client.query_status(id=id, service='shipment_tracking') results.append(result) return results # 初始化供应链示例对象并与远程 MCP Server 建立连接 manager = SupplyChainManager("http://example-mcp-server.com/api/v1") # 对库存进行管理操作 print(manager.manage_inventory({"item": "Widget", "quantity": 50})) # 查询一批货物运输状态 shipments_to_track = ["SH123456789", "SH987654321"] tracking_results = manager.track_shipments(shipments_to_track) for res in tracking_results: print(res["location"], "-", res["eta"]) ``` 此 Python 类展示了如何创建一个用于管理公司供应链的高层抽象接口,并通过 MCP 客户端发送请求给远端服务器以获取实时更新的信息或者提交新的指令集。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只会写bug的靓仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值