什么是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)步驟如下:
- 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 提供的工具方法
- 通過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
}
- 調(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)
}