Eino中的组件-MCP工具组件

什么是MCP?

MCP允许您构建服务器,以安全、标准化的方式向 LLM 应用程序公开数据和功能。您可以将其视为一个 Web API,但专为 LLM 交互而设计。MCP 服务器可以:

  • 通过Resources公开数据
  • 通过Tools提供功能调用
  • 通过Prompts 定义交互模式

MCP的golang实现可参阅mcp-go,A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.

MCP Server(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)
    }
}

MCP Client(mcp-go):

package main

import (
    "context"
    "fmt"
    "github.com/mark3labs/mcp-go/client"
    "github.com/mark3labs/mcp-go/mcp"
    "log"
)

func main() {
    ctx := context.Background()
    cli, err := client.NewSSEMCPClient("http://localhost:12345/sse")
    if err != nil {
        log.Fatal(err)
    }
    err = cli.Start(ctx)
    if err != nil {
        log.Fatal(err)
    }
    initRequest := mcp.InitializeRequest{}
    initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
    initRequest.Params.ClientInfo = mcp.Implementation{
        Name:    "example-client",
        Version: "1.0.0",
    }
    _, err = cli.Initialize(ctx, initRequest)
    if err != nil {
        log.Fatal(err)
    }
    listResults, err := cli.ListTools(ctx, mcp.ListToolsRequest{})
    if err != nil {
        log.Fatal(err)
    }
    for _, t := range listResults.Tools {
        fmt.Println(t.Name + ":" + t.Description)

    }
    // 1. 构造请求
    req := mcp.CallToolRequest{
        Request: mcp.Request{
            Method: "tools/call",
        },
    }
    req.Params.Name = "calculate" // 服务器端注册的工具名
    req.Params.Arguments = map[string]any{
        "operation": "add",
        "x":         3.0,
        "y":         4.0,
    }
    resp, err := cli.CallTool(ctx, req)
    if err != nil {
        log.Fatal(err)
    }
    // 3. 取结果
    fmt.Println(resp.Content[0].(mcp.TextContent).Text)
}

Eino 中MCP工具组件:

Eino MCP 工具组件 就是一层薄薄的 “协议适配器”,把任何 MCP Server 的 tool → 标准 Eino InvokableTool, 从而让开发者 “零胶水代码” 就能在 Graph / Agent / Chain 里使用 MCP 生态的全部能力, Eino 中工具组件实现步骤如下:

  1. Helper工具类定义
type toolHelper struct {
    cli                   client.MCPClient
    info                  *schema.ToolInfo
    toolCallResultHandler func(ctx context.Context, name string, result *mcp.CallToolResult) (*mcp.CallToolResult, error)
}

func (m *toolHelper) Info(ctx context.Context) (*schema.ToolInfo, error) {
    return m.info, nil
}
  • cli: mcp-go 客户端
  • info ToolInfo信息
  • toolCallResultHandler 工具调用完成时用于结果出来的函数

2: 实现InvokableRun方法:


func (m *toolHelper) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
    result, err := m.cli.CallTool(ctx, mcp.CallToolRequest{
        Request: mcp.Request{
            Method: "tools/call",
        },
        Params: struct {
            Name      string    `json:"name"`
            Arguments any       `json:"arguments,omitempty"`
            Meta      *mcp.Meta `json:"_meta,omitempty"`
        }{
            Name:      m.info.Name,
            Arguments: json.RawMessage(argumentsInJSON),
        },
    })
    if err != nil {
        return "", fmt.Errorf("failed to call mcp tool: %w", err)
    }

    if m.toolCallResultHandler != nil {
        result, err = m.toolCallResultHandler(ctx, m.info.Name, result)
        if err != nil {
            return "", fmt.Errorf("failed to execute mcp tool call result handler: %w", err)
        }
    }

    marshaledResult, err := sonic.MarshalString(result)
    if err != nil {
        return "", fmt.Errorf("failed to marshal mcp tool result: %w", err)
    }
    if result.IsError {
        return "", fmt.Errorf("failed to call mcp tool, mcp server return error: %s", marshaledResult)
    }
    return marshaledResult, nil
}

封装CallToolRequest调用请求, 然后通过cli的CallTool调用mcp server 提供的工具方法

  1. 通过mcp-go 的cli 获取server 端的tools, 然后转换为toolHelper
func GetTools(ctx context.Context, conf *Config) ([]tool.BaseTool, error) {
    listResults, err := conf.Cli.ListTools(ctx, mcp.ListToolsRequest{})
    if err != nil {
        return nil, fmt.Errorf("list mcp tools fail: %w", err)
    }

    nameSet := make(map[string]struct{})
    for _, name := range conf.ToolNameList {
        nameSet[name] = struct{}{}
    }

    ret := make([]tool.BaseTool, 0, len(listResults.Tools))
    for _, t := range listResults.Tools {
        if len(conf.ToolNameList) > 0 {
            if _, ok := nameSet[t.Name]; !ok {
                continue
            }
        }

        marshaledInputSchema, err := sonic.Marshal(t.InputSchema)
        if err != nil {
            return nil, fmt.Errorf("conv mcp tool input schema fail(marshal): %w, tool name: %s", err, t.Name)
        }
        inputSchema := &openapi3.Schema{}
        err = sonic.Unmarshal(marshaledInputSchema, inputSchema)
        if err != nil {
            return nil, fmt.Errorf("conv mcp tool input schema fail(unmarshal): %w, tool name: %s", err, t.Name)
        }

        ret = append(ret, &toolHelper{
            cli: conf.Cli,
            info: &schema.ToolInfo{
                Name:        t.Name,
                Desc:        t.Description,
                ParamsOneOf: schema.NewParamsOneOfByOpenAPIV3(inputSchema),
            },
            toolCallResultHandler: conf.ToolCallResultHandler,
        })
    }

    return ret, nil
}
  1. 调用toolHelper 的InvokableRun方法:
mcpTools := getMCPTool(ctx)

    for i, mcpTool := range mcpTools {
        info, err := mcpTool.Info(ctx)
        if err != nil {
            log.Fatal(err)
        }
        result, err := mcpTool.(tool.InvokableTool).InvokableRun(ctx, `{"operation":"add", "x":1, "y":1}`)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("Result:", result)
    }
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容