本教程带你用 Go 语言 和 Anthropic 官方维护的 modelcontextprotocol/go-sdk,从零写出一个可被 Claude Code 直接调用的 MCP 服务器。学完你将掌握:Go SDK 的"结构体即接口"开发范式、多工具注册、stdio 传输、JSON Schema 自动生成,以及把服务器接入 Claude Code 的完整闭环。适用人群:有 Go 基础(懂 goroutine、结构体标签即可)、想用静态类型语言给大模型造工具的后端/运维工程师。
之前几轮我们用 Python(FastMCP)、TypeScript(MCP SDK)、Rust 都造过 MCP 服务器。那 Go 值不值得再单独学一遍?答案取决于你的场景:
| 维度 | Go + 官方 go-sdk | Python(FastMCP) | TypeScript SDK |
|---|---|---|---|
| 部署产物 | 单文件二进制,零依赖 | 需 Python 运行时+依赖 | 需 Node 运行时 |
| 类型安全 | 编译期强类型 | 运行时 | 编译期 |
| 启动速度 | 毫秒级 | 较慢(导入开销) | 中等 |
| Schema 生成 | 结构体标签自动派生 | 装饰器自动派生 | Zod/手动 |
最大的杀手锏是单二进制部署:Claude Code 通过 command 字段直接拉起一个进程,Go 编译出的可执行文件不需要装解释器、不需要 npm install、不需要 venv。这对运维工具、CLI 工具尤其友好——你写完 go build,把二进制丢进 PATH,Claude Code 就能调。
官方 SDK 截至 2026 年已迭代到 v1.7.0+,支持最新的 MCP 规范版本 2026-07-28,并且完整实现了 Tools / Resources / Prompts / Sampling / Elicitation 等全部原语。本教程聚焦最常用的 Tools 原语 + stdio 传输。
claude --version)package main、懂 context.Context、看过结构体 json 标签不需要你之前学过其他语言的 MCP SDK——Go 版的范式和它们都不太一样。
这是整个教程最重要的一节,请仔细读。
其他语言的 SDK 多用装饰器或 builder 模式声明工具入参。Go 官方 SDK 走了一条更符合 Go 哲学的路:你定义一个普通结构体作为 Input,SDK 自动从结构体反射出 JSON Schema 暴露给模型;模型调用时传入的 JSON 参数,SDK 又自动反序列化成这个结构体交给你的 handler。
// Input 结构体 = 工具的入参契约
type ProbeInput struct {
Host string `json:"host" jsonschema:"the hostname or IP to probe"`
Port int `json:"port" jsonschema:"the TCP port number (1-65535)"`
Timeout int `json:"timeout" jsonschema:"timeout in milliseconds, default 3000"`
}
三个标签各司其职:
json:"host" —— 序列化/反序列化的字段名(模型传 JSON 时用的 key)jsonschema:"..." —— 这个字段会出现在给模型看的工具描述里,写清楚它能极大提升模型调用准确率string / int)会自动映射成 Schema 里的 type: string / type: integer你的 handler 函数签名是固定的泛型三返回值:
func TCPProbe(ctx context.Context, req *mcp.CallToolRequest, in ProbeInput) (*mcp.CallToolResult, ProbeOutput, error) {
// in 已经是反序列化好的强类型结构体,直接用
...
return nil, ProbeOutput{...}, nil // 第一个返回值通常是 nil
}
返回值三件套的含义:
*mcp.CallToolResult —— 当你想返回非结构化的自由文本/多模态内容时填它(比如一段日志、一张图);纯结构化返回时给 nilProbeOutput —— 结构化输出,SDK 会按它的标签生成 output schema,并在结果里塞一份 structuredContenterror —— 协议级错误(基础设施挂了)。注意:业务上的"探测失败"不该用 error 返回,而应该正常返回带 IsError: true 的 CallToolResult,否则模型会以为工具本身坏了这套设计的好处是:你的工具逻辑是纯 Go 函数,没有任何 MCP/JSON 知识渗透进来,单元测试就是普通的 Go 测试。
mkdir netcheck-mcp && cd netcheck-mcp
go mod init github.com/yourname/netcheck-mcp
go get github.com/modelcontextprotocol/go-sdk/mcp@latest
go get 会拉取最新稳定版。本教程代码基于 v1.7.x API。
先写一个最小可运行版本,确认链路通了,再加复杂工具。新建 main.go:
package main
import (
"context"
"log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// ---- 第一个工具:问候 ----
type HelloInput struct {
Name string `json:"name" jsonschema:"the name of the person to greet"`
}
type HelloOutput struct {
Greeting string `json:"greeting" jsonschema:"the greeting text returned to the user"`
}
func SayHi(ctx context.Context, req *mcp.CallToolRequest, in HelloInput) (*mcp.CallToolResult, HelloOutput, error) {
return nil, HelloOutput{Greeting: "你好," + in.Name + "!我是用 Go 写的 MCP 工具。"}, nil
}
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "netcheck", Version: "v0.1.0"}, nil)
// 注册工具:传入 server、工具元数据、handler
mcp.AddTool(server, &mcp.Tool{
Name: "greet",
Description: "向指定的人打招呼,用于验证 MCP 链路是否通畅",
}, SayHi)
// 通过 stdin/stdout 运行,直到客户端断开
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}
关键点解读:
mcp.NewServer(实现信息, 配置) 第一个参数是服务器的名字和版本(会出现在协议握手里),第二个传 nil 用默认配置mcp.AddTool 的第二个参数是 *mcp.Tool,注意 Name 是模型调用时用的标识符,必须唯一;Description 是给模型看的,写清"干什么、什么时候用"mcp.StdioTransport{} 是本地集成的标准传输:Claude Code 会 fork 你的进程,通过它的 stdin/stdout 收发 JSON-RPC 消息。你的工具里绝对不能往 stdout 打印调试信息——那会污染协议通道,必须用 log(走 stderr)或返回值编译验证:
go build -o netcheck-mcp .
./netcheck-mcp # 会卡住等待输入,按 Ctrl+C 退出 —— 这是正常的
卡住等输入是对的,说明它在 stdio 上待命。先别关,下一步直接让 Claude Code 来连它。
在另一个终端,进入任意项目目录,注册这个本地服务器:
claude mcp add netcheck -- /absolute/path/to/netcheck-mcp
claude mcp add 把服务器详情写进配置文件(默认注册到 local 作用域,仅当前项目可见)。-- 后面是启动命令;用绝对路径最稳妥,避免 PATH 找不到。
验证注册成功:
claude mcp list # 应该看到 netcheck
claude mcp get netcheck # 查看完整配置
然后启动 Claude Code 交互会话:
claude
在对话里输入:请用 greet 工具向 "张工" 打个招呼。
预期结果: 模型识别到你有 greet 工具,自动填入 {"name":"张工"} 调用它,最终输出 你好,张工!我是用 Go 写的 MCP 工具。。
常见问题排查:
| 现象 | 原因 | 解决 |
|---|---|---|
claude mcp list 显示 failed to connect |
二进制路径不对或无执行权限 | 用绝对路径;chmod +x netcheck-mcp |
| 模型说"没有这个工具" | 未重启会话,或注册到错的 scope | 在目标项目目录下重新 claude mcp add |
| 工具报错"invalid input" | 结构体标签没写对 | 检查 json / jsonschema 标签拼写 |
| 连上但模型乱传参 | Description 太模糊 |
把工具描述写具体,包含取值范围 |
链路一通,我们就可以放心加真正的实战工具了。
现在把项目升级成一个真正有用的运维 MCP 服务器,包含三个工具:DNS 解析、TCP 端口探测、HTTP 探测。全部用 Go 标准库,零额外依赖。
把 main.go 替换为完整版本:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
const defaultTimeout = 3000 // 毫秒
// ========== 工具 1:DNS 解析 ==========
type DNSInput struct {
Host string `json:"host" jsonschema:"the hostname to resolve, e.g. example.com"`
}
type DNSOutput struct {
Host string `json:"host"`
IPs []string `json:"ips" jsonschema:"resolved IP addresses"`
}
func DNSLookup(ctx context.Context, req *mcp.CallToolRequest, in DNSInput) (*mcp.CallToolResult, DNSOutput, error) {
ips, err := net.LookupHost(in.Host)
if err != nil {
// 业务失败:返回带 IsError 的结果,而不是 protocol error
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf("DNS 解析失败: %v", err)}},
IsError: true,
}, DNSOutput{}, nil
}
return nil, DNSOutput{Host: in.Host, IPs: ips}, nil
}
// ========== 工具 2:TCP 端口探测 ==========
type ProbeInput struct {
Host string `json:"host" jsonschema:"hostname or IP to probe"`
Port int `json:"port" jsonschema:"TCP port, 1-65535"`
Timeout int `json:"timeout" jsonschema:"timeout in ms, optional, default 3000"`
}
type ProbeOutput struct {
Host string `json:"host"`
Port int `json:"port"`
Reachable bool `json:"reachable" jsonschema:"whether the port accepted a connection"`
RTTMs int64 `json:"rtt_ms" jsonschema:"round-trip time in milliseconds, -1 if unreachable"`
}
func TCPProbe(ctx context.Context, req *mcp.CallToolRequest, in ProbeInput) (*mcp.CallToolResult, ProbeOutput, error) {
timeout := in.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
addr := fmt.Sprintf("%s:%d", in.Host, in.Port)
start := time.Now()
conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Millisecond)
rtt := time.Since(start).Milliseconds()
if err != nil {
return nil, ProbeOutput{Host: in.Host, Port: in.Port, Reachable: false, RTTMs: -1}, nil
}
conn.Close()
return nil, ProbeOutput{Host: in.Host, Port: in.Port, Reachable: true, RTTMs: rtt}, nil
}
// ========== 工具 3:HTTP 探测 ==========
type HTTPInput struct {
URL string `json:"url" jsonschema:"full URL including scheme, e.g. https://example.com"`
Timeout int `json:"timeout" jsonschema:"timeout in ms, optional, default 3000"`
}
type HTTPOutput struct {
URL string `json:"url"`
StatusCode int `json:"status_code"`
RTTMs int64 `json:"rtt_ms"`
Server string `json:"server,omitempty"`
FinalURL string `json:"final_url,omitempty"`
}
func HTTPProbe(ctx context.Context, req *mcp.CallToolRequest, in HTTPInput) (*mcp.CallToolResult, HTTPOutput, error) {
timeout := in.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
client := &http.Client{Timeout: time.Duration(timeout) * time.Millisecond}
start := time.Now()
resp, err := client.Get(in.URL)
rtt := time.Since(start).Milliseconds()
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: fmt.Sprintf("HTTP 请求失败: %v", err)}},
IsError: true,
}, HTTPOutput{}, nil
}
defer resp.Body.Close()
return nil, HTTPOutput{
URL: in.URL,
StatusCode: resp.StatusCode,
RTTMs: rtt,
Server: resp.Header.Get("Server"),
FinalURL: resp.Request.URL.String(),
}, nil
}
// ========== 主函数:注册并运行 ==========
func main() {
server := mcp.NewServer(&mcp.Implementation{Name: "netcheck", Version: "v0.2.0"}, nil)
mcp.AddTool(server, &mcp.Tool{
Name: "dns_lookup",
Description: "解析主机名到 IP 地址列表。当用户想知道某个域名解析到哪些 IP 时使用。",
}, DNSLookup)
mcp.AddTool(server, &mcp.Tool{
Name: "tcp_probe",
Description: "探测目标主机的 TCP 端口是否可达并测量连接耗时(毫秒)。用于检查服务端口是否开放。",
}, TCPProbe)
mcp.AddTool(server, &mcp.Tool{
Name: "http_probe",
Description: "对目标 URL 发起 HTTP GET,返回状态码、耗时、Server 头。用于快速检查网站是否在线。",
}, HTTPProbe)
log.Printf("netcheck MCP server starting, %d tools registered", 3) // 走 stderr,不污染协议
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}
重新编译并让 Claude Code 重新加载:
go build -o netcheck-mcp .
注意:之前注册过 netcheck,配置里指向的还是同一个二进制路径,你只要重新编译覆盖那个文件即可,无需重新 claude mcp add。但在已开的 Claude Code 会话里,需要重启会话才会加载新版二进制(MCP 服务器是在会话启动时 fork 的)。
进入新会话后试这些指令:
查一下 github.com 解析到哪些 IP探测一下 8.8.8.8 的 53 端口通不通检查 https://httpbin.org/status/200 是否在线,状态码是多少模型会根据每个工具的 Description 自动选对工具、填对参数,并用人话解读结构化结果。
claude mcp add 是个人配置。如果想让整个团队的项目都用上这个服务器,把配置写进项目根目录的 .mcp.json,提交到版本库:
{
"mcpServers": {
"netcheck": {
"command": "/opt/tools/netcheck-mcp",
"args": [],
"env": {}
}
}
}
Claude Code 在该项目目录启动时,会读取 .mcp.json 并提示用户授权连接(出于安全,项目级 MCP 服务器首次连接需用户确认)。这样同事 clone 项目后,开箱即得同一套工具。
type 字段:stdio 服务器可省略(默认就是 stdio);若是远程 HTTP/SSE 服务器则写 "type": "http" + "url"。
把上面三件套串起来,就是一个真实可用的"网络排障"工作流。重新编译后,在 Claude Code 里给一个复合任务:
我怀疑
api.example.com有问题。帮我排查:先 DNS 解析看 IP 是否正常,再探测它的 443 端口,最后对https://api.example.com做一次 HTTP 探测,综合告诉我结论。
预期行为:模型会串行调用三个工具(DNS → TCP → HTTP),把每一步的结构化输出汇总,最终给你一个判断:"DNS 正常解析到 x.x.x.x,443 端口可达(耗时 45ms),但 HTTP 返回 503,可能是后端服务异常。"——而这三步的"排障判断"逻辑是模型基于工具返回的强类型数据自己推理的。
这就是 MCP 的价值:你只负责把能力暴露成可靠的、强类型的、自描述的工具,编排与推理交给模型。
flowchart LR
U[用户在 Claude Code<br/>输入排障指令] --> CC[Claude Code<br/>模型推理+编排]
CC -->|读取 .mcp.json<br/>发现工具列表| REG[工具注册表]
REG --> T1[dns_lookup]
REG --> T2[tcp_probe]
REG --> T3[http_probe]
CC -->|JSON-RPC over stdio<br/>fork 进程| BIN[/netcheck-mcp<br/>Go 二进制/]
BIN --> T1
BIN --> T2
BIN --> T3
T1 -->|net.LookupHost| NET[(真实网络)]
T2 -->|net.DialTimeout| NET
T3 -->|http.Client| NET
T1 & T2 & T3 -->|结构化 Output| CC
CC -->|自然语言结论| U
style BIN fill:#00ADD8,color:#fff
style CC fill:#d97706,color:#fff
通了的链路只是起点,Go SDK 还有几个值得探索的能力:
ToolHandler:当你需要手写 Schema 校验逻辑(比如约束 port 在 1-65535、自定义错误消息)时,用底层 handler 自己控制 unmarshalAndValidate,官方 toolschemas 示例有完整范式StdioTransport{} 换成 streamable HTTP transport,就能把 Go 服务器部署成远程服务,给团队共享——配合上一轮讲过的远程部署模式rate-limiting 示例)、鉴权(auth-middleware 示例)mcp.CallToolRequest{} + Input 结构体调用即可断言 Output,无需起真实传输层| 评估项 | 说明 |
|---|---|
| 适合训练营类型 | Go 后端训练营、运维/DevOps 自动化训练营、AI 工程实战训练营 |
| 前置技能 | Go 基础(结构体、context、标准库 net) |
| 难度 | 中等(API 直观,但需理解 JSON Schema 反射) |
| 预计教学时长 | 3-4 小时(含动手:1h 讲范式 + 2h 写三件套 + 1h 接入排障) |
| 学员产出 | 一个可带回家复用的 Go 单二进制 MCP 运维工具 |
| 与已有课程衔接 | 可作为"Go 进阶"的实战收尾,或"AI Agent 工程"的本地工具化模块 |
NewServer/AddTool/StdioTransport/结构体 I/O 范式)与版本兼容表mcp、jsonrpc、auth 等子包ToolHandlerFor 与底层 ToolHandler 手写 Schema 校验两种范式claude mcp add、.mcp.json、stdio/HTTP 配置与作用域说明本文由「51学通信」原创制作,站长爱卫生(微信:gprshome201101)持续分享通信与 AI 工程实战。想交流 MCP 落地、Go 工具开发,欢迎留言。
[!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 等场景全覆盖。
