From 393637eb702c750a0a71dce747f7ed465d35c88d Mon Sep 17 00:00:00 2001 From: Kaxi <1042864399@qq.com> Date: Mon, 1 Jun 2026 00:05:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20go-mcp=20=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=20gin=20=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- content/posts/go-mcp-gin/index.md | 267 ++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 content/posts/go-mcp-gin/index.md diff --git a/content/posts/go-mcp-gin/index.md b/content/posts/go-mcp-gin/index.md new file mode 100644 index 0000000..ee5d5ee --- /dev/null +++ b/content/posts/go-mcp-gin/index.md @@ -0,0 +1,267 @@ +--- +title: Go-MCP 接入 Gin:从零搭建 MCP 服务器 +published: 2026-06-01 +description: 介绍如何使用 mark3labs/mcp-go 与 Gin 框架集成,搭建一个带鉴权的 MCP(Model Context Protocol)服务器,让 AI Agent 能直接调用你的后端服务。 +image: /posts/go-mcp-gin/img/cover.png +tags: ['Go', 'MCP', 'Gin', 'AI Agent', '后端'] +--- + +## 什么是 MCP + +MCP(Model Context Protocol)是 Anthropic 提出的开放协议,用于让 AI 助手(如 Claude)与外部服务进行标准化交互。简单来说:你提供一个 MCP 服务器,AI 就能发现并调用你定义的工具(Tools),就像给 AI 装了一双手。 + +传统做法是写 REST API 然后让 AI 通过函数调用(Function Calling)间接使用,但 MCP 统一了协议层,AI 客户端只需要配置一个 URL 就能接入所有工具。 + +## 为什么选 Gin + mcp-go + +Go 生态里做 MCP 服务器最成熟的库是 `mark3labs/mcp-go`,它支持 StreamableHTTP 传输模式,可以直接挂载到任意 HTTP 框架上。Gin 作为 Go 最流行的 HTTP 框架,天然适配。 + +核心思路:**MCP 服务器本质上就是一个 HTTP Handler,挂到 Gin 路由上即可。** + +## 需求分析 + +我们要搭建一个具备以下能力的 MCP 服务器: + +1. **工具注册** — 向 AI 暴露可调用的工具(列出资源、查看详情、创建/删除) +2. **HTTP 集成** — MCP 端点作为 Gin 路由的一部分,与业务 API 共存 +3. **鉴权** — MCP 请求必须经过身份验证,工具处理器能获取当前用户信息 + +## 实现步骤 + +### 1. 安装依赖 + +```bash +go get github.com/mark3labs/mcp-go@v0.54.0 +go get github.com/gin-gonic/gin@v1.12.0 +``` + +### 2. 创建 MCP 服务器 + +```go +import "github.com/mark3labs/mcp-go/server" + +mcpServer := server.NewMCPServer( + "my-app", // 服务器名称 + "1.0.0", // 版本 + server.WithLogging(), + server.WithRecovery(), + server.WithInstructions("这是一个 MCP 服务器示例"), +) +``` + +`NewMCPServer` 创建一个 MCP 协议实例,它负责处理工具发现(`tools/list`)和工具调用(`tools/call`)的协议逻辑。 + +### 3. 注册工具 + +```go +import "github.com/mark3labs/mcp-go/mcp" + +// 定义工具 +listTool := mcp.NewTool("list_items", + mcp.WithDescription("列出所有项目"), +) + +// 注册处理器 +mcpServer.AddTool(listTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 业务逻辑 + return mcp.NewToolResultText("返回结果"), nil +}) +``` + +带参数的工具: + +```go +getTool := mcp.NewTool("get_item", + mcp.WithDescription("获取指定项目的详情"), + mcp.WithString("item_id", + mcp.Required(), + mcp.Description("项目 ID"), + ), +) + +mcpServer.AddTool(getTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + itemID, err := req.RequireString("item_id") + if err != nil { + return mcp.NewToolResultError("缺少 item_id 参数"), nil + } + // 查询并返回 + return mcp.NewToolResultText("项目详情..."), nil +}) +``` + +### 4. 挂载到 Gin 路由 + +关键一步:用 `server.NewStreamableHTTPServer` 把 MCP 实例转成 HTTP Handler,再用 `gin.WrapH` 适配 Gin。 + +```go +import "github.com/mark3labs/mcp-go/server" + +mcpHTTPServer := server.NewStreamableHTTPServer(mcpServer, + server.WithEndpointPath("/mcp"), + server.WithHeartbeatInterval(30*time.Second), +) + +// 挂载到 Gin +r.Any("/mcp", gin.WrapH(mcpHTTPServer)) +``` + +就这样,`POST /mcp` 端点就生效了。AI 客户端通过这个 URL 发送 JSON-RPC 请求,MCP 服务器自动处理协议握手、工具列表返回、工具调用路由。 + +### 5. 完整的服务启动代码 + +```go +func main() { + r := gin.Default() + + // 创建 MCP 服务器 + mcpServer := server.NewMCPServer("my-app", "1.0.0", + server.WithLogging(), + server.WithRecovery(), + ) + + // 注册工具 + registerTools(mcpServer) + + // 挂载到 Gin + mcpHTTP := server.NewStreamableHTTPServer(mcpServer, + server.WithEndpointPath("/mcp"), + server.WithHeartbeatInterval(30*time.Second), + ) + r.Any("/mcp", gin.WrapH(mcpHTTP)) + + // 其他业务路由 + r.GET("/api/health", func(c *gin.Context) { + c.JSON(200, gin.H{"status": "ok"}) + }) + + r.Run(":8080") +} +``` + +此时启动服务,用 curl 测试: + +```bash +# 发现工具列表 +curl -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' + +# 调用工具 +curl -X POST http://localhost:8080/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_items","arguments":{}}}' +``` + +## 拓展:接入鉴权 + +裸奔的 MCP 端点任何人都能调用,生产环境必须加鉴权。思路和普通 API 鉴权一致:Bearer Token。 + +### 问题 + +MCP 的工具处理器签名为 `func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)`,它不接收 Gin 的 `*gin.Context`,所以无法直接在工具处理器里读取请求头。 + +### 解决方案:中间件传递用户信息 + +用两层中间件:第一层做 Gin 的 Token 验证,第二层把用户信息从 Gin Context 复制到 `request.Context`。 + +```go +// Gin 鉴权中间件 +func AuthRequired(authService *AuthService) gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.AbortWithStatusJSON(401, gin.H{"message": "缺少认证令牌"}) + return + } + + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + c.AbortWithStatusJSON(401, gin.H{"message": "格式错误,请使用 Bearer "}) + return + } + + user, err := authService.GetUserByToken(parts[1]) + if err != nil { + c.AbortWithStatusJSON(401, gin.H{"message": "无效的认证令牌"}) + return + } + + c.Set("userID", user.ID) + c.Set("username", user.Username) + c.Next() + } +} +``` + +```go +// 用户信息桥接中间件:Gin Context → request.Context +type ctxKey string +const ( + ctxUserID ctxKey = "userID" + ctxUsername ctxKey = "username" +) + +func WithUserContext() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.Request.Context() + ctx = context.WithValue(ctx, ctxUserID, c.GetUint("userID")) + ctx = context.WithValue(ctx, ctxUsername, c.GetString("username")) + c.Request = c.Request.WithContext(ctx) + c.Next() + } +} +``` + +### 组合使用 + +```go +r.Any("/mcp", + AuthRequired(authService), // 1. 验证 Token + WithUserContext(), // 2. 桥接用户信息到 request.Context + gin.WrapH(mcpHTTP), // 3. MCP 处理 +) +``` + +在工具处理器里就能拿到用户信息了: + +```go +func (h *Handler) handleListItems(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + userID, ok := ctx.Value(ctxUserID).(uint) + if !ok { + return mcp.NewToolResultError("未认证"), nil + } + + items, _ := h.service.ListByUser(userID) + // ... + return mcp.NewToolResultText(result), nil +} +``` + +### 客户端配置 + +AI 客户端(如 Claude Desktop)配置 MCP 服务器时带上 Authorization 头: + +```json +{ + "mcpServers": { + "my-app": { + "url": "http://localhost:8080/mcp", + "headers": { + "Authorization": "Bearer your-token-here" + } + } + } +} +``` + +## 总结 + +| 步骤 | 说明 | +|------|------| +| `NewMCPServer` | 创建 MCP 协议实例 | +| `NewTool` + `AddTool` | 定义并注册工具 | +| `NewStreamableHTTPServer` | 转为 HTTP Handler | +| `gin.WrapH` | 挂载到 Gin 路由 | +| `AuthRequired` + `WithUserContext` | 鉴权 + 用户信息桥接 | + +核心就一句话:**MCP 是 HTTP Handler,挂上去就行。** 鉴权复用你已有的中间件体系,只需要多写一个桥接函数把用户信息传进 `request.Context`。