随着 Model Context Protocol (MCP) 成为连接 AI 模型与数据源、工具的标准协议,使用 Go 语言构建 MCP 客户端的需求日益增长。本文将详细介绍如何使用官方 Go SDK 轻松构建一个功能完善的 MCP 客户端,涵盖从基础连接到调用工具、资源、提示的完整流程。
MCP 与官方 Go SDK 简介
什么是 MCP?
Model Context Protocol (MCP) 是一个开放协议,旨在标准化 AI 语言模型/代理与外部数据源、工具和功能之间的通信。通过 MCP,开发者可以构建:
- MCP 客户端:连接 MCP 服务器以使用其工具、资源和提示的应用程序
- MCP 服务器:向客户端暴露工具、资源和提示的应用程序
官方 Go SDK 概述
官方 Go SDK(github.com/modelcontextprotocol/go-sdk)是由 Model Context Protocol 团队维护的官方实现,与 Google 合作开发。该 SDK 目前处于候选发布状态,即将发布 v1.0.0 版本,API 相对稳定。
SDK 状态说明
| 版本 | 状态 | 说明 |
|---|---|---|
| v0.6.0 | 候选发布 | API 相对稳定,适合开发使用 |
| v0.5.0 | 不稳定 | 包含破坏性变更,不建议用于生产 |
重要提示:官方 SDK 即将发布 v1.0.0 版本,目前 API 已趋于稳定,可以放心使用。
SDK 包结构
官方 SDK 包含以下几个主要包:
| 包路径 | 用途 |
|---|---|
github.com/modelcontextprotocol/go-sdk/mcp | 构建和使用 MCP 客户端/服务器的主要 API |
github.com/modelcontextprotocol/go-sdk/jsonrpc | 用于实现自定义传输层 |
github.com/modelcontextprotocol/go-sdk/auth | OAuth 认证相关原语 |
github.com/modelcontextprotocol/go-sdk/oauthex | OAuth 协议扩展 |
开发环境准备
1. 安装要求
- Go 版本:1.20 或更高
- MCP 服务器:用于测试的 MCP 服务器(可使用示例服务器或任何兼容服务器)
2. 创建项目
# 创建项目目录
mkdir mcp-client-demo
cd mcp-client-demo
# 初始化 Go 模块
go mod init github.com/yourusername/mcp-client-demo3. 安装 SDK
go get github.com/modelcontextprotocol/go-sdk@v0.6.0基础客户端实现
最简单的客户端示例
以下代码演示了如何创建一个基础的 MCP 客户端,连接到服务器并调用工具:
package main
import (
"context"
"log"
"os/exec"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
ctx := context.Background()
// 创建新客户端,指定客户端名称和版本
client := mcp.NewClient(&mcp.Implementation{
Name: "mcp-client-demo",
Version: "v1.0.0",
}, nil)
// 通过 stdio 传输连接到服务器
// 假设服务器可执行文件名为 "myserver"
transport := &mcp.CommandTransport{
Command: exec.Command("myserver"),
}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer session.Close()
log.Println("成功连接到 MCP 服务器")
// 调用服务器上的工具
params := &mcp.CallToolParams{
Name: "greet",
Arguments: map[string]any{"name": "World"},
}
res, err := session.CallTool(ctx, params)
if err != nil {
log.Fatalf("调用工具失败: %v", err)
}
if res.IsError {
log.Fatal("工具执行返回错误")
}
// 处理返回内容
for _, content := range res.Content {
if textContent, ok := content.(*mcp.TextContent); ok {
log.Printf("工具响应: %s", textContent.Text)
}
}
}代码详解
| 组件 | 说明 |
|---|---|
mcp.NewClient() | 创建客户端实例,传入客户端标识信息 |
mcp.CommandTransport | 基于命令的传输层,启动子进程并通过 stdio 通信 |
client.Connect() | 建立与服务器的连接,返回会话对象 |
session.CallTool() | 调用服务器上的工具 |
session.Close() | 关闭会话,释放资源 |
进阶功能实现
1. 检查服务器能力
在调用特定功能前,可以先检查服务器支持哪些能力:
package main
import (
"context"
"log"
"os/exec"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func checkServerCapabilities(session *mcp.Session) {
// 获取服务器信息
serverInfo := session.ServerInfo()
log.Printf("服务器: %s (版本: %s)", serverInfo.Name, serverInfo.Version)
// 检查能力(需要通过初始化响应获取)
// 注意:实际能力信息在连接时已交换,可通过 session 获取
log.Println("已与服务器完成能力协商")
}
func main() {
ctx := context.Background()
client := mcp.NewClient(&mcp.Implementation{
Name: "capability-checker",
Version: "v1.0.0",
}, nil)
transport := &mcp.CommandTransport{
Command: exec.Command("your-server"),
}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()
checkServerCapabilities(session)
}2. 列出可用工具
func listTools(session *mcp.Session) error {
ctx := context.Background()
// 列出所有工具
tools, err := session.ListTools(ctx, nil)
if err != nil {
return err
}
log.Printf("找到 %d 个工具:", len(tools.Tools))
for i, tool := range tools.Tools {
log.Printf(" %d. %s: %s", i+1, tool.Name, tool.Description)
if tool.InputSchema != nil {
log.Printf(" 输入模式: %v", tool.InputSchema)
}
}
return nil
}3. 列出资源
func listResources(session *mcp.Session) error {
ctx := context.Background()
// 列出资源
resources, err := session.ListResources(ctx, nil)
if err != nil {
return err
}
log.Printf("找到 %d 个资源:", len(resources.Resources))
for i, resource := range resources.Resources {
log.Printf(" %d. %s (%s): %s", i+1, resource.Name, resource.URI, resource.Description)
}
// 列出资源模板
templates, err := session.ListResourceTemplates(ctx, nil)
if err != nil {
return err
}
log.Printf("找到 %d 个资源模板:", len(templates.ResourceTemplates))
for i, template := range templates.ResourceTemplates {
log.Printf(" %d. %s: %s", i+1, template.Name, template.URITemplate)
}
return nil
}4. 读取资源内容
func readResource(session *mcp.Session, uri string) error {
ctx := context.Background()
params := &mcp.ReadResourceParams{
URI: uri,
}
result, err := session.ReadResource(ctx, params)
if err != nil {
return err
}
log.Printf("资源内容 (类型: %s):", result.Contents[0].GetType())
for _, content := range result.Contents {
switch c := content.(type) {
case *mcp.TextResourceContent:
log.Printf(" 文本: %s", c.Text)
case *mcp.BlobResourceContent:
log.Printf(" 二进制数据: %d 字节", len(c.Blob))
}
}
return nil
}5. 列出和使用提示
func listPrompts(session *mcp.Session) error {
ctx := context.Background()
// 列出提示
prompts, err := session.ListPrompts(ctx, nil)
if err != nil {
return err
}
log.Printf("找到 %d 个提示:", len(prompts.Prompts))
for i, prompt := range prompts.Prompts {
log.Printf(" %d. %s: %s", i+1, prompt.Name, prompt.Description)
if len(prompt.Arguments) > 0 {
log.Printf(" 参数:")
for _, arg := range prompt.Arguments {
log.Printf(" - %s: %s (必需: %v)",
arg.Name, arg.Description, arg.Required)
}
}
}
return nil
}
func getPrompt(session *mcp.Session, name string, args map[string]string) error {
ctx := context.Background()
params := &mcp.GetPromptParams{
Name: name,
Arguments: args,
}
result, err := session.GetPrompt(ctx, params)
if err != nil {
return err
}
log.Printf("提示内容:")
for _, message := range result.Messages {
log.Printf(" [%s] %v", message.Role, message.Content)
}
return nil
}完整示例:功能完善的客户端
以下是一个综合示例,演示了如何使用 MCP SDK 的所有主要功能:
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
// 命令行参数
serverCmd := flag.String("server", "", "MCP 服务器可执行文件路径")
toolName := flag.String("tool", "", "要调用的工具名称")
toolArg := flag.String("arg", "", "工具参数 (JSON 格式)")
resourceURI := flag.String("resource", "", "要读取的资源 URI")
promptName := flag.String("prompt", "", "要获取的提示名称")
flag.Parse()
if *serverCmd == "" {
log.Fatal("请指定服务器可执行文件路径: -server <path>")
}
ctx := context.Background()
// 创建客户端
client := mcp.NewClient(&mcp.Implementation{
Name: "advanced-mcp-client",
Version: "1.0.0",
}, nil)
// 连接到服务器
transport := &mcp.CommandTransport{
Command: exec.Command(*serverCmd),
}
log.Printf("正在连接到服务器: %s", *serverCmd)
session, err := client.Connect(ctx, transport, nil)
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer session.Close()
log.Println("成功连接!")
// 显示服务器信息
serverInfo := session.ServerInfo()
log.Printf("服务器信息: %s (版本: %s)", serverInfo.Name, serverInfo.Version)
// 根据命令行参数执行不同操作
if *toolName != "" {
// 调用工具
args := make(map[string]any)
if *toolArg != "" {
// 简单解析,实际应使用 json.Unmarshal
args["input"] = *toolArg
}
callTool(ctx, session, *toolName, args)
} else if *resourceURI != "" {
// 读取资源
readResource(ctx, session, *resourceURI)
} else if *promptName != "" {
// 获取提示
getPrompt(ctx, session, *promptName, nil)
} else {
// 默认:列出所有功能
listAllFeatures(ctx, session)
}
}
func callTool(ctx context.Context, session *mcp.Session, name string, args map[string]any) {
log.Printf("调用工具: %s", name)
log.Printf("参数: %v", args)
params := &mcp.CallToolParams{
Name: name,
Arguments: args,
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
result, err := session.CallTool(ctx, params)
if err != nil {
log.Fatalf("调用工具失败: %v", err)
}
if result.IsError {
log.Println("工具返回错误")
}
log.Println("工具响应:")
for i, content := range result.Content {
switch c := content.(type) {
case *mcp.TextContent:
log.Printf(" [%d] 文本: %s", i+1, c.Text)
case *mcp.ImageContent:
log.Printf(" [%d] 图片: %s (MIME: %s)", i+1, c.URL, c.MIMEType)
case *mcp.EmbeddedResource:
log.Printf(" [%d] 嵌入资源", i+1)
default:
log.Printf(" [%d] 未知内容类型", i+1)
}
}
}
func readResource(ctx context.Context, session *mcp.Session, uri string) {
log.Printf("读取资源: %s", uri)
params := &mcp.ReadResourceParams{
URI: uri,
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
result, err := session.ReadResource(ctx, params)
if err != nil {
log.Fatalf("读取资源失败: %v", err)
}
log.Printf("找到 %d 个资源内容:", len(result.Contents))
for i, content := range result.Contents {
switch c := content.(type) {
case *mcp.TextResourceContent:
log.Printf(" [%d] 文本资源: %s", i+1, c.Text)
case *mcp.BlobResourceContent:
log.Printf(" [%d] 二进制资源: %d 字节", i+1, len(c.Blob))
default:
log.Printf(" [%d] 未知资源类型", i+1)
}
}
}
func getPrompt(ctx context.Context, session *mcp.Session, name string, args map[string]string) {
log.Printf("获取提示: %s", name)
params := &mcp.GetPromptParams{
Name: name,
Arguments: args,
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
result, err := session.GetPrompt(ctx, params)
if err != nil {
log.Fatalf("获取提示失败: %v", err)
}
log.Printf("提示包含 %d 条消息:", len(result.Messages))
for i, msg := range result.Messages {
log.Printf(" [%d] %s: %v", i+1, msg.Role, msg.Content)
}
}
func listAllFeatures(ctx context.Context, session *mcp.Session) {
// 列出工具
tools, err := session.ListTools(ctx, nil)
if err != nil {
log.Printf("列出工具失败: %v", err)
} else {
log.Printf("\n=== 可用工具 (%d) ===", len(tools.Tools))
for _, tool := range tools.Tools {
log.Printf(" - %s: %s", tool.Name, tool.Description)
}
}
// 列出资源
resources, err := session.ListResources(ctx, nil)
if err != nil {
log.Printf("列出资源失败: %v", err)
} else {
log.Printf("\n=== 可用资源 (%d) ===", len(resources.Resources))
for _, resource := range resources.Resources {
log.Printf(" - %s (%s): %s", resource.Name, resource.URI, resource.Description)
}
}
// 列出提示
prompts, err := session.ListPrompts(ctx, nil)
if err != nil {
log.Printf("列出提示失败: %v", err)
} else {
log.Printf("\n=== 可用提示 (%d) ===", len(prompts.Prompts))
for _, prompt := range prompts.Prompts {
log.Printf(" - %s: %s", prompt.Name, prompt.Description)
}
}
}传输层选项
MCP SDK 支持多种传输方式:
1. Stdio 传输(默认)
// 通过命令启动服务器进程
transport := &mcp.CommandTransport{
Command: exec.Command("path/to/server", "--arg", "value"),
}
// 或直接使用标准输入输出(假设服务器已在运行)
transport := &mcp.StdioTransport{
In: os.Stdin,
Out: os.Stdout,
}2. SSE 传输(HTTP Server-Sent Events)
// 连接到 SSE 服务器
transport := &mcp.SSETransport{
URL: "http://localhost:8080/sse",
}3. WebSocket 传输
// 连接到 WebSocket 服务器
transport := &mcp.WebSocketTransport{
URL: "ws://localhost:8080/ws",
}错误处理与最佳实践
1. 超时控制
func callToolWithTimeout(session *mcp.Session, name string, args map[string]any) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
params := &mcp.CallToolParams{
Name: name,
Arguments: args,
}
result, err := session.CallTool(ctx, params)
if err != nil {
return fmt.Errorf("工具调用超时或失败: %w", err)
}
// 处理结果...
return nil
}2. 重试机制
func callToolWithRetry(session *mcp.Session, name string, args map[string]any, maxRetries int) (*mcp.CallToolResult, error) {
var lastErr error
for attempt := 0; attempt < maxRetries; attempt++ {
if attempt > 0 {
log.Printf("重试第 %d 次...", attempt)
time.Sleep(time.Second * time.Duration(attempt))
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
params := &mcp.CallToolParams{
Name: name,
Arguments: args,
}
result, err := session.CallTool(ctx, params)
if err == nil {
return result, nil
}
lastErr = err
log.Printf("尝试 %d 失败: %v", attempt+1, err)
}
return nil, fmt.Errorf("在 %d 次尝试后失败: %w", maxRetries, lastErr)
}3. 资源清理
func main() {
client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil)
transport := &mcp.CommandTransport{Command: exec.Command("server")}
session, err := client.Connect(context.Background(), transport, nil)
if err != nil {
log.Fatal(err)
}
// 确保会话关闭
defer func() {
if err := session.Close(); err != nil {
log.Printf("关闭会话时出错: %v", err)
}
}()
// 使用会话...
}与其他 Go MCP SDK 对比
| SDK | 特点 | 适用场景 |
|---|---|---|
| 官方 SDK | 官方维护,即将稳定,API 简洁 | 生产环境,追求长期稳定性 |
| gomcp | 完整规范实现,支持多种传输,丰富的钩子系统 | 需要高级特性或自定义传输 |
| mcp-go | 第三方实现,社区活跃 | 已有项目使用,社区支持 |
| go-mcp | 轻量级,易上手 | 快速原型开发 |
官方 SDK 的优势在于:
- 由 MCP 团队官方维护
- 与规范同步更新
- API 设计简洁,易于使用
- 即将发布 v1.0.0 稳定版本
测试与调试
使用 MCP Inspector 测试
MCP Inspector 是一个类似 Postman 的开发工具,用于测试和调试 MCP 服务器:
# 启动 MCP Inspector
npx @modelcontextprotocol/inspector
# 连接到你的服务器
# 然后在 Inspector 界面中配置服务器命令日志记录
func setupLogging() {
// 设置日志格式
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetOutput(os.Stderr)
// 可以同时输出到文件
f, err := os.OpenFile("mcp-client.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err == nil {
log.SetOutput(io.MultiWriter(os.Stderr, f))
}
}总结
通过官方 Go SDK 构建 MCP 客户端具有以下优势:
- 简单易用:API 设计简洁,几行代码即可完成连接和调用
- 功能完整:支持工具、资源、提示等所有 MCP 核心功能
- 即将稳定:v1.0.0 即将发布,API 已趋于稳定
- 传输灵活:支持 stdio、SSE、WebSocket 等多种传输方式
- 官方维护:与 MCP 规范同步更新,长期有保障
本文从基础客户端到完整功能实现,全面介绍了使用官方 Go SDK 构建 MCP 客户端的方法。无论是简单的工具调用,还是复杂的资源管理,官方 SDK 都能提供简洁而强大的支持。
暂无评论