第四章:Go语言大模型调用框架 - Eino (MCP调用示例)
1. mcp简介
MCP(Model Context Protocol,模型上下文协议)就是为了解决这个问题而生的。它定义了一套标准化的协议,让任何语言、任何平台上的工具都能以统一的方式暴露给大模型使用。你可以把 MCP 理解成 AI 世界的 USB 接口——不管是键盘、鼠标还是U盘,只要遵循 USB 标准,插上就能用。同样,不管工具是用什么语言写的、跑在哪台机器上,只要实现了 MCP 协议,Agent 就能直接调用。
2. mcp server 示例
packagemainimport("context""fmt""github.com/mark3labs/mcp-go/mcp""github.com/mark3labs/mcp-go/server""log""strings""time""unicode/utf8")// 处理字符串工具funchandleStringTransform(ctx context.Context,req mcp.CallToolRequest)(*mcp.CallToolResult,error){text:=req.GetString("text","")operation:=req.GetString("operation","")varresultstringswitchoperation{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)fori,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:returnmcp.NewToolResultError("不支持的操作类型: "+operation),nil}returnmcp.NewToolResultText(result),nil}// 时间工具的 handlerfunchandleTimeUtil(ctx context.Context,req mcp.CallToolRequest)(*mcp.CallToolResult,error){operation:=req.GetString("operation","")format:=req.GetString("format","2006-01-02 15:04:05")switchoperation{case"now":returnmcp.NewToolResultText(time.Now().Format(format)),nilcase"diff":date1Str:=req.GetString("date1","")date2Str:=req.GetString("date2","")ifdate1Str==""||date2Str==""{returnmcp.NewToolResultError("计算日期差需要提供 date1 和 date2 参数"),nil}d1,err:=time.Parse("2006-01-02",date1Str)iferr!=nil{returnmcp.NewToolResultError("date1 格式错误,请使用 2006-01-02 格式"),nil}d2,err:=time.Parse("2006-01-02",date2Str)iferr!=nil{returnmcp.NewToolResultError("date2 格式错误,请使用 2006-01-02 格式"),nil}diff:=d2.Sub(d1)days:=int(diff.Hours()/24)returnmcp.NewToolResultText(fmt.Sprintf("%s 到 %s 相差 %d 天",date1Str,date2Str,days)),nildefault:returnmcp.NewToolResultError("不支持的操作类型: "+operation),nil}}funcmain(){// 创建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 方式启动 ServersseServer:=server.NewSSEServer(s,server.WithBaseURL("http://localhost:8080"),)fmt.Println("MCP Server (DevToolbox) 启动中,监听 :8080 ...")iferr:=sseServer.Start(":8080");err!=nil{log.Fatal(err)}}3. mcp client 连接 server 示例
packagemainimport("context""fmt""github.com/mark3labs/mcp-go/client""github.com/mark3labs/mcp-go/mcp""log""time")funcmain(){ctx,cancel:=context.WithTimeout(context.Background(),30*time.Second)defercancel()// 创建 SSE Client,连接到 MCP Servercli,err:=client.NewSSEMCPClient("http://localhost:8080/sse")iferr!=nil{log.Fatalf("创建 Client 失败: %v",err)}// 启动连接err=cli.Start(ctx)iferr!=nil{log.Fatal(err)}defercli.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)iferr!=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{})iferr!=nil{log.Fatalf("获取工具列表失败: %v",err)}fmt.Printf("发现 %d 个工具:\n",len(toolsResult.Tools))for_,t:=rangetoolsResult.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)iferr!=nil{log.Fatalf("调用工具失败: %v",err)}for_,content:=rangeresult.Content{iftc,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)iferr!=nil{log.Fatalf("调用工具失败: %v",err)}for_,content:=rangeresult2.Content{iftc,ok:=content.(mcp.TextContent);ok{fmt.Printf("当前时间: %s\n",tc.Text)}}}4. 使用eino的桥接工具,获取server端的所有工具
packagemainimport("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")funcmain(){ctx,cancel:=context.WithTimeout(context.Background(),30*time.Second)defercancel()// 连接到mcp servercli,err:=client.NewSSEMCPClient("http://localhost:8080/sse")iferr!=nil{log.Fatalf("创建 Client 失败: %v",err)}// 启动 SSE 传输层 — Initialize 之前必须先启动底层连接iferr:=cli.Start(ctx);err!=nil{log.Fatalf("启动传输失败: %v",err)}defercli.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)iferr!=nil{log.Fatalf("握手失败: %v",err)}// 关键一步:用 Eino 桥接组件把 MCP 工具转换为 Eino Tooltools,err:=mcpp.GetTools(ctx,&mcpp.Config{Cli:cli})iferr!=nil{log.Fatal(err)}fmt.Printf("成功桥接 %d 个 MCP 工具为 Eino Tool:\n\n",len(tools))fori,t:=rangetools{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 工具示例
packagemainimport("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,获取工具funcconnectMcpServer(ctx context.Context)([]tool.BaseTool,func()){cli,err:=client.NewSSEMCPClient("http://localhost:8080/sse")iferr!=nil{log.Fatalf("创建 Client 失败: %v",err)}iferr:=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)iferr!=nil{log.Fatalf("握手失败: %v",err)}tools,err:=mcpp.GetTools(ctx,&mcpp.Config{Cli:cli})iferr!=nil{log.Fatal("桥接工具失败")}returntools,func(){cli.Close()}}funcmain(){ctx:=context.Background()// ========== 第一步:连接 MCP Server,获取工具 ==========mcpTools,cleanup:=connectMcpServer(ctx)defercleanup()// ========== 第二步:创建大模型实例 ==========model,err:=openai.NewChatModel(ctx,&openai.ChatModelConfig{BaseURL:"https://api-inference.modelscope.cn/v1/",APIKey:"xxx",// api_keyModel:"Qwen/Qwen3.5-35B-A3B",})iferr!=nil{log.Fatalf("创建 ChatModel 失败: %v",err)}// ========== 第三步:创建 Agent 实例 ==========agent,err:=react.NewAgent(ctx,&react.AgentConfig{ToolCallingModel:model,ToolsConfig:compose.ToolsNodeConfig{Tools:mcpTools,},})iferr!=nil{log.Fatalf("创建 Agent 失败: %v",err)}// ========== 第四步:与 Agent 对话 ==========questions:=[]string{"请帮我把 'hello world from mcp' 这段文字转成大写","现在时间是几点",}fori,q:=rangequestions{fmt.Printf("===== 问题 %d =====\n",i+1)fmt.Printf("用户: %s\n\n",q)result,err:=agent.Generate(ctx,[]*schema.Message{{Role:schema.User,Content:q},})iferr!=nil{fmt.Printf("Agent 执行出错: %v\n\n",err)continue}fmt.Printf("Agent: %s\n\n",result.Content)}}注意: 在client, 桥接,agent 测试时都需要启动 mcp server 才能正常调用。
