---
title: Go-MCP 接入 Gin：从零搭建 MCP 服务器
published: 2026-06-01T00:50:00
description: 介绍如何使用 mark3labs/mcp-go 与 Gin 框架集成，搭建一个带鉴权的 MCP（Model Context Protocol）服务器，让 AI Agent 能直接调用你的后端服务。
image: /posts/go-mcp-gin/img/cover.svg
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 <token>"})
            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`。
