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 才能正常调用。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