<!-- title: Go构建MCP服务器：stdio到Streamable HTTP -->

# Go 构建 MCP 服务器：从 stdio 本地工具到 Streamable HTTP 远程部署

## 概述

本教程面向已熟悉一种后端语言、想让 Claude Code 调用自建工具链的开发者。你将从零搭建一个用 Go 编写的 MCP（Model Context Protocol）服务器，先以 **stdio** 传输作为本地子进程接入 Claude Code，再迁移到 2025-03-26 规范引入的 **Streamable HTTP** 传输，把它部署成可被团队、云端、跨机器共享的远程服务。教程同时对比官方 `modelcontextprotocol/go-sdk`（已发布 v1.0.0）与社区 `mark3labs/mcp-go` 两套 SDK 的取舍，并给出生产加固（CORS、OAuth 受保护资源元数据、限流中间件、会话清理）的落地代码。

## 前置条件

- Go 1.23+（官方 SDK 跟随 Go 最近两个稳定版的发布策略）
- 已安装并跑通 Claude Code（CLI）
- 了解 JSON-RPC 的基本概念（请求带 `method`/`params`，响应带 `result`/`error`）
- 一台可用于监听 8080 端口的本机或服务器

## 一、为什么用 Go 写 MCP 服务器

绝大多数入门教程用 Python（FastMCP）或 TypeScript（`@modelcontextprotocol/sdk`）起步。它们上手快，但在"把 MCP 当成长期服务运行"时，Go 有三个结构性优势：

1. **冷启动与延迟**：Go 编译成单个原生二进制，没有解释器预热，也没有 Python 在每次 `tools/call` 时重复加载依赖的开销。对 stdio 模式尤其关键——Claude Code 会在需要时反复 spawn 你的进程，启动越快，首次调用越顺滑。
2. **真并发**：Python 受 GIL 约束，asyncio 在阻塞型 IO（如读大文件、查数据库）面前需要格外小心。Go 的 goroutine 天生适合"一个请求一个协程"，配合 `context.Context` 做超时与取消非常自然。
3. **分发即部署**：`go build` 产出一个无依赖的二进制，扔进 Docker scratch 镜像只有十几兆，团队共享和 CI 流水线集成成本极低。

代价是：Go 的 MCP 生态比 Python/TS 年轻，配套的"脚手架生成器"较少。但官方 Go SDK 已在 2025 年 9 月中旬推出并达到 v1.0.0，能力已足够覆盖生产场景。

## 二、两套 SDK：官方 go-sdk vs mcp-go

| 维度 | `modelcontextprotocol/go-sdk`（官方） | `mark3labs/mcp-go`（社区） |
| --- | --- | --- |
| 维护方 | Anthropic 协作维护，规范跟随最快 | 社区主导，迭代激进 |
| 工具定义 | 泛型 `AddTool[In, Out]`，struct tag 自动生成 JSON Schema | `mcp.NewTool` + 链式选项 `WithString/WithNumber` |
| 传输 | stdio / Streamable HTTP / SSE / Command | stdio / Streamable HTTP / SSE |
| 生产特性 | 较精简，留给你自己组装 | 内置中间件、CORS、OAuth 元数据、采样、会话清理 |
| 心智负担 | 低，类型安全 | 中，API 表面积大但开箱即用 |

**选型建议**：学习与新项目首选官方 SDK，代码干净、泛型让 schema 与 handler 强绑定，重构时编译器替你兜底；当你需要"装上即用"的中间件链、RFC 9728 OAuth 受保护资源元数据、跨域、采样等成熟能力时，再评估 mcp-go。本教程主线用官方 SDK 走完全流程，第七节用 mcp-go 演示生产加固。

## 三、环境准备

```bash
mkdir logseek && cd logseek
go mod init github.com/yourname/logseek
go get github.com/modelcontextprotocol/go-sdk
```

创建一个示例日志目录，方便后面测试：

```bash
mkdir -p logs
echo "2026-06-16T08:12:01 INFO  用户 alice 登录" > logs/2026-06-16.log
echo "2026-06-16T08:13:44 ERROR 数据库连接超时 timeout=5s" >> logs/2026-06-16.log
echo "2026-06-16T08:14:02 WARN  重试第 2 次" >> logs/2026-06-16.log
```

