Skip to content

Day 6:MCP改造——让工具变成标准接口

昨天的问题

Day 4 的工具是硬编码在 Agent 代码里的:GetOrderToolGetDeliveryTimeTool 直接 import。如果另一个服务也想用这些工具,得复制代码。

今天做什么

MCP(Model Context Protocol) 把工具标准化。工具变成独立的 MCP Server,Agent 通过标准协议调用。

代码

MCP Server(工具提供方)

mcp_server/main.go

go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
)

// ===== MCP Server 暴露的工具 =====
type MCPServer struct {
    tools map[string]ToolHandler
}

type ToolHandler func(ctx context.Context, params map[string]any) (string, error)

func NewMCPServer() *MCPServer {
    s := &MCPServer{tools: make(map[string]ToolHandler)}
    s.tools["get_order_status"] = handleOrderStatus
    s.tools["get_delivery_time"] = handleDeliveryTime
    return s
}

func handleOrderStatus(ctx context.Context, params map[string]any) (string, error) {
    orderID, _ := params["order_id"].(string)
    order, ok := orderDB[orderID]
    if !ok {
        return fmt.Sprintf("未找到订单 %s", orderID), nil
    }
    return fmt.Sprintf("订单 %s%s,预计 %s", order.ID, order.Status, order.ETA), nil
}

func handleDeliveryTime(ctx context.Context, params map[string]any) (string, error) {
    city, _ := params["city"].(string)
    return fmt.Sprintf("%s地区预计2-3天送达", city), nil
}

// MCP 工具发现接口
func (s *MCPServer) handleListTools(w http.ResponseWriter, r *http.Request) {
    tools := []map[string]any{
        {"name": "get_order_status", "description": "查询订单状态",
            "parameters": map[string]any{"order_id": "string"}},
        {"name": "get_delivery_time", "description": "查询配送时间",
            "parameters": map[string]any{"city": "string"}},
    }
    json.NewEncoder(w).Encode(tools)
}

// MCP 工具调用接口
func (s *MCPServer) handleCallTool(w http.ResponseWriter, r *http.Request) {
    var req struct {
        ToolName string         `json:"tool_name"`
        Params   map[string]any `json:"params"`
    }
    json.NewDecoder(r.Body).Decode(&req)

    handler, ok := s.tools[req.ToolName]
    if !ok {
        http.Error(w, "tool not found", 404)
        return
    }

    result, err := handler(r.Context(), req.Params)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    json.NewEncoder(w).Encode(map[string]string{"result": result})
}

func main() {
    server := NewMCPServer()
    http.HandleFunc("/tools", server.handleListTools)
    http.HandleFunc("/tools/call", server.handleCallTool)
    log.Fatal(http.ListenAndServe(":9090", nil))
}

Agent 端(MCP Client)

go
// MCP Client:调用远程工具
type MCPClient struct {
    baseURL    string
    httpClient *http.Client
}

func (c *MCPClient) ListTools(ctx context.Context) ([]ToolInfo, error) {
    resp, err := c.httpClient.Get(c.baseURL + "/tools")
    // 解析返回的工具列表
    var tools []ToolInfo
    json.NewDecoder(resp.Body).Decode(&tools)
    return tools, nil
}

func (c *MCPClient) CallTool(ctx context.Context,
    name string, params map[string]any) (string, error) {
    // 调远程 MCP Server 的工具
    body, _ := json.Marshal(map[string]any{
        "tool_name": name,
        "params":    params,
    })
    resp, _ := c.httpClient.Post(c.baseURL+"/tools/call",
        "application/json", bytes.NewReader(body))
    var result struct{ Result string }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.Result, nil
}

运行

bash
# 终端1:启动 MCP Server
go run mcp_server/main.go

# 终端2:运行 Agent
go run main.go

你学到了什么

  1. MCP 架构:工具从 Agent 代码中解耦,变成独立服务。
  2. 标准接口/tools 发现工具,/tools/call 调用工具。任何语言都能调。
  3. Go 编译成二进制:MCP Server 可以 go build 成一个文件,分发给团队。

明天的预告

最后一天——把 Agent 部署上线。编译成二进制文件,通过 HTTP 接口对外服务。


Day 6 完成。你的工具现在是标准接口了。