在前两篇文章中,我们揭开了 MCP(Model Context Protocol)的面纱:它是什么、为什么需要它,以及协议的核心规范。但光有理论不够——真正的工程价值,在于实现
        本文将带你从零开始,用 Go 语言 构建一个生产就绪的 MCP Server。我们将遵循清晰的工程结构、支持动态工具注册、实现完整的上下文管理,并集成测试与 CI/CD。无论你是 Go 高手还是刚接触 MCP,都能通过这篇实战掌握如何将你的后端服务变成 AI Agent 可调用的“原子能力”


一、项目结构设计:清晰、可维护、符合工程规范

我们采用典型的 Go 微服务结构,兼顾可测试性与可扩展性:

mcp-server/
├── cmd/
│   └── server/
│       └── main.go                 # 启动入口
├── internal/
│   ├── handler/                    # HTTP/gRPC 处理逻辑
│   ├── tool/                       # 工具注册与执行核心
│   ├── context/                    # 上下文管理
│   └── security/                   # 认证与鉴权
├── pkg/
│   └── mcp/                        # MCP 协议定义(消息类型、Schema)
├── test/
│   ├── integration/                # 集成测试
│   └── fixtures/                   # 测试用例数据
├── Dockerfile
├── .gitlab-ci.yml
└── go.mod

关键设计原则

  • internal/:仅本项目使用,防止外部误依赖;
  • pkg/mcp/:协议定义独立,未来可被其他项目复用;
  • 无全局状态:所有依赖通过构造函数注入,便于测试。

二、定义 Tool Registry:动态注册与卸载

MCP 的核心是“工具即服务”,因此我们首先实现一个线程安全的注册中心:

// internal/tool/registry.go
type Tool struct {
    Name        string          `json:"name"`
    Description string          `json:"description"`
    Parameters  json.RawMessage `json:"parameters"` // JSON Schema
    Exec        func(args map[string]interface{}, ctx context.Context) (interface{}, error)
}

type Registry struct {
    mu    sync.RWMutex
    tools map[string]*Tool
}

func (r *Registry) Register(tool *Tool) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    if _, exists := r.tools[tool.Name]; exists {
        return errors.New("tool already exists")
    }
    r.tools[tool.Name] = tool
    return nil
}

func (r *Registry) Get(name string) (*Tool, bool) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    tool, ok := r.tools[name]
    return tool, ok
}

支持运行时动态注册,意味着你可以在不停机的情况下,通过 API 添加新工具(如临时调试工具)。


三、实现 HTTP 接口:解析请求 → 执行 → 响应

我们以 HTTP 为例(gRPC 实现逻辑类似),在 handler/mcp.go 中处理 /mcp 路由:

func (h *MCPHandler) Handle(w http.ResponseWriter, r *http.Request) {
    // 1. 认证
    token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
    if !h.auth.Validate(token) {
        http.Error(w, "unauthorized", http.StatusUnauthorized)
        return
    }

    // 2. 解析请求体
    var req mcp.ToolRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid JSON", http.StatusBadRequest)
        return
    }

    // 3. 获取工具
    tool, ok := h.registry.Get(req.ToolName)
    if !ok {
        http.Error(w, "tool not found", http.StatusNotFound)
        return
    }

    // 4. 执行工具(带超时)
    ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
    defer cancel()

    result, err := tool.Exec(req.Arguments, ctx)
    
    // 5. 构造响应
    resp := mcp.ToolResponse{
        RequestID: req.RequestID,
        Status:    "success",
        Result:    result,
    }
    if err != nil {
        resp.Status = "error"
        resp.Error = err.Error()
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

重点处理

  • 认证前置:拒绝未授权请求;
  • 超时控制:防止工具阻塞整个服务;
  • 错误封装:统一返回格式,便于 Agent 解析。

四、上下文管理:会话级状态支持

MCP 要求支持 context_id,我们通过一个简单的内存存储实现(生产环境可用 Redis):

// internal/context/store.go
type Store struct {
    mu   sync.RWMutex
    data map[string]map[string]interface{}
}

func (s *Store) Get(ctxID string) (map[string]interface{}, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    v, ok := s.data[ctxID]
    return v, ok
}

func (s *Store) Update(ctxID string, updates map[string]interface{}) {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, exists := s.data[ctxID]; !exists {
        s.data[ctxID] = make(map[string]interface{})
    }
    for k, v := range updates {
        s.data[ctxID][k] = v
    }
}

在工具执行时,可通过 context_id 从 Store 中读取或写入状态,实现跨工具协作(如:第一步验证用户,第二步查询其数据)。


五、错误处理与重试策略

真实运维中,工具可能因网络、依赖故障失败。我们在执行层加入重试机制:

func withRetry(exec func() (interface{}, error), maxRetries int) (interface{}, error) {
    var err error
    for i := 0; i <= maxRetries; i++ {
        if result, e := exec(); e == nil {
            return result, nil
        } else {
            err = e
            time.Sleep(time.Duration(i+1) * time.Second) // 指数退避
        }
    }
    return nil, fmt.Errorf("failed after %d retries: %w", maxRetries, err)
}

同时记录结构化日志(如 request_id, tool_name, error),便于排查问题。


六、测试:确保协议合规

单元测试(工具注册、上下文)

func TestRegistry_Register(t *testing.T) {
    r := tool.NewRegistry()
    err := r.Register(&tool.Tool{Name: "test", Exec: dummyExec})
    assert.NoError(t, err)
    _, ok := r.Get("test")
    assert.True(t, ok)
}

集成测试(端到端 HTTP 调用)

func TestMCPHandler_ToolRequest(t *testing.T) {
    // 启动测试服务器
    // 注册一个 mock 工具
    // 发送 curl 等价请求
    // 验证响应是否符合 MCP 规范
}

所有测试均验证 JSON Schema 合规性,确保与主流 Agent 兼容。


七、Docker 与 GitLab CI/CD

Dockerfile

FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o mcp-server ./cmd/server

FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/mcp-server /usr/local/bin/
EXPOSE 8080
CMD ["mcp-server"]

.gitlab-ci.yml(简化)

stages:
  - test
  - build
  - deploy

test:
  stage: test
  script:
    - go test -v ./...

build:
  stage: build
  script:
    - docker build -t registry.example.com/mcp-server:$CI_COMMIT_SHA .
    - docker push registry.example.com/mcp-server:$CI_COMMIT_SHA

实现代码提交 → 自动测试 → 镜像构建 → 部署的完整闭环


八、MCP Server 架构全景图

下图展示了我们实现的 MCP Server 内部组件协作关系:

该架构高内聚、低耦合,每个模块职责单一,便于维护和扩展。


结语:你写的不只是 Server,而是 AI 的“手脚”

通过本文,你不仅实现了一个 MCP Server,更构建了一个标准化的 AI 能力接入层
未来,无论是数据库查询、内部 API、还是 Shell 脚本,只需实现一个 Exec 函数并注册,就能被任何支持 MCP 的 Agent 调用。
这正是 MCP 的威力所在:让后端工程师成为 AI 时代的能力提供者,而非旁观者
Go 的高性能、简洁性与工程生态,使其成为实现 MCP Server 的理想语言。现在,轮到你动手了!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