## 四、Step 1：用官方 go-sdk 写第一个工具（stdio）

新建 `main.go`，实现一个"按正则检索某天日志"的工具，并用 stdio 传输跑起来。

```go
package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

// --- 工具入参：struct tag 自动生成 JSON Schema 供客户端（LLM）理解 ---
type SearchParams struct {
	Pattern string `json:"pattern" jsonschema:"正则或关键字"`
	Date    string `json:"date"    jsonschema:"日期 YYYY-MM-DD，留空表示今天"`
	Limit   int    `json:"limit"   jsonschema:"返回行数上限，默认 50"`
}

type CountParams struct {
	Pattern string `json:"pattern"`
	Date    string `json:"date"`
}

func logDir() string {
	if d := os.Getenv("LOGSEEK_DIR"); d != "" {
		return d
	}
	return "logs"
}

func today() string { return time.Now().Format("2006-01-02") }

// --- handler 签名固定：泛型版 ToolHandlerFor[In] ---
func searchLogs(ctx context.Context, _ *mcp.ServerSession,
	p *mcp.CallToolParamsFor[SearchParams]) (*mcp.CallToolResultFor[any], error) {

	date := p.Arguments.Date
	if date == "" {
		date = today()
	}
	path := filepath.Join(logDir(), date+".log")

	f, err := os.Open(path)
	if err != nil {
		// 返回 error 会被 SDK 转成 tool 级错误（IsError=true），不会让进程崩溃
		return nil, fmt.Errorf("打开日志失败 %s: %w", path, err)
	}
	defer f.Close()

	re, err := regexp.Compile(p.Arguments.Pattern)
	if err != nil {
		return nil, fmt.Errorf("非法正则: %w", err)
	}
	limit := p.Arguments.Limit
	if limit <= 0 {
		limit = 50
	}

	var hits []string
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 1<<20), 1<<20) // 单行最大 1MB，避免超长行截断
	for sc.Scan() {
		if ctx.Err() != nil { // 响应客户端取消
			return nil, ctx.Err()
		}
		line := sc.Text()
		if re.MatchString(line) {
			hits = append(hits, line)
			if len(hits) >= limit {
				break
			}
		}
	}

	text := fmt.Sprintf("命中 %d 行（上限 %d）:\n%s",
		len(hits), limit, strings.Join(hits, "\n"))
	return &mcp.CallToolResultFor[any]{
		Content: []mcp.Content{&mcp.TextContent{Text: text}},
	}, nil
}

func countLogs(ctx context.Context, _ *mcp.ServerSession,
	p *mcp.CallToolParamsFor[CountParams]) (*mcp.CallToolResultFor[any], error) {

	date := p.Arguments.Date
	if date == "" {
		date = today()
	}
	data, err := os.ReadFile(filepath.Join(logDir(), date+".log"))
	if err != nil {
		return nil, err
	}
	re, err := regexp.Compile(p.Arguments.Pattern)
	if err != nil {
		return nil, err
	}
	n := len(re.FindAll(data, -1))
	return &mcp.CallToolResultFor[any]{
		Content: []mcp.Content{
			&mcp.TextContent{Text: strconv.Itoa(n)},
		},
	}, nil
}

func newServer() *mcp.Server {
	s := mcp.NewServer(&mcp.Implementation{Name: "logseek", Version: "1.0.0"}, nil)
	mcp.AddTool(s,
		&mcp.Tool{Name: "search_logs", Description: "按正则搜索某天日志的匹配行"},
		searchLogs)
	mcp.AddTool(s,
		&mcp.Tool{Name: "count_logs", Description: "统计某天日志中某模式的出现次数"},
		countLogs)
	return s
}

func main() {
	mode := flag.String("mode", "stdio", "stdio | http")
	flag.Parse()

	s := newServer()

	switch *mode {
	case "http":
		runHTTP(s)
	default:
		// 阻塞直到客户端断开或 context 取消
		if err := s.Run(context.Background(), mcp.NewStdioTransport()); err != nil {
			log.Fatal(err)
		}
	}
}

func runHTTP(s *mcp.Server) {
	handler := mcp.NewStreamableHTTPHandler(
		func(*http.Request) *mcp.Server { return s },
		&mcp.StreamableHTTPOptions{},
	)
	http.HandleFunc("/mcp", handler.ServeHTTP)
	log.Println("logseek HTTP 监听 :8080/mcp")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

**几个关键点**：

- **Schema 即类型**：`SearchParams` 的 `jsonschema` tag 会被 SDK 反射成 JSON Schema 挂到 `tools/list` 的响应里。LLM 据此决定何时调用、传什么参数。你改结构体，schema 自动同步，不会出现"文档说支持 limit，代码却读不出来"的漂移。
- **错误分两层**：handler 返回 `error` 是"工具级失败"（结果里 `IsError=true`，文本给到模型）；只有 panic 或传输层错误才会让连接异常。永远不要在 handler 里 `log.Fatal`。
- **`ctx` 要用**：长任务务必检查 `ctx.Err()`，否则 Claude Code 取消时你的扫描还在空跑。

### 接入 Claude Code（stdio 模式）

先编译出二进制并放到 PATH，或直接用绝对路径：

```bash
go build -o logseek .
```

在项目根目录添加服务器，使用 stdio 传输：

```bash
claude mcp add --transport stdio logseek -- /home/yourname/logseek/logseek
```

`--` 之后是真正启动进程的命令，Claude Code 不会把它当自己的参数解析。配置默认写入 `~/.claude.json` 的 local scope（仅当前项目、私有）。验证：

```bash
claude mcp list
claude mcp get logseek
```

进入交互会话用 `/mcp` 查看连接状态，应看到 `logseek` 后面带工具数 `2`。然后直接提问：

> 帮我看看 2026-06-16 的日志里有没有 ERROR，统计一下出现了几次。

Claude 会自行决定调用 `search_logs`（pattern=`ERROR`）和 `count_logs`，你无需手动指定工具。

### 常见问题

- **`tools` 数显示 0**：说明服务器进程起来了但没注册工具，检查 `mcp.AddTool` 是否执行；也可能是 handler 签名与泛型不匹配导致注册被静默跳过——`go vet ./...` 能抓到一部分。
- **`/mcp` 显示连接失败**：八成是 `command` 路径不对。用 `which logseek` 确认，或写绝对路径。
- **日志找不到**：stdio 子进程的工作目录可能与你想的不同，建议显式设环境变量，`claude mcp add --env LOGSEEK_DIR=/var/log/app --transport stdio logseek -- ./logseek`。

## 五、Step 2：迁移到 Streamable HTTP

stdio 适合"个人本地工具"，但它把进程绑定在你本机，无法跨机器、无法被团队共享、无法做集中式鉴权。把同一个服务器切换到 Streamable HTTP 传输，它就变成了一个普通的远程 web 服务。

关键背景：MCP 规范 **2025-03-26** 版本用 **Streamable HTTP** 取代了旧的 HTTP+SSE 传输。旧方案要两个端点（`/sse` 接收、`/sse/messages` 发送），像用两部手机对话；新方案只用**单个端点**（通常是 `/mcp`），既能像普通 HTTP 请求/响应那样处理快速调用，又能在长任务时动态升级成 SSE 流，还能让服务器反向通过同一连接发通知（采样、进度）。旧的 SSE 传输已标记废弃。

好消息是：上文的 `runHTTP` 已经是 Streamable HTTP 实现，`-mode http` 即可：

```bash
./logseek -mode http
# logseek HTTP 监听 :8080/mcp
```

用 Claude Code 接入远程服务器：

```bash
claude mcp add --transport http logseek http://localhost:8080/mcp
```

等价的项目级配置（写入项目根的 `.mcp.json`，可提交版本控制与团队共享）：

```json
{
  "mcpServers": {
    "logseek": {
      "type": "http",
      "url": "http://localhost:8080/mcp"
    }
  }
}
```

`type` 字段同时接受 `http` 和 `streamable-http`，后者与 MCP 规范用词一致，所以从服务器文档里复制过来的配置无需改动。带鉴权头时：

```bash
claude mcp add --transport http logseek https://mcp.yourcorp.com/mcp \
  --header "Authorization: Bearer ${LOGSEEK_TOKEN}"
