第四章:Go语言大模型调用框架 - Eino (MCP调用示例)
·
1. mcp简介
MCP(Model Context Protocol,模型上下文协议)就是为了解决这个问题而生的。它定义了一套标准化的协议,让任何语言、任何平台上的工具都能以统一的方式暴露给大模型使用。你可以把 MCP 理解成 AI 世界的 USB 接口——不管是键盘、鼠标还是U盘,只要遵循 USB 标准,插上就能用。同样,不管工具是用什么语言写的、跑在哪台机器上,只要实现了 MCP 协议,Agent 就能直接调用。
2. mcp server 示例
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"log"
"strings"
"time"
"unicode/utf8"
)
// 处理字符串工具
func handleStringTransform(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text := req.GetString("text", "")
operation := req.GetString("operation", "")
var result string
switch operation {
case "to_upper":
result = strings.ToUpper(text)
case "to_lower":
result = strings.ToLower(text)
case "to_count":
count := utf8.RuneCountInString(text)
result = fmt.Sprintf("字符串长度为: %d", count)
case "reverse":
runes := []rune(text)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
result = string(runes)
default:
return mcp.NewToolResultError("不支持的操作类型: " + operation), nil
}
return mcp.NewToolResultText(result), nil
}
// 时间工具的 handler
func handleTimeUtil(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
operation := req.GetString("operation", "")
format := req.GetString("format", "2006-01-02 15:04:05")
switch operation {
case "now":
return mcp.NewToolResultText(time.Now().Format(format)), nil
case "diff":
date1Str := req.GetString("date1", "")
date2Str := req.GetString("date2", "")
if date1Str == "" || date2Str == "" {
return mcp.NewToolResultError("计算日期差需要提供 date1 和 date2 参数"), nil
}
d1, err := time.Parse("2006-01-02", date1Str)
if err != nil {
return mcp.NewToolResultError("date1 格式错误,请使用 2006-01-02 格式"), nil
}
d2, err := time.Parse("2006-01-02", date2Str)
if err != nil {
return mcp.NewToolResultError("date2 格式错误,请使用 2006-01-02 格式"), nil
}
diff := d2.Sub(d1)
days := int(diff.Hours() / 24)
return mcp.NewToolResultText(fmt.Sprintf("%s 到 %s 相差 %d 天", date1Str, date2Str, days)), nil
default:
return mcp.NewToolResultError("不支持的操作类型: " + operation), nil
}
}
func main() {
// 创建MCP server,声明名称和版本
s := server.NewMCPServer("DevToolbox",
"1.0.0",
server.WithToolCapabilities(true),
)
// 注册字符串处理工具
s.AddTool(
mcp.NewTool("string_transform",
mcp.WithDescription("字符串处理工具,支持大小写转换和字数统计"),
mcp.WithString("text",
mcp.Required(),
mcp.Description("要处理的文本内容"),
),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("操作类型"),
mcp.Enum("to_upper", "to_lower", "count_chars", "reverse"),
),
),
handleStringTransform,
)
// 注册时间工具
s.AddTool(
mcp.NewTool("time_util",
mcp.WithDescription("时间工具,支持获取当前时间和计算日期差"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("操作类型"),
mcp.Enum("now", "diff"),
),
mcp.WithString("format",
mcp.Description("时间格式,默认为 2006-01-02 15:04:05"),
),
mcp.WithString("date1",
mcp.Description("第一个日期,格式 2006-01-02,计算日期差时必填"),
),
mcp.WithString("date2",
mcp.Description("第二个日期,格式 2006-01-02,计算日期差时必填"),
),
),
handleTimeUtil,
)
// 以 SSE 方式启动 Server
sseServer := server.NewSSEServer(s,
server.WithBaseURL("http://localhost:8080"),
)
fmt.Println("MCP Server (DevToolbox) 启动中,监听 :8080 ...")
if err := sseServer.Start(":8080"); err != nil {
log.Fatal(err)
}
}
3. mcp client 连接 server 示例
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
"log"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 创建 SSE Client,连接到 MCP Server
cli, err := client.NewSSEMCPClient("http://localhost:8080/sse")
if err != nil {
log.Fatalf("创建 Client 失败: %v", err)
}
// 启动连接
err = cli.Start(ctx)
if err != nil {
log.Fatal(err)
}
defer cli.Close()
// 握手:发送 Initialize 请求
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "demo-client",
Version: "1.0.0",
}
serverInfo, err := cli.Initialize(ctx, initReq)
if err != nil {
log.Fatalf("握手失败: %v", err)
}
fmt.Printf("已连接到 Server: %s (v%s)\n\n", serverInfo.ServerInfo.Name, serverInfo.ServerInfo.Version)
// 发现工具:获取 Server 上所有可用的工具
toolsResult, err := cli.ListTools(ctx, mcp.ListToolsRequest{})
if err != nil {
log.Fatalf("获取工具列表失败: %v", err)
}
fmt.Printf("发现 %d 个工具:\n", len(toolsResult.Tools))
for _, t := range toolsResult.Tools {
fmt.Printf(" - %s: %s\n", t.Name, t.Description)
}
fmt.Println()
// 调用工具:字符串转大写
callReq := mcp.CallToolRequest{}
callReq.Params.Name = "string_transform"
callReq.Params.Arguments = map[string]interface{}{
"text": "hello, mcp world!",
"operation": "to_upper",
}
result, err := cli.CallTool(ctx, callReq)
if err != nil {
log.Fatalf("调用工具失败: %v", err)
}
for _, content := range result.Content {
if tc, ok := content.(mcp.TextContent); ok {
fmt.Printf("字符串转大写结果: %s\n", tc.Text)
}
}
// 调用工具:获取当前时间
callReq2 := mcp.CallToolRequest{}
callReq2.Params.Name = "time_util"
callReq2.Params.Arguments = map[string]interface{}{
"date1": "2026-05-26",
"date2": "2026-05-30",
"operation": "diff",
}
result2, err := cli.CallTool(ctx, callReq2)
if err != nil {
log.Fatalf("调用工具失败: %v", err)
}
for _, content := range result2.Content {
if tc, ok := content.(mcp.TextContent); ok {
fmt.Printf("当前时间: %s\n", tc.Text)
}
}
}
4. 使用eino的桥接工具,获取server端的所有工具
package main
import (
"context"
"fmt"
mcpp "github.com/cloudwego/eino-ext/components/tool/mcp"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
"log"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 连接到mcp server
cli, err := client.NewSSEMCPClient("http://localhost:8080/sse")
if err != nil {
log.Fatalf("创建 Client 失败: %v", err)
}
// 启动 SSE 传输层 — Initialize 之前必须先启动底层连接
if err := cli.Start(ctx); err != nil {
log.Fatalf("启动传输失败: %v", err)
}
defer cli.Close()
// 初始化握手
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "eino-bridge-demo",
Version: "1.0.0",
}
_, err = cli.Initialize(ctx, initReq)
if err != nil {
log.Fatalf("握手失败: %v", err)
}
// 关键一步:用 Eino 桥接组件把 MCP 工具转换为 Eino Tool
tools, err := mcpp.GetTools(ctx, &mcpp.Config{Cli: cli})
if err != nil {
log.Fatal(err)
}
fmt.Printf("成功桥接 %d 个 MCP 工具为 Eino Tool:\n\n", len(tools))
for i, t := range tools {
info, _ := t.Info(ctx)
fmt.Printf("[%d] 名称: %s\n 描述: %s\n 参数Schema: %v\n\n",
i+1, info.Name, info.Desc, info.ParamsOneOf)
}
}
5. 在agent中调用 mcp 工具示例
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/components/model/openai"
mcpp "github.com/cloudwego/eino-ext/components/tool/mcp"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/flow/agent/react"
"github.com/cloudwego/eino/schema"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
"log"
)
// 连接MCP Server,获取工具
func connectMcpServer(ctx context.Context) ([]tool.BaseTool, func()) {
cli, err := client.NewSSEMCPClient("http://localhost:8080/sse")
if err != nil {
log.Fatalf("创建 Client 失败: %v", err)
}
if err := cli.Start(ctx); err != nil {
log.Fatalf("启动传输失败: %v", err)
}
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "eino-agent",
Version: "1.0.0",
}
_, err = cli.Initialize(ctx, initReq)
if err != nil {
log.Fatalf("握手失败: %v", err)
}
tools, err := mcpp.GetTools(ctx, &mcpp.Config{Cli: cli})
if err != nil {
log.Fatal("桥接工具失败")
}
return tools, func() {
cli.Close()
}
}
func main() {
ctx := context.Background()
// ========== 第一步:连接 MCP Server,获取工具 ==========
mcpTools, cleanup := connectMcpServer(ctx)
defer cleanup()
// ========== 第二步:创建大模型实例 ==========
model, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: "https://api-inference.modelscope.cn/v1/",
APIKey: "xxx", // api_key
Model: "Qwen/Qwen3.5-35B-A3B",
})
if err != nil {
log.Fatalf("创建 ChatModel 失败: %v", err)
}
// ========== 第三步:创建 Agent 实例 ==========
agent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: model,
ToolsConfig: compose.ToolsNodeConfig{
Tools: mcpTools,
},
})
if err != nil {
log.Fatalf("创建 Agent 失败: %v", err)
}
// ========== 第四步:与 Agent 对话 ==========
questions := []string{
"请帮我把 'hello world from mcp' 这段文字转成大写",
"现在时间是几点",
}
for i, q := range questions {
fmt.Printf("===== 问题 %d =====\n", i+1)
fmt.Printf("用户: %s\n\n", q)
result, err := agent.Generate(ctx, []*schema.Message{
{Role: schema.User, Content: q},
})
if err != nil {
fmt.Printf("Agent 执行出错: %v\n\n", err)
continue
}
fmt.Printf("Agent: %s\n\n", result.Content)
}
}
注意: 在client, 桥接,agent 测试时都需要启动 mcp server 才能正常调用。
更多推荐




所有评论(0)