Eino中的組件-MCP工具組件

什么是MCP?

MCP允許您構(gòu)建服務(wù)器,以安全、標(biāo)準(zhǔn)化的方式向 LLM 應(yīng)用程序公開數(shù)據(jù)和功能。您可以將其視為一個(gè) Web API,但專為 LLM 交互而設(shè)計(jì)。MCP 服務(wù)器可以:

  • 通過Resources公開數(shù)據(jù)
  • 通過Tools提供功能調(diào)用
  • 通過Prompts 定義交互模式

MCP的golang實(shí)現(xiàn)可參閱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. 構(gòu)造請求
    req := mcp.CallToolRequest{
        Request: mcp.Request{
            Method: "tools/call",
        },
    }
    req.Params.Name = "calculate" // 服務(wù)器端注冊的工具名
    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. 取結(jié)果
    fmt.Println(resp.Content[0].(mcp.TextContent).Text)
}

Eino 中MCP工具組件:

Eino MCP 工具組件 就是一層薄薄的 “協(xié)議適配器”,把任何 MCP Server 的 tool → 標(biāo)準(zhǔn) Eino InvokableTool, 從而讓開發(fā)者 “零膠水代碼” 就能在 Graph / Agent / Chain 里使用 MCP 生態(tài)的全部能力, Eino 中工具組件實(shí)現(xiàn)步驟如下:

  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 工具調(diào)用完成時(shí)用于結(jié)果出來的函數(shù)

2: 實(shí)現(xiàn)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調(diào)用請求, 然后通過cli的CallTool調(diào)用mcp server 提供的工具方法

  1. 通過mcp-go 的cli 獲取server 端的tools, 然后轉(zhuǎn)換為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. 調(diào)用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)
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容