```

`.mcp.json` 里支持环境变量展开 `"url": "${LOGSEEK_BASE:-https://mcp.yourcorp.com}/mcp"` 与 `"headers": {"Authorization": "Bearer ${LOGSEEK_TOKEN}"}`，敏感值不落库。

需要 OAuth 时，无需手动配置流程：Claude Code 在服务器返回 `401/403` 时自动把它标记为"需认证"，在 `/mcp` 面板里走浏览器登录；服务器若按 RFC 9728 暴露 `/.well-known/oauth-protected-resource`，会被自动发现。

## 六、Streamable HTTP 的关键机制

理解下面四点，调试和设计远程 MCP 时就不会抓瞎。

**1. 单端点 + 会话 ID。** 客户端 `POST /mcp` 发 `initialize`，服务器可选地回一个 `Mcp-Session-Id` 响应头（如 `abc-123`）。此后每次请求，客户端都要在 `Mcp-Session-Id` 请求头里带上它，服务器据此把同一会话的工具列表、订阅、日志级别等状态归并起来。

**2. 动态降级/升级。** 简单调用就是一次普通 `POST`，返回 `application/json`；如果调用过程中服务器要推进度通知，同一个 POST 响应会被升级成 `text/event-stream`（SSE）；客户端想持续监听服务器主动推送（如采样请求），可发 `GET /mcp` 建立长连接。`DELETE /mcp` 结束会话。这正是"Streamable"的含义——按需流式，不必为短查询维持长连接。

**3. 有状态 vs 无状态。** 默认有状态：会话内可累积上下文。横向扩展（多副本、负载均衡）时，要么开启粘性路由（同一 session 落到同一实例），要么走无状态模式——每个请求自包含所有信息，状态外置到 Redis/数据库。无状态模式放弃会话亲和，换来任意副本可处理的水平扩展能力。

**4. 会话清理。** 客户端断开却没发 `DELETE`，会话状态会泄漏内存。生产环境务必设置空闲 TTL，定期清扫长时间无请求的会话并使其 ID 失效。

## 七、生产加固（用 mcp-go 演示成熟能力）

官方 SDK 刻意精简，把组装权留给你。若你想"装上即用"，`mark3labs/mcp-go` 提供了一整套生产选项。下面代码切换到 mcp-go（`go get github.com/mark3labs/mcp-go`），同样的服务器，加固四件套：

```go
package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func main() {
	s := server.NewMCPServer("logseek", "1.0.0",
		server.WithToolCapabilities(true),
		server.WithResourceCapabilities(true, true),
		server.WithLogging(),
	)

	s.AddTool(mcp.NewTool("search_logs",
		mcp.WithDescription("按正则搜索某天日志"),
		mcp.WithString("pattern", mcp.Required()),
		mcp.WithString("date"),
		mcp.WithNumber("limit", mcp.DefaultNumber(50), mcp.Max(500)),
	), handleSearch)

	// ① 工具中间件链：鉴权 + 限流 + 缓存
	s.AddToolMiddleware(func(next server.ToolHandler) server.ToolHandler {
		return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
			token := req.Header.Get("Authorization") // 传输层自动透传 HTTP 头
			if token == "" {
				return mcp.NewToolResultError("authentication required"), nil
			}
			return next(ctx, req)
		}
	})

	// ② Streamable HTTP 服务器，含端点、会话清理、CORS、OAuth 元数据
	httpServer := server.NewStreamableHTTPServer(s,
		server.WithEndpointPath("/mcp"),
		server.WithStateLess(false),
		server.WithSessionIdleTTL(10*time.Minute), // 清扫空闲 10 分钟的会话
		server.WithStreamableHTTPCORS(
			server.WithCORSAllowedOrigins("https://app.yourcorp.com"),
			server.WithCORSAllowCredentials(),
			server.WithCORSMaxAge(300),
		),
		server.WithProtectedResourceMetadata(server.ProtectedResourceMetadataConfig{
			Resource:            "https://mcp.yourcorp.com/mcp",
			AuthorizationServers: []string{"https://auth.yourcorp.com"},
			ScopesSupported:     []string{"logs:read"},
			BearerMethodsSupported: []string{"header"},
		}),
	)
	log.Println("生产 logseek 监听 :8080/mcp")
	log.Fatal(httpServer.Start(":8080"))
}

func handleSearch(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// req.GetString("pattern", ""), req.GetInt("limit", 50) …
	return mcp.NewToolResultText("..."), nil
}
```

四个加固点的意义：

- **中间件链**：`AddToolMiddleware` 在每个 `tools/call` 前后切面，可叠加鉴权、限流（如 `golang.org/x/time/rate`）、埋点、缓存。注意顺序——鉴权应放最外层。
- **会话空闲 TTL**：`WithSessionIdleTTL` 启动后台清扫器，干掉泄漏会话，`Shutdown()` 时自动停止。
- **CORS**：MCP 端点默认不开 CORS（避免安全回退）。浏览器端客户端跨域时用 `WithStreamableHTTPCORS` 显式放行；`*` + 凭证会被规范要求回显具体 Origin。
- **OAuth 受保护资源元数据（RFC 9728）**：客户端据此发现该用哪个授权服务器、支持哪些 scope。Claude Code 的自动 OAuth 发现正是读这个端点。带路径的资源（`/mcp`）会自动挂到 `/.well-known/oauth-protected-resource/mcp`。

**采样（sampling）**——服务器反向请求客户端的 LLM 能力——也已被 Streamable HTTP 支持，且规范强制要求"人在回路"审批。生产中若开启 `mcpServer.EnableSampling()`，务必实现一个会向用户展示并允许拒绝的采样 handler，否则服务器可能在你不知情时以你的名义产生 LLM 调用与费用。

## 八、完整实战：日志检索服务 logseek 的端到端流程

把上面拼起来，一个可交付的最小服务长这样：本地用 stdio 开发调试 → 编译成单二进制 → 容器化以 HTTP 部署 → 团队经 Claude Code 接入。

Dockerfile（scratch 镜像，约 15MB）：

```dockerfile
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/logseek .

FROM gcr.io/distroless/static
COPY --from=build /out/logseek /logseek
ENV LOGSEEK_DIR=/data
EXPOSE 8080
ENTRYPOINT ["/logseek", "-mode", "http"]
```

```bash
docker build -t logseek .
docker run -p 8080:8080 -v $(pwd)/logs:/data logseek
claude mcp add --transport http logseek http://localhost:8080/mcp
```

### 架构与交互图

```mermaid
flowchart LR
  subgraph 本地开发[本地开发 stdio]
    CC1[Claude Code] -->|spawn + stdin/stdout| LS1[logseek 二进制]
    LS1 --> F1[(本地 logs/)]
  end
  subgraph 远程部署[团队/云端 Streamable HTTP]
    CC2[Claude Code] -->|POST /mcp\nMcp-Session-Id| GW[网关: CORS+鉴权+OAuth元数据]
    GW --> LS2[logseek HTTP :8080]
    LS2 --> F2[(共享日志卷)]
  end
```

```mermaid
sequenceDiagram
    participant CC as Claude Code
    participant S as logseek /mcp
    CC->>S: POST /mcp  initialize
    S-->>CC: 200  Mcp-Session-Id: abc-123
    CC->>S: POST /mcp  tools/list  (Header: Mcp-Session-Id)
    S-->>CC: search_logs, count_logs
    CC->>S: POST /mcp  tools/call search_logs {pattern:"ERROR"}
    Note over S: 命中行多 → 响应升级为 text/event-stream
    S-->>CC: 200  命中结果（流式分块）
    CC->>S: DELETE /mcp  结束会话
    S-->>CC: 200
```

### Claude Code 侧的运维要点

- **自动重连**：HTTP 服务器断线时，Claude Code 以指数退避重试最多 5 次（1s 起翻倍）；5 次失败标记为 failed，可在 `/mcp` 手动重试。所以你的服务器做滚动升级时，客户端会自愈。
- **大输出控制**：工具返回超过 10,000 token 会告警，默认上限 25,000 token。日志类工具极易触发——要么分页，要么在 `tools/list` 里给工具加 `_meta["anthropic/maxResultSizeChars"]`（最高 50 万字符），把大 schema/全量日志这种"天生就大"的结果放行，避免被转存成文件引用。
- **工具搜索（Tool Search）**：默认开启，工具定义按需加载而非一次性塞进上下文。给服务器写清晰的 `instructions`（"本服务处理日志检索，需要查日志时调用"），Claude 据此判断何时来搜你的工具——这和 Skill 的触发逻辑异曲同工。描述与指令各截断在 2KB，重要信息放最前面。
- **动态工具更新**：服务器发 `list_changed` 通知，Claude Code 会自动刷新工具列表，无需重连。

## 九、常见问题汇总

- **stdio 模式下进程频繁重启慢**：把初始化（读配置、建连接池）尽量做轻；重 IO 放到 handler 内按需执行。
- **HTTP 模式多副本会话错乱**：要么粘性路由，要么 `WithStateLess(true)` + 状态外置。
- **`/mcp` 一直 pending**：检查防火墙/端口；首次连接失败 Claude Code 会重试 3 次（v2.1.121 起），仍失败才标记 failed。
- **浏览器客户端被 CORS 拦截**：默认不开 CORS，需显式 `WithStreamableHTTPCORS` 放行来源。
- **schema 没暴露给模型**：确认 `tools/list` 返回里 `inputSchema` 非空；官方 SDK 靠 struct tag，tag 写错 schema 就空。
- **远程服务器要 OAuth 但 `/mcp` 没提示登录**：确认返回了 `401/403`，并按 RFC 9728 暴露了 `/.well-known/oauth-protected-resource`。

## 延伸阅读

- 官方 Go SDK 仓库与示例：<https://github.com/modelcontextprotocol/go-sdk>
- mcp-go 文档（传输、中间件、OAuth、采样）：<https://mcp-go.dev/transports/http/>
- MCP 规范 2025-03-26 传输章节：<https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>
- Claude Code 接入 MCP 全参考：<https://code.claude.com/docs/en/mcp>
- SSE 为何被 Streamable HTTP 取代：<https://blog.fka.dev/blog/2025-06-06-why-mcp-deprecated-sse-and-go-with-streamable-http/>

## 训练营适用性评估

- **适用类型**：后端工程训练营（Go 方向）、AI 工程化训练营、企业内部工具链建设营。不适合纯前端或零编程基础营。
- **教学时长**：建议 4–5 小时。其中 SDK 选型与第一个 stdio 工具 1.5 小时；Streamable HTTP 迁移与机制讲解 1 小时；生产加固与容器化部署 1.5 小时；端到端联调与排障 1 小时。
- **前置衔接**：建议放在"已学过一种 MCP 客户端接入（如用 Claude Code 连官方服务器）"之后，作为"从消费者到生产者"的跨越。
- **可裁剪**：时间紧时跳过第七节生产加固，保留 stdio + HTTP 主线即可跑通；训练营偏运维则反向，主攻部署与加固。

## 参考来源

1. [StreamableHTTP Transport – MCP-Go 官方文档](https://mcp-go.dev/transports/http/) — mcp-go 的 StreamableHTTP 实现细节、端点、会话管理、CORS、OAuth 元数据、采样与 fasthttp 适配，代码示例来源。
2. [Why MCP Deprecated SSE and Went with Streamable HTTP – fka.dev](https://blog.fka.dev/blog/2025-06-06-why-mcp-deprecated-sse-and-go-with-streamable-http/) — SSE 双端点模型的局限、Streamable HTTP 单端点设计、规范版本 2025-03-26 与 TypeScript SDK 1.10.0 时间线。
3. [Getting Started – modelcontextprotocol/go-sdk DeepWiki](https://deepwiki.com/modelcontextprotocol/go-sdk/2-getting-started) — 官方 Go SDK 的 NewServer/AddTool/Run、StdioTransport、泛型 ToolHandlerFor 与 struct tag 自动生成 schema 的权威说明。
4. [How to Build an MCP Server in Go: A Complete Guide – ContexaAI](https://www.contexaai.com/blogs/build-mcp-server-golang) — 官方 go-sdk 的泛型 handler 签名、NewStreamableHTTPHandler 用法、Go 相对 Python 的性能优势。
5. [Connect Claude Code to tools via MCP – Claude Code Docs](https://code.claude.com/docs/en/mcp) — `claude mcp add --transport http`、`.mcp.json` 配置、scope 层级、OAuth 自动发现、工具搜索、大输出与 `anthropic/maxResultSizeChars` 等客户端侧行为。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
