第13章 给工具世界定个规矩
第6章我们讲了Function Calling,第12章讲了安全。但还有一个痛点没解决:你写的Agent接入的是OpenAI,工具定义是OpenAI的格式;另一个Agent用的是豆包,工具定义又是另一种格式。换个模型就得重写工具?MCP协议就是来解决这个问题的——让所有工具说同一种语言。而Go在这个故事里,有一张别人没有的牌。
13.1 为什么需要MCP?
13.1.1 "万国插座"问题
假设你写了三个Agent,用了三个不同的LLM:
| Agent | LLM | 工具定义方式 |
|---|---|---|
| Agent A | OpenAI | OpenAI Function Calling 格式 |
| Agent B | 豆包 | 豆包函数调用格式 |
| Agent C | DeepSeek | DeepSeek 工具模式 |
同一个工具 get_weather,你得写三遍定义。三套代码,三种格式,维护噩梦。
这就是MCP要解决的问题——一套工具定义,所有LLM都能用。
13.1.2 MCP是什么
MCP(Model Context Protocol) 是一个开放协议,由Anthropic在2024年底提出。它定义了LLM和外部工具之间的标准通信方式。
简单类比:
- USB-C出现之前:每个手机有自己充电口,出门得带三根线
- MCP出现之前:每个LLM有自己的工具调用格式,写工具得适配三套
- 有了MCP之后:一套接口,所有LLM都能用
13.2 MCP Server / Client 协议规范
13.2.1 MCP的架构
MCP采用经典的C/S架构:
- MCP Server:暴露工具。定义"我有哪些工具、每个工具要什么参数"
- MCP Client:调用工具。Agent作为Client,通过标准协议调用Server暴露的工具
13.2.2 Eino的MCP支持
Eino内置了MCP的Client和Server能力:
go
import (
"github.com/cloudwego/eino/components/tool/mcp"
)
// 方式一:作为 MCP Client——调用别人暴露的工具
func createMCPTools(ctx context.Context) ([]tool.InvokableTool, error) {
// 连接一个外部 MCP Server
mcpClient, err := mcp.NewClient(ctx, &mcp.ClientConfig{
Transport: "stdio", // 或 "sse"
Command: "python", // 如果MCP Server是Python写的
Args: []string{"-m", "weather_mcp_server"},
})
if err != nil {
return nil, err
}
// 获取 Server 暴露的所有工具
tools, err := mcpClient.ListTools(ctx)
if err != nil {
return nil, err
}
// 直接把这些工具注册给 Eino Agent
return tools, nil
}
// 方式二:作为 MCP Server——把自己的工具暴露出去
func startMCPServer(ctx context.Context) error {
mcpServer := mcp.NewServer("my-agent-tools", "1.0.0")
// 注册工具
mcpServer.AddTool(ctx, &mcp.ToolDefinition{
Name: "get_weather",
Description: "查询指定城市的天气",
InputSchema: mcp.Schema{
Type: "object",
Properties: map[string]mcp.Property{
"city": {Type: "string", Description: "城市名"},
},
Required: []string{"city"},
},
Handler: handleWeatherQuery,
})
return mcpServer.Serve(ctx)
}Eino帮你屏蔽了MCP协议的细节——你只需要定义工具和处理逻辑,框架负责协议的序列化、传输、错误处理。
13.3 Go实现MCP的天然优势:编译一次,到处运行
13.3.1 为什么Go特别适合MCP
MCP Server本质是一个独立运行的服务。部署MCP Server的痛点:
| 语言 | 部署痛点 |
|---|---|
| Python | 需要Python环境 + venv + 依赖安装 |
| Node.js | 需要Node环境 + npm install |
| Go | 一个二进制文件,扔上去就跑 |
bash
# Go MCP Server 的部署
go build -o weather-server ./cmd/weather
scp weather-server user@server:/opt/mcp/
ssh user@server "/opt/mcp/weather-server"
# 整个部署过程不到10秒,不需要装任何依赖Agent需要调用你的工具?给一个二进制文件就行。不需要Python环境、不需要Node版本、不需要系统库——这是Go做MCP最大的优势。
13.3.2 跨平台编译
bash
# 在 macOS 上编译 Linux 版
GOOS=linux GOARCH=amd64 go build -o weather-server-linux
# 编译 Windows 版
GOOS=windows GOARCH=amd64 go build -o weather-server.exe
# 编译 ARM 版(树莓派、Apple Silicon服务器)
GOOS=linux GOARCH=arm64 go build -o weather-server-arm同一个代码库,三行命令出三个平台的二进制。Python做MCP Server,你得确保目标机器Python版本和依赖都对了。
13.4 gRPC工具链:让你的工具被任何语言调用
13.4.1 MCP + gRPC:更强的组合
MCP定义了"工具的标准接口",gRPC定义了"高效的通信方式"。两者组合在一起:
Go 写的 MCP Server(编译成二进制)
↓ 通过 gRPC 暴露
Java 微服务可以调用
Python 脚本可以调用
前端 Node.js 可以调用go
// 用 gRPC 暴露 MCP 工具
import (
"google.golang.org/grpc"
mcp_pb "your-project/proto/mcp"
)
type MCPServer struct {
mcp_pb.UnimplementedMCPServer
tools map[string]ToolHandler
}
func (s *MCPServer) CallTool(ctx context.Context,
req *mcp_pb.ToolCallRequest) (*mcp_pb.ToolCallResponse, error) {
handler, ok := s.tools[req.ToolName]
if !ok {
return nil, status.Errorf(codes.NotFound, "工具 %s 不存在", req.ToolName)
}
result, err := handler.Execute(ctx, req.Params)
if err != nil {
return nil, status.Errorf(codes.Internal, "工具执行失败: %v", err)
}
return &mcp_pb.ToolCallResponse{Result: result}, nil
}
// 启动 gRPC 服务
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
mcp_pb.RegisterMCPServer(s, &MCPServer{tools: loadTools()})
s.Serve(lis)
}Go 写 Proto → protoc 生成各语言客户端 → 任何语言都能调你的工具。这是 Go 在工具链生态里的杀手锏。
13.4.2 流式工具调用
gRPC 支持四种调用模式。Agent场景里最有用的是服务端流式——工具边执行边返回结果:
go
func (s *MCPServer) StreamTool(req *mcp_pb.ToolCallRequest,
stream mcp_pb.MCP_StreamToolServer) error {
// 工具执行过程中,分批返回结果
results := executeLongRunningTool(ctx, req.ToolName, req.Params)
for _, chunk := range results {
if err := stream.Send(&mcp_pb.ToolCallChunk{
Content: chunk,
Progress: float32(chunk.Index) / float32(len(results)),
}); err != nil {
return err
}
}
return nil
}13.5 MCP不是替代,是互补
13.5.1 MCP和Function Calling的关系
很多人以为MCP要替代Function Calling——不对。
| Function Calling | MCP | |
|---|---|---|
| 解决的问题 | LLM怎么"知道"该调哪个工具 | 工具怎么被"定义"和"发现" |
| 层级 | LLM内部能力 | LLM外部协议 |
| 关系 | — | MCP包装的工具最终还是通过Function Calling让LLM调用 |
可以理解为:Function Calling是"怎么用",MCP是"怎么接"。它们不是竞争关系,是上下游。
13.5.2 MCP的生态展望
MCP还年轻,但方向很明确——标准化工具接口。目前:
- Anthropic(提出者)、OpenAI 都在跟进
- Eino 已内置 MCP Client/Server 支持
- Go 编译二进制 + gRPC 流式,让 Go 在 MCP 生态里有独特优势
13.6 本章小结
这一章讲清楚了工具世界的"统一接口":
- MCP是什么:工具定义的"USB-C"。一套接口,所有LLM通用。
- Eino的MCP支持:内置Client(调别人工具)和Server(暴露自己的工具)。
- Go的独特优势:编译一个二进制,不需要Python环境、不需要npm install。跨平台编译,三行命令出三个平台。
- gRPC强化:Proto定义 + 代码生成,任何语言都能调你的Go工具。流式返回让工具执行过程可见。
- MCP + Function Calling是协作关系:MCP管"怎么接",Function Calling管"怎么用"。
✅ 知识点检查
学完这一章,试试回答这几个问题:
- [ ] MCP解决了什么痛点?把它比作USB-C是什么意思?
- [ ] Go做MCP Server比Python有什么优势?
- [ ] gRPC在MCP生态里扮演什么角色?
- [ ] MCP和Function Calling是什么关系?
📚 延伸阅读
- MCP协议官方文档:https://modelcontextprotocol.io
- Eino MCP 文档:https://www.cloudwego.io/zh/docs/eino/
- gRPC Go 教程:https://grpc.io/docs/languages/go/
- 本书配套源码:关注公众号「图解AI系列」免费领取
🎯 下一章预告
第14章,Agent的边界——
"知道边界在哪,比知道怎么做更难。Agent能做什么、不能做什么?做错了谁负责?"

