手把手用 Go 写一个 MCP Server:让大模型真正“调得动”你的服务
在前两篇文章中,我们揭开了 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 的理想语言。现在,轮到你动手了!
更多推荐


所有评论(0)