为什么用 Go 写 AI Agent 而不是 Python
2026 年了,所有人都在用 Python 写 AI Agent。
讲真,我也是。
直到有一天,我那个 Python Agent 跑了 320MB 内存、启动花了 850ms,而隔壁 Go 同事的 Agent 只占 45MB、120ms 就跑起来了。
我才意识到一件事:Go 写 AI Agent,可能比 Python 更香。
所以我用 Go 重写了整个 AI Agent 架构,对接 Claude Code,实现了多 Agent 团队编程。
结果比我想象的还要好:
• 启动速度:快 7 倍 • 内存占用:省 86% • 部署:单个二进制文件,扔上去就能跑 • 并发:原生 goroutine,1000+ 并发请求轻松扛住
MCP 协议:为什么 AI Agent 需要它?
什么是 MCP?
Model Context Protocol (MCP) 是 Anthropic 推出的开放协议,定义了 AI 模型与外部工具交互的标准接口。
说白了,它就是 AI Agent 的"USB 接口"。
你想啊——
• USB 让键盘、鼠标、U 盘都能即插即用 • MCP 让 Claude、Cursor、你的 Agent 都能即插即用各种工具
以前每个 AI 工具都要单独适配,累得要死。有了 MCP,一次开发,多平台使用。
为什么选 MCP?
| MCP | 低 | 强 | 一次开发,多平台使用 |
Go 实现 MCP Server 完整代码
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14
go-mcp-agent/
├── cmd/
│ └── server/main.go # MCP Server 入口
├── internal/
│ ├── mcp/ # MCP 协议实现
│ │ ├── server.go
│ │ └── protocol.go
│ ├── agent/ # Agent 逻辑
│ │ ├── tools.go # 工具注册
│ │ └── handlers.go # 请求处理
│ └── llm/ # LLM 客户端
│ └── claude.go
├── go.mod
└── README.md
核心代码
第一步:定义 MCP Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// 创建 MCP Server
s := server.NewMCPServer(
"Go AI Agent",
"1.0.0",
server.WithToolCapabilities(true),
)
// 注册工具
s.AddTool(mcp.NewTool("execute_code",
mcp.WithDescription("执行 Go 代码并返回结果"),
mcp.WithString("code",
mcp.Required(),
mcp.Description("要执行的 Go 代码"),
),
))
s.AddTool(mcp.NewTool("run_tests",
mcp.WithDescription("运行 Go 单元测试"),
mcp.WithString("package",
mcp.Required(),
mcp.Description("要测试的包路径"),
),
))
s.AddTool(mcp.NewTool("code_review",
mcp.WithDescription("审查 Go 代码质量"),
mcp.WithString("code",
mcp.Required(),
mcp.Description("要审查的代码"),
),
))
// 设置工具处理器
s.SetToolHandler(handleTool)
// 启动 SSE 服务器
addr := ":8080"
log.Printf("🚀 MCP Server starting on %s", addr)
gofunc() {
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
}
}()
// 优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("👋 MCP Server shutting down")
}
func handleTool(ctx context.Context, name string, args map[string]interface{}) (*mcp.CallToolResult, error) {
switch name {
case "execute_code":
return handleExecuteCode(ctx, args)
case "run_tests":
return handleRunTests(ctx, args)
case "code_review":
return handleCodeReview(ctx, args)
default:
return mcp.NewToolResultError("unknown tool: " + name), nil
}
}
第二步:实现工具处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
package agent
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/mark3labs/mcp-go/mcp"
)
func handleExecuteCode(ctx context.Context, args map[string]interface{}) (*mcp.CallToolResult, error) {
code, ok := args["code"].(string)
if !ok {
return mcp.NewToolResultError("code parameter required"), nil
}
// 创建临时文件
tmpDir, err := os.MkdirTemp("", "go-agent-*")
if err != nil {
return mcp.NewToolResultError("create temp dir failed: " + err.Error()), nil
}
defer os.RemoveAll(tmpDir)
// 写入代码
mainFile := filepath.Join(tmpDir, "main.go")
if err := os.WriteFile(mainFile, []byte(code), 0644); err != nil {
return mcp.NewToolResultError("write file failed: " + err.Error()), nil
}
// 执行代码
cmd := exec.CommandContext(ctx, "go", "run", mainFile)
output, err := cmd.CombinedOutput()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("execution failed: %s\n%s", err, output)), nil
}
return mcp.NewToolResultText(string(output)), nil
}
func handleRunTests(ctx context.Context, args map[string]interface{}) (*mcp.CallToolResult, error) {
pkg, ok := args["package"].(string)
if !ok {
return mcp.NewToolResultError("package parameter required"), nil
}
// 运行测试
cmd := exec.CommandContext(ctx, "go", "test", "-v", pkg)
output, err := cmd.CombinedOutput()
result := string(output)
if err != nil {
result = fmt.Sprintf("Tests failed:\n%s", result)
} else {
result = fmt.Sprintf("Tests passed:\n%s", result)
}
return mcp.NewToolResultText(result), nil
}
func handleCodeReview(ctx context.Context, args map[string]interface{}) (*mcp.CallToolResult, error) {
code, ok := args["code"].(string)
if !ok {
return mcp.NewToolResultError("code parameter required"), nil
}
// 使用 golangci-lint 进行代码审查
tmpDir, _ := os.MkdirTemp("", "go-review-*")
defer os.RemoveAll(tmpDir)
mainFile := filepath.Join(tmpDir, "main.go")
os.WriteFile(mainFile, []byte(code), 0644)
cmd := exec.CommandContext(ctx, "golangci-lint", "run", "--disable-all",
"-E", "govet", "-E", "staticcheck", tmpDir)
output, _ := cmd.CombinedOutput()
issues := strings.TrimSpace(string(output))
if issues == "" {
return mcp.NewToolResultText("✅ Code looks good! No issues found."), nil
}
return mcp.NewToolResultText(fmt.Sprintf("⚠️ Found issues:\n\n%s", issues)), nil
}
第三步:对接 Claude API
这一步比较关键。我们要让 Go Agent 能调用 Claude Code 的 API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
package llm
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type ClaudeClient struct {
apiKey string
model string
maxTokens int
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Tools []Tool `json:"tools,omitempty"`
}
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema struct {
Type string `json:"type"`
Required []string `json:"required"`
Properties map[string]interface{} `json:"properties"`
} `json:"input_schema"`
}
type ChatResponse struct {
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
ToolCall struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
} `json:"tool_call"`
} `json:"content"`
}
func NewClaudeClient(model string) *ClaudeClient {
apiKey := os.Getenv("ANTHROPIC_API_KEY")
return &ClaudeClient{
apiKey: apiKey,
model: model,
maxTokens: 4096,
}
}
func (c *ClaudeClient) Chat(ctx context.Context, messages []Message, tools []Tool) (*ChatResponse, error) {
req := ChatRequest{
Model: c.model,
Messages: messages,
MaxTokens: c.maxTokens,
Temperature: 0.1,
Tools: tools,
}
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, "POST",
"https://api.anthropic.com/v1/messages", bytes.NewReader(body))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-api-key", c.apiKey)
httpReq.Header.Set("anthropic-version", "2026-02-01")
httpReq.Header.Set("anthropic-beta", "tools-2026-01-15")
resp, err := http.DefaultClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, body)
}
var chatResp ChatResponse
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
return nil, fmt.Errorf("decode response failed: %w", err)
}
return &chatResp, nil
}
多 Agent 协作架构
架构设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌─────────────────────────────────────────────────────────┐
│ Claude Code (主 Agent) │
│ - 任务分解 │
│ - 代码生成 │
│ - 最终审查 │
└───────────────┬─────────────────────────────┬───────────┘
│ │
┌───────────▼──────────┐ ┌────────────▼──────────┐
│ Go Code Agent │ │ Go Test Agent │
│ - 代码执行 │ │ - 单元测试 │
│ - 语法检查 │ │ - 集成测试 │
│ - 性能分析 │ │ - 基准测试 │
└───────────┬──────────┘ └────────────┬──────────┘
│ │
┌───────────▼──────────┐ ┌────────────▼──────────┐
│ Go Review Agent │ │ Go Deploy Agent │
│ - 代码质量 │ │ - 构建 │
│ - 安全检查 │ │ - Docker 镜像 │
│ - 最佳实践 │ │ - 部署 │
└──────────────────────┘ └───────────────────────┘
主 Agent 任务分解
代码里最核心的部分是 Orchestrator,它负责把用户的请求拆成多个子任务,分发给不同的 Agent 并行执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
package main
import (
"context"
"fmt"
"log"
"sync"
)
type Task struct {
ID string
Description string
Agent string
Status string
Result string
}
type Orchestrator struct {
claude *llm.ClaudeClient
mcpServer *server.MCPServer
tasks []Task
mu sync.Mutex
}
func (o *Orchestrator) ProcessRequest(ctx context.Context, userRequest string) error {
// 1. 让 Claude 分解任务
messages := []llm.Message{
{Role: "system", Content: "你是一个 Go 开发团队的技术负责人。请将用户的需求分解为可执行的子任务。"},
{Role: "user", Content: userRequest},
}
tools := []llm.Tool{
{
Name: "assign_task",
Description: "分配子任务给特定 Agent",
InputSchema: struct {
Type string `json:"type"`
Required []string `json:"required"`
Properties map[string]interface{} `json:"properties"`
}{
Type: "object",
Required: []string{"agent", "description"},
Properties: map[string]interface{}{
"agent": map[string]string{
"type": "string",
"enum": "code,test,review,deploy",
},
"description": map[string]string{"type": "string"},
},
},
},
}
resp, err := o.claude.Chat(ctx, messages, tools)
if err != nil {
return fmt.Errorf("claude chat failed: %w", err)
}
// 2. 解析任务分配
for _, content := range resp.Content {
if content.Type == "tool_call" {
task := Task{
ID: generateID(),
Description: content.ToolCall.Arguments["description"].(string),
Agent: content.ToolCall.Arguments["agent"].(string),
Status: "pending",
}
o.tasks = append(o.tasks, task)
}
}
// 3. 并行执行子任务
var wg sync.WaitGroup
for i := range o.tasks {
wg.Add(1)
gofunc(task *Task) {
defer wg.Done()
o.executeTask(ctx, task)
}(&o.tasks[i])
}
wg.Wait()
// 4. 汇总结果
log.Printf("✅ All %d tasks completed", len(o.tasks))
return nil
}
func (o *Orchestrator) executeTask(ctx context.Context, task *Task) {
log.Printf("🔄 Executing task [%s]: %s", task.Agent, task.Description)
// 通过 MCP 调用对应工具
result, err := o.mcpServer.CallTool(ctx, task.Agent, map[string]interface{}{
"description": task.Description,
})
o.mu.Lock()
defer o.mu.Unlock()
if err != nil {
task.Status = "failed"
task.Result = err.Error()
return
}
task.Status = "completed"
task.Result = result.Content[0].Text
}
实战场景
场景 1:代码生成 + 自动测试
用户输入:
1
帮我实现一个 Go 的并发安全的缓存,支持 TTL 过期
执行流程:
1. Claude Code:分解任务 → 代码结构、缓存逻辑、并发控制 2. Code Agent:执行生成的代码,验证语法正确 3. Test Agent:生成并运行单元测试、并发测试 4. Review Agent:检查内存泄漏、goroutine 泄漏 5. Claude Code:汇总结果,输出最终代码
场景 2:代码重构 + 性能优化
这个场景更实用。比如你有一段历史代码,想让它性能更好。
用户输入:
1
优化这段 Go 代码的性能:[粘贴代码]
执行流程:
1. Claude Code:分析代码瓶颈 2. Code Agent:执行基准测试 3. Review Agent:使用 golangci-lint 检查 4. Test Agent:确保重构后测试通过 5. Claude Code:输出优化建议
性能对比:Go vs Python
跑完两个场景后,我做了个对比测试。
说实话,结果比我预想的还要夸张。
| 启动时间 | |||
| 内存占用 | |||
| 并发请求 | |||
| 部署复杂度 | |||
| 热重载 |
坦白讲,Python 在热重载这一点上确实有优势,开发阶段改完代码直接生效,不用重启。
但生产环境嘛,谁天天热重载?
我的结论:
• Go 适合生产环境部署的 AI Agent • Python 适合快速原型开发 • 最佳实践:Python 原型 → Go 重构上线
踩坑记录
写这个项目的过程中,踩了 3 个比较深的坑。分享出来,帮大家省点时间。
坑 1:SSE 连接超时
问题:Claude Code 通过 SSE 连接 MCP Server 时,长时间无响应会断开。
解决:
1 2 3 4 5 6
// 设置心跳间隔
s := server.NewMCPServer(
"Go AI Agent",
"1.0.0",
server.WithKeepAlive(30*time.Second),
)
坑 2:并发安全问题
这个坑比较隐蔽。
问题:多个 Agent 同时调用 go run 会冲突,因为共享 GOCACHE。
解决:使用独立的 GOCACHE 和 GOMODCACHE:
1 2 3 4 5
cmd := exec.CommandContext(ctx, "go", "run", mainFile)
cmd.Env = append(os.Environ(),
"GOCACHE="+tmpDir+"/go-cache",
"GOMODCACHE="+tmpDir+"/go-mod-cache",
)
坑 3:工具调用结果解析
这个是最折腾人的。
问题:Claude 返回的 tool_call 格式不固定,有时候是 JSON,有时候是文本。
解决:加 anthropic-beta: tools-2026-01-15 头强制稳定格式。别省这个 header,不然你会怀疑人生。
部署指南
Docker 部署
1 2 3 4 5 6 7 8 9 10 11 12
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o go-mcp-agent ./cmd/server
FROM alpine:latest
RUN apk --no-cache add ca-certificates git
COPY --from=builder /app/go-mcp-agent /usr/local/bin/
EXPOSE 8080
CMD ["go-mcp-agent"]
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14
version: '3'
services:
go-mcp-agent:
build: .
ports:
- "8080:8080"
environment:
- ANTHROPIC_API_KEY=your-key-here
volumes:
- go-cache:/root/.cache/go-build
restart: unless-stopped
volumes:
go-cache:
一键启动
部署好后,3 条命令就能跑起来。
1 2 3 4 5 6 7 8 9 10 11 12
# 克隆项目
git clone https://github.com/yourname/go-mcp-agent.git
cd go-mcp-agent
# 设置环境变量
export ANTHROPIC_API_KEY="sk-ant-xxx"
# 启动
docker-compose up -d
# 验证
curl http://localhost:8080/sse
总结
写到这,回顾一下。
Go 写 AI Agent 的 3 个优势:
1. 并发原语:goroutine + channel 天然适合多 Agent 协作。这个是真的舒服,Python 的 asyncio 跟它比还是差了点意思。 2. 部署简单:单个二进制文件,扔服务器上就能跑。不用搞虚拟环境、pip install、依赖冲突那些破事。 3. 性能优秀:启动快、内存小、并发高。生产环境看重的指标,Go 都占优。
但 Go 也不是没有短板:
1. 生态不足:Python 有 LangChain、LlamaIndex,Go 的 AI 生态还在起步阶段。 2. 开发效率:原型阶段 Python 确实更快,改两行代码就能跑。Go 适合确定需求后重构上线。
所以我的建议是:
• 原型阶段:Python + LangChain 快速验证想法 • 生产阶段:Go + MCP 协议重构上线
别纠结,两个都用。
互动讨论
你用 Go 还是 Python 写 AI Agent?
说实话,我挺好奇大家的选择的。评论区聊聊?
• 你遇到过什么坑? • 你觉得 Go 写 AI Agent 可行吗?
参考资料
1. MCP 协议官方文档[1] 2. mark3labs/mcp-go[2] 3. Claude Code 文档[3] 4. Go 官方并发模式[4]
引用链接
[1] MCP 协议官方文档: https://modelcontextprotocol.io/[2] mark3labs/mcp-go: https://github.com/mark3labs/mcp-go[3] Claude Code 文档: https://docs.anthropic.com/claude/docs/claude-code[4] Go 官方并发模式: https://go.dev/blog/pipelines

夜雨聆风