MCP详解

本文介绍如何使用 Go 语言开发 MCP (Model Context Protocol) Server 和 Client,并集成到 CodeBuddy 等 AI 客户端中。

思维导图

1. MCP 协议介绍

1.1 什么是 MCP

MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 于 2024 年 11 月发布的开放标准协议。说白了,MCP 就是 AI 领域的"USB-C 标准"——就像 USB 创建了一个通用接口,让任何 USB 设备都能连接到任何 USB 端口一样,MCP 让 LLM 应用能够以标准化的方式连接到各种外部数据源和工具。

打个比方,以前每个 AI 应用想要调用外部工具,都得自己写一套对接代码,就像早期每个手机厂商都有自己的充电接口一样。现在有了 MCP,大家都用同一套协议,开发一次就能到处使用。

1.2 MCP 协议架构

MCP 采用客户端-服务器架构,通过 JSON-RPC 2.0 协议进行通信。下面这张图展示了 MCP 的整体架构:

MCP Server (Go 实现)

Host (CodeBuddy/IDE)

执行操作

读取数据

JSON-RPC 2.0

LLM 大语言模型

MCP Client

Tools 工具

Resources 资源

Prompts 提示词

外部系统/API

数据源

从图中可以看到,MCP 架构主要包含三个角色:

角色 说明
Host 宿主应用,如 CodeBuddy、Claude Desktop 等 AI 客户端
MCP Client 内置在 Host 中,负责与 MCP Server 通信
MCP Server 提供具体能力的服务端,可以用 Go、Python、TypeScript 等语言实现

1.3 MCP 核心概念

MCP Server 可以提供三种类型的能力:

能力类型 说明 类比
Tools(工具) 可执行的操作,如调用 API、执行计算、操作数据库等 REST API 的 POST 端点
Resources(资源) 可读取的数据,如文件内容、数据库记录、配置信息等 REST API 的 GET 端点
Prompts(提示词) 可复用的交互模板,定义 LLM 与用户的交互模式 预设的对话模板

1.4 MCP 数据流

下面这张时序图展示了一次完整的 MCP 调用流程:

外部系统 MCP Server (Go) MCP Client CodeBuddy 用户 外部系统 MCP Server (Go) MCP Client CodeBuddy 用户 发送请求 解析意图 initialize 请求 返回能力声明 tools/list 请求 返回可用工具列表 tools/call 请求 执行具体操作 返回结果 返回执行结果 整合响应 展示结果

整个流程可以分为三个阶段:

  1. 初始化阶段:Client 发送 initialize 请求,Server 返回自己支持的能力(capabilities)
  2. 发现阶段:Client 通过 tools/listresources/list 等请求获取可用的工具和资源
  3. 调用阶段:Client 根据用户意图调用具体的工具或读取资源

2. Go 开发环境准备

2.1 环境要求

依赖 版本要求 说明
Go 1.24+ 本文使用 Go 1.24
mcp-go v0.44.0+ Go 语言 MCP SDK

2.2 创建项目

# 创建项目目录
mkdir mcp-demo && cd mcp-demo

# 初始化 Go 模块
go mod init mcp-demo

# 安装 mcp-go SDK
go get github.com/mark3labs/mcp-go

执行完成后,go.mod 文件内容如下:

module mcp-demo

go 1.24

require github.com/mark3labs/mcp-go v0.44.0

3. MCP Server 开发

这一节我们来实现一个完整的 MCP Server,包含 Tools、Resources、Prompts 三种能力。

3.1 项目结构

mcp-demo/
├── go.mod
├── go.sum
└── main.go          # MCP Server 完整实现

3.2 完整代码实现

下面是一个功能完整的 MCP Server 实现,包含详尽的中文注释:

package main

import (
	"context"
	"fmt"
	"os"
	"strings"
	"time"

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

func main() {

	// 第一步:创建 MCP Server 实例
	// NewMCPServer 参数说明:
	// - name: 服务器名称,会在客户端显示
	// - version: 服务器版本号
	// - options: 可选配置项
	s := server.NewMCPServer(
		"Go MCP Demo Server",                        // 服务器名称
		"1.0.0",                                     // 版本号
		server.WithToolCapabilities(true),           // 启用 Tools 能力
		server.WithResourceCapabilities(true, true), // 启用 Resources 能力
		server.WithPromptCapabilities(true),         // 启用 Prompts 能力
		server.WithRecovery(),                       // 启用 panic 恢复,防止服务器崩溃
	)

	// 第二步:注册 Tools(工具)
	registerTools(s)

	// 第三步:注册 Resources(资源)
	registerResources(s)

	// 第四步:注册 Prompts(提示词模板)
	registerPrompts(s)

	// 第五步:启动服务器
	// ServeStdio 通过标准输入输出与客户端通信
	// 这是最常用的传输方式,适合与 CodeBuddy 等客户端集成
	_, _ = fmt.Fprintln(os.Stderr, "MCP Server 启动中...")
	if err := server.ServeStdio(s); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "服务器错误: %v\n", err)
		os.Exit(1)
	}
}

// Tools 注册函数
// registerTools 注册所有工具
// Tools 是 MCP 最核心的能力,用于执行具体操作
func registerTools(s *server.MCPServer) {
	// 工具1:hello_world - 打招呼工具
	// 这是最简单的工具示例,演示基本的参数处理
	helloTool := mcp.NewTool("hello_world",
		// 工具描述,会显示在客户端的工具列表中
		mcp.WithDescription("向指定的人打招呼,返回问候语"),
		// 定义字符串类型参数
		mcp.WithString("name",
			mcp.Required(),                        // 标记为必填参数
			mcp.Description("要打招呼的人的名字"), // 参数描述
		),
	)

	// 添加工具和对应的处理函数
	s.AddTool(helloTool, helloHandler)

	// 工具2:calculator - 计算器工具
	// 演示枚举类型参数和数值计算
	calculatorTool := mcp.NewTool("calculator",
		mcp.WithDescription("执行基本的四则运算"),
		// 枚举类型参数,限定可选值
		mcp.WithString("operation",
			mcp.Required(),
			mcp.Description("运算类型"),
			mcp.Enum("add", "subtract", "multiply", "divide"), // 限定可选值
		),
		// 数值类型参数
		mcp.WithNumber("x",
			mcp.Required(),
			mcp.Description("第一个操作数"),
		),
		mcp.WithNumber("y",
			mcp.Required(),
			mcp.Description("第二个操作数"),
		),
	)

	s.AddTool(calculatorTool, calculatorHandler)

	// 工具3:get_current_time - 获取当前时间
	// 演示无参数工具和时间处理
	timeTool := mcp.NewTool("get_current_time",
		mcp.WithDescription("获取当前系统时间"),
		// 可选参数:时区
		mcp.WithString("timezone",
			mcp.Description("时区,如 Asia/Shanghai,默认为本地时区"),
		),
	)

	s.AddTool(timeTool, timeHandler)

	// 工具4:string_utils - 字符串处理工具
	// 演示复杂的字符串操作
	stringTool := mcp.NewTool("string_utils",
		mcp.WithDescription("字符串处理工具,支持多种操作"),
		mcp.WithString("action",
			mcp.Required(),
			mcp.Description("操作类型"),
			mcp.Enum("uppercase", "lowercase", "reverse", "length", "trim"),
		),
		mcp.WithString("text",
			mcp.Required(),
			mcp.Description("要处理的文本"),
		),
	)

	s.AddTool(stringTool, stringUtilsHandler)
}

// helloHandler 处理 hello_world 工具的调用
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// 获取必填的 name 参数
	// RequireString 会自动验证参数是否存在且为字符串类型
	name, err := request.RequireString("name")
	if err != nil {
		// 返回错误结果,客户端会收到错误信息
		return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
	}

	// 构造问候语
	greeting := fmt.Sprintf("你好,%s!欢迎使用 Go MCP Server!", name)

	// 返回文本结果
	return mcp.NewToolResultText(greeting), nil
}

// calculatorHandler 处理 calculator 工具的调用
func calculatorHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// 获取操作类型
	operation, err := request.RequireString("operation")
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
	}

	// 获取操作数
	x, err := request.RequireFloat("x")
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("参数 x 错误: %v", err)), nil
	}

	y, err := request.RequireFloat("y")
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("参数 y 错误: %v", err)), nil
	}

	// 执行计算
	var result float64
	var symbol string

	switch operation {
	case "add":
		result = x + y
		symbol = "+"
	case "subtract":
		result = x - y
		symbol = "-"
	case "multiply":
		result = x * y
		symbol = "×"
	case "divide":
		// 除法需要检查除数是否为零
		if y == 0 {
			return mcp.NewToolResultError("错误:除数不能为零"), nil
		}
		result = x / y
		symbol = "÷"
	default:
		return mcp.NewToolResultError(fmt.Sprintf("不支持的操作: %s", operation)), nil
	}

	// 返回计算结果
	resultText := fmt.Sprintf("%.2f %s %.2f = %.2f", x, symbol, y, result)
	return mcp.NewToolResultText(resultText), nil
}

// timeHandler 处理 get_current_time 工具的调用
func timeHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// 获取可选的时区参数
	timezone := request.GetString("timezone", "")

	var t time.Time
	if timezone != "" {
		// 尝试加载指定时区
		loc, err := time.LoadLocation(timezone)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("无效的时区: %s", timezone)), nil
		}
		t = time.Now().In(loc)
	} else {
		// 使用本地时区
		t = time.Now()
	}

	// 格式化时间输出
	result := fmt.Sprintf("当前时间: %s\n时区: %s",
		t.Format(time.DateTime),
		t.Location().String(),
	)

	return mcp.NewToolResultText(result), nil
}

// stringUtilsHandler 处理 string_utils 工具的调用
func stringUtilsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// 获取操作类型和文本
	action, err := request.RequireString("action")
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
	}

	text, err := request.RequireString("text")
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
	}

	// 执行字符串操作
	var result string
	switch action {
	case "uppercase":
		result = strings.ToUpper(text)
	case "lowercase":
		result = strings.ToLower(text)
	case "reverse":
		// 反转字符串(支持 Unicode)
		runes := []rune(text)
		for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
			runes[i], runes[j] = runes[j], runes[i]
		}
		result = string(runes)
	case "length":
		// 返回字符数(不是字节数)
		result = fmt.Sprintf("字符串长度: %d 个字符", len([]rune(text)))
	case "trim":
		result = strings.TrimSpace(text)
	default:
		return mcp.NewToolResultError(fmt.Sprintf("不支持的操作: %s", action)), nil
	}

	return mcp.NewToolResultText(result), nil
}

// Resources 注册函数
// registerResources 注册所有资源
// Resources 用于向 LLM 暴露数据,类似于 REST API 的 GET 端点
func registerResources(s *server.MCPServer) {
	// 资源1:server_info - 服务器信息(静态资源)
	// 静态资源使用固定的 URI
	serverInfoResource := mcp.NewResource(
		"server://info",                                           // 资源 URI
		"服务器信息",                                              // 资源名称
		mcp.WithResourceDescription("获取 MCP Server 的基本信息"), // 资源描述
		mcp.WithMIMEType("application/json"),                      // MIME 类型
	)

	s.AddResource(serverInfoResource, serverInfoHandler)

	// 资源2:config - 配置信息(静态资源)
	configResource := mcp.NewResource(
		"config://app",
		"应用配置",
		mcp.WithResourceDescription("获取应用的配置信息"),
		mcp.WithMIMEType("application/json"),
	)

	s.AddResource(configResource, configHandler)

	// 资源3:file - 文件内容(动态资源模板)
	// 动态资源使用 URI 模板,可以根据参数返回不同内容
	fileTemplate := mcp.NewResourceTemplate(
		"file://{path}", // URI 模板,{path} 是变量
		"文件内容",
		mcp.WithTemplateDescription("读取指定路径的文件内容"),
		mcp.WithTemplateMIMEType("text/plain"),
	)

	s.AddResourceTemplate(fileTemplate, fileHandler)
}

// serverInfoHandler 处理 server_info 资源的读取
func serverInfoHandler(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
	// 构造服务器信息 JSON
	info := `{
    "name": "Go MCP Demo Server",
    "version": "1.0.0",
    "go_version": "1.24",
    "sdk": "mcp-go v0.44.0",
    "capabilities": ["tools", "resources", "prompts"],
    "author": "jiayq"
}`

	// 返回资源内容
	return []mcp.ResourceContents{
		mcp.TextResourceContents{
			URI:  request.Params.URI,
			Text: info,
		},
	}, nil
}

// configHandler 处理 config 资源的读取
func configHandler(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
	// 模拟配置信息
	config := `{
    "debug": false,
    "log_level": "info",
    "max_connections": 100,
    "timeout_seconds": 30,
    "features": {
        "tools_enabled": true,
        "resources_enabled": true,
        "prompts_enabled": true
    }
}`

	return []mcp.ResourceContents{
		mcp.TextResourceContents{
			URI:  request.Params.URI,
			Text: config,
		},
	}, nil
}

// fileHandler 处理文件资源的读取
func fileHandler(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
	// 从 URI 中提取文件路径
	// 注意:实际应用中需要做安全检查,防止路径遍历攻击
	uri := request.Params.URI
	path := strings.TrimPrefix(uri, "file://")

	// 这里只是演示,返回模拟内容
	// 实际应用中应该读取真实文件
	content := fmt.Sprintf("这是文件 %s 的模拟内容\n\n实际应用中,这里会返回真实的文件内容。", path)

	return []mcp.ResourceContents{
		mcp.TextResourceContents{
			URI:  uri,
			Text: content,
		},
	}, nil
}

// Prompts 注册函数
// registerPrompts 注册所有提示词模板
// Prompts 是可复用的交互模板,帮助 LLM 更好地理解用户意图
func registerPrompts(s *server.MCPServer) {
	// 提示词1:code_review - 代码审查模板
	codeReviewPrompt := mcp.NewPrompt("code_review",
		mcp.WithPromptDescription("代码审查提示词模板,帮助进行代码评审"),
		mcp.WithArgument("code",
			mcp.ArgumentDescription("要审查的代码"),
			mcp.RequiredArgument(),
		),
		mcp.WithArgument("language",
			mcp.ArgumentDescription("编程语言,如 go、python、java"),
			mcp.RequiredArgument(),
		),
	)

	s.AddPrompt(codeReviewPrompt, codeReviewHandler)

	// 提示词2:explain_error - 错误解释模板
	explainErrorPrompt := mcp.NewPrompt("explain_error",
		mcp.WithPromptDescription("解释错误信息,提供解决方案"),
		mcp.WithArgument("error_message",
			mcp.ArgumentDescription("错误信息"),
			mcp.RequiredArgument(),
		),
		mcp.WithArgument("context",
			mcp.ArgumentDescription("错误发生的上下文(可选)"),
		),
	)

	s.AddPrompt(explainErrorPrompt, explainErrorHandler)

	// 提示词3:generate_test - 生成测试用例模板
	generateTestPrompt := mcp.NewPrompt("generate_test",
		mcp.WithPromptDescription("为指定函数生成单元测试"),
		mcp.WithArgument("function_code",
			mcp.ArgumentDescription("要测试的函数代码"),
			mcp.RequiredArgument(),
		),
		mcp.WithArgument("test_framework",
			mcp.ArgumentDescription("测试框架,如 testing、testify"),
		),
	)

	s.AddPrompt(generateTestPrompt, generateTestHandler)
}

// codeReviewHandler 处理代码审查提示词
func codeReviewHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	// 获取参数
	code := request.Params.Arguments["code"]
	language := request.Params.Arguments["language"]

	// 构造提示词消息
	promptText := fmt.Sprintf(`请对以下 %s 代码进行审查,从以下几个方面给出建议:

1. **代码质量**:是否符合最佳实践,是否有代码异味
2. **性能**:是否存在性能问题,是否有优化空间
3. **安全性**:是否存在安全漏洞
4. **可读性**:命名是否清晰,注释是否充分
5. **可维护性**:代码结构是否合理,是否易于扩展

代码如下:

%s`, language, code)

	return &mcp.GetPromptResult{
		Description: "代码审查提示词",
		Messages: []mcp.PromptMessage{
			{
				Role: mcp.RoleUser,
				Content: mcp.TextContent{
					Type: "text",
					Text: promptText,
				},
			},
		},
	}, nil
}

// explainErrorHandler 处理错误解释提示词
func explainErrorHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	errorMessage := request.Params.Arguments["error_message"]
	context := request.Params.Arguments["context"]

	var promptText string
	if context != "" {
		promptText = fmt.Sprintf(`请解释以下错误信息,并提供可能的解决方案:

错误信息:
%s

发生上下文:
%s

请从以下几个方面回答:
1. 这个错误是什么意思?
2. 可能的原因有哪些?
3. 如何解决这个问题?
4. 如何避免再次发生?`, errorMessage, context)
	} else {
		promptText = fmt.Sprintf(`请解释以下错误信息,并提供可能的解决方案:

错误信息:
%s

请从以下几个方面回答:
1. 这个错误是什么意思?
2. 可能的原因有哪些?
3. 如何解决这个问题?`, errorMessage)
	}

	return &mcp.GetPromptResult{
		Description: "错误解释提示词",
		Messages: []mcp.PromptMessage{
			{
				Role: mcp.RoleUser,
				Content: mcp.TextContent{
					Type: "text",
					Text: promptText,
				},
			},
		},
	}, nil
}

// generateTestHandler 处理生成测试提示词
func generateTestHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
	functionCode := request.Params.Arguments["function_code"]
	testFramework := request.Params.Arguments["test_framework"]

	if testFramework == "" {
		testFramework = "testing" // 默认使用标准库
	}

	promptText := fmt.Sprintf(`请为以下 Go 函数生成完整的单元测试代码:

函数代码:
%s

要求:
1. 使用 %s 框架
2. 覆盖正常情况和边界情况
3. 包含表驱动测试(Table-Driven Tests)
4. 添加清晰的测试用例注释
5. 测试函数命名遵循 Go 规范`, functionCode, testFramework)

	return &mcp.GetPromptResult{
		Description: "生成测试用例提示词",
		Messages: []mcp.PromptMessage{
			{
				Role: mcp.RoleUser,
				Content: mcp.TextContent{
					Type: "text",
					Text: promptText,
				},
			},
		},
	}, nil
}

3.3 代码说明

上面的代码实现了一个功能完整的 MCP Server,包含以下能力:

Tools(工具)

工具名 功能 参数
hello_world 向指定的人打招呼 name(必填)
calculator 四则运算计算器 operationxy(必填)
get_current_time 获取当前时间 timezone(可选)
string_utils 字符串处理 actiontext(必填)

Resources(资源)

资源 URI 功能
server://info 获取服务器信息
config://app 获取应用配置
file://{path} 读取指定文件(动态资源)

Prompts(提示词)

提示词名 功能
code_review 代码审查模板
explain_error 错误解释模板
generate_test 生成测试用例模板

3.4 编译运行

# 编译
go build -o mcp-server main.go

# 测试运行(会等待 stdin 输入)
./mcp-server

image-20260106185216564

4. 传输方式详解

MCP 协议支持三种传输方式:stdio、SSE(Server-Sent Events)、Streamable HTTP。前面的示例使用的是 stdio 方式,这一节详细介绍另外两种传输方式。

4.1 三种传输方式对比

先来看一张对比图,了解三种传输方式的特点:

Streamable HTTP 传输

HTTP /mcp

Client

MCP Server

SSE 传输

POST /message

SSE /sse

Client

MCP Server

stdio 传输

stdin/stdout

Client

MCP Server

特性 stdio SSE Streamable HTTP
通信方式 标准输入输出 服务端推送 + POST 请求 HTTP 双向流
适用场景 本地 CLI 工具、IDE 插件 Web 应用、需要实时推送 通用 HTTP 服务、微服务
部署复杂度 低(无需网络) 中(需要 HTTP 服务器) 中(需要 HTTP 服务器)
网络穿透 不支持 支持 支持
多客户端 不支持 支持 支持
会话管理 自动(进程级别) 需要配置 可配置有状态/无状态

4.2 SSE 传输方式

SSE(Server-Sent Events)是一种基于 HTTP 的单向推送技术。在 MCP 中,SSE 用于服务端向客户端推送消息,而客户端通过 POST 请求向服务端发送消息。

4.2.1 SSE Server 配置选项

mcp-go SDK 提供了丰富的 SSE 配置选项:

配置选项 说明 默认值
WithSSEEndpoint(endpoint) 设置 SSE 端点路径 /sse
WithMessageEndpoint(endpoint) 设置消息端点路径 /message
WithBaseURL(url) 设置服务器基础 URL -
WithStaticBasePath(path) 设置静态基础路径 -
WithKeepAlive(bool) 启用/禁用 Keep-Alive false
WithKeepAliveInterval(duration) Keep-Alive 间隔时间 -
WithHTTPServer(srv) 使用自定义 HTTP 服务器 -
4.2.2 SSE Server 完整示例

下面是一个完整的 SSE Server 实现:

package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

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

func main() {
	// 第一步:创建 MCP Server 实例(与 stdio 方式相同)
	mcpServer := server.NewMCPServer(
		"Go MCP SSE Server",                         // 服务器名称
		"1.0.0",                                     // 版本号
		server.WithToolCapabilities(true),           // 启用 Tools 能力
		server.WithResourceCapabilities(true, true), // 启用 Resources 能力
		server.WithPromptCapabilities(true),         // 启用 Prompts 能力
		server.WithRecovery(),                       // 启用 panic 恢复
	)

	// 第二步:注册工具(这里只注册一个简单的工具作为演示)
	helloTool := mcp.NewTool("hello_world",
		mcp.WithDescription("向指定的人打招呼"),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("要打招呼的人的名字"),
		),
	)

	mcpServer.AddTool(helloTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// 获取 name 参数
		name, err := request.RequireString("name")
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
		}
		// 返回问候语
		return mcp.NewToolResultText(fmt.Sprintf("你好,%s!这是来自 SSE Server 的问候!", name)), nil
	})

	host := "0.0.0.0"
	port := 10001
	// 第三步:创建 SSE Server
	// NewSSEServer 参数说明:
	// - mcpServer: 上面创建的 MCP Server 实例
	// - opts: SSE 配置选项
	sseServer := server.NewSSEServer(mcpServer,
		// 设置 SSE 端点路径,客户端通过此路径接收服务端推送的消息
		server.WithSSEEndpoint("/sse"),
		// 设置消息端点路径,客户端通过此路径发送请求
		server.WithMessageEndpoint("/message"),
		// 设置基础 URL,用于生成完整的端点 URL
		server.WithBaseURL(fmt.Sprintf("http://%s:%d", host, port)),
		// 启用 Keep-Alive,保持连接活跃
		server.WithKeepAlive(true),
		// 设置 Keep-Alive 间隔为 30 秒
		server.WithKeepAliveInterval(30*time.Second),
	)

	// 第四步:启动 SSE Server
	// 监听地址
	fmt.Printf("SSE Server 启动中,监听地址: http://%s:%d\n", host, port)
	fmt.Printf("SSE 端点: http://%s:%d/sse\n", host, port)
	fmt.Printf("消息端点: http://%s:%d/message\n", host, port)

	// 优雅关闭处理
	// 创建一个 channel 用于接收系统信号
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// 在 goroutine 中启动服务器
	go func() {
		// Start 方法会阻塞,直到服务器停止
		if err := sseServer.Start(fmt.Sprintf(":%d", port)); err != nil {
			fmt.Printf("服务器错误: %v\n", err)
		}
	}()

	// 等待关闭信号
	<-sigChan
	fmt.Println("\n收到关闭信号,正在优雅关闭...")

	// 创建带超时的 context
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// 优雅关闭服务器
	if err := sseServer.Shutdown(ctx); err != nil {
		fmt.Printf("关闭服务器错误: %v\n", err)
	}

	fmt.Println("服务器已关闭")
}

在服务端启动

image-20260106192510974

配置使用

image-20260106192538957

使用

image-20260106192602264

4.2.3 SSE Server 与自定义路由集成

如果你已经有一个 HTTP 服务器,可以将 SSE Server 集成到现有路由中:

package main

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

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

// SSE Server 与自定义路由集成示例
// 功能:演示如何将 SSE Server 集成到现有的 HTTP 服务中

func main() {
	// 创建 MCP Server
	mcpServer := server.NewMCPServer(
		"Go MCP SSE Server",
		"1.0.0",
		server.WithToolCapabilities(true),
	)

	// 注册一个简单的工具
	mcpServer.AddTool(
		mcp.NewTool("ping", mcp.WithDescription("返回 pong")),
		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
			return mcp.NewToolResultText("pong"), nil
		},
	)

	// 创建 SSE Server
	sseServer := server.NewSSEServer(mcpServer,
		server.WithSSEEndpoint("/sse"),
		server.WithMessageEndpoint("/message"),
		server.WithKeepAlive(true),
		server.WithKeepAliveInterval(30*time.Second),
	)

	// 创建自定义路由
	mux := http.NewServeMux()

	// 健康检查端点
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	// 挂载 SSE 端点
	// SSEHandler() 返回处理 SSE 连接的 http.Handler
	mux.Handle("/mcp/sse", sseServer.SSEHandler())

	// 挂载消息端点
	// MessageHandler() 返回处理客户端消息的 http.Handler
	mux.Handle("/mcp/message", sseServer.MessageHandler())

	// 其他业务端点
	mux.HandleFunc("/api/info", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"name":"MCP Demo","version":"1.0.0"}`))
	})

	// 启动 HTTP 服务器
	fmt.Println("服务器启动中,监听 :8080")
	fmt.Println("SSE 端点: http://localhost:8080/mcp/sse")
	fmt.Println("消息端点: http://localhost:8080/mcp/message")
	fmt.Println("健康检查: http://localhost:8080/health")

	if err := http.ListenAndServe(":8080", mux); err != nil {
		fmt.Printf("服务器错误: %v\n", err)
	}
}

4.3 Streamable HTTP 传输方式

Streamable HTTP 是 MCP 协议支持的另一种 HTTP 传输方式,它支持直接的 HTTP 请求/响应和 SSE 流式响应。相比 SSE,Streamable HTTP 更加灵活,支持有状态和无状态两种模式。

4.3.1 Streamable HTTP Server 配置选项
配置选项 说明 默认值
WithEndpointPath(path) 设置端点路径 /mcp
WithStateful(bool) 启用有状态会话管理 false
WithStateLess(bool) 启用无状态模式 true
WithSessionIdManager(manager) 自定义会话 ID 管理器 -
WithHeartbeatInterval(duration) 设置心跳间隔 -
WithDisableStreaming(bool) 禁用流式响应 false
WithHTTPContextFunc(fn) 设置上下文修改函数 -
WithTLSCert(cert, key) 设置 TLS 证书 -
4.3.2 Streamable HTTP Server 完整示例
package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

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

// MCP Streamable HTTP Server 完整示例
// 功能:演示如何使用 Streamable HTTP 传输方式启动 MCP Server
// 作者:
// Go版本:1.24
// SDK版本:mcp-go v0.44.0

func main() {
	// 第一步:创建 MCP Server 实例
	mcpServer := server.NewMCPServer(
		"Go MCP HTTP Server",  // 服务器名称
		"1.0.0",               // 版本号
		server.WithToolCapabilities(true),      // 启用 Tools 能力
		server.WithResourceCapabilities(true),  // 启用 Resources 能力
		server.WithPromptCapabilities(true),    // 启用 Prompts 能力
		server.WithRecovery(),                  // 启用 panic 恢复
	)

	// 第二步:注册工具
	// 工具1:获取服务器状态
	statusTool := mcp.NewTool("get_server_status",
		mcp.WithDescription("获取服务器运行状态"),
	)

	mcpServer.AddTool(statusTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// 返回服务器状态信息
		status := fmt.Sprintf(`{
    "status": "running",
    "uptime": "%s",
    "transport": "streamable_http",
    "timestamp": "%s"
}`, time.Since(startTime).String(), time.Now().Format(time.RFC3339))
		return mcp.NewToolResultText(status), nil
	})

	// 工具2:回显工具
	echoTool := mcp.NewTool("echo",
		mcp.WithDescription("回显输入的消息"),
		mcp.WithString("message",
			mcp.Required(),
			mcp.Description("要回显的消息"),
		),
	)

	mcpServer.AddTool(echoTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		message, err := request.RequireString("message")
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
		}
		return mcp.NewToolResultText(fmt.Sprintf("Echo: %s", message)), nil
	})

	// 第三步:创建 Streamable HTTP Server
	// NewStreamableHTTPServer 参数说明:
	// - mcpServer: 上面创建的 MCP Server 实例
	// - opts: HTTP 配置选项
	httpServer := server.NewStreamableHTTPServer(mcpServer,
		// 设置端点路径,所有 MCP 请求都通过此路径处理
		server.WithEndpointPath("/mcp"),
		// 启用有状态会话管理
		// 有状态模式下,服务器会跟踪每个客户端的会话
		server.WithStateful(true),
		// 设置心跳间隔,保持长连接活跃
		server.WithHeartbeatInterval(30*time.Second),
	)

	// 第四步:启动 HTTP Server
	addr := ":8080"
	fmt.Printf("Streamable HTTP Server 启动中,监听地址: %s\n", addr)
	fmt.Printf("MCP 端点: http://localhost%s/mcp\n", addr)

	// 优雅关闭处理
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// 在 goroutine 中启动服务器
	go func() {
		if err := httpServer.Start(addr); err != nil {
			fmt.Printf("服务器错误: %v\n", err)
		}
	}()

	// 等待关闭信号
	<-sigChan
	fmt.Println("\n收到关闭信号,正在优雅关闭...")

	// 优雅关闭
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := httpServer.Shutdown(ctx); err != nil {
		fmt.Printf("关闭服务器错误: %v\n", err)
	}

	fmt.Println("服务器已关闭")
}

// startTime 记录服务器启动时间
var startTime = time.Now()
4.3.3 无状态模式 vs 有状态模式

Streamable HTTP Server 支持两种会话管理模式:

无状态模式(Stateless)

  • 每个请求都是独立的,不保存会话信息
  • 适合简单的工具调用场景
  • 资源消耗低,易于水平扩展
// 无状态模式配置
httpServer := server.NewStreamableHTTPServer(mcpServer,
    server.WithEndpointPath("/mcp"),
    server.WithStateLess(true),  // 启用无状态模式
)

有状态模式(Stateful)

  • 服务器跟踪每个客户端的会话
  • 支持复杂的多轮交互
  • 适合需要上下文的场景
// 有状态模式配置
httpServer := server.NewStreamableHTTPServer(mcpServer,
    server.WithEndpointPath("/mcp"),
    server.WithStateful(true),  // 启用有状态模式
)
4.3.4 启用 HTTPS

生产环境中建议启用 HTTPS:

// 启用 HTTPS
httpServer := server.NewStreamableHTTPServer(mcpServer,
    server.WithEndpointPath("/mcp"),
    server.WithStateful(true),
    // 设置 TLS 证书和密钥文件路径
    server.WithTLSCert("/path/to/cert.pem", "/path/to/key.pem"),
)

// 启动 HTTPS 服务器
if err := httpServer.Start(":8443"); err != nil {
    fmt.Printf("服务器错误: %v\n", err)
}

4.4 MCP Client 连接不同传输方式

前面介绍了三种 Server 的实现,现在来看看 Client 如何连接这些不同传输方式的 Server。

4.4.1 连接 stdio Server
// 连接 stdio Server(已在前面介绍)
client, err := client.NewStdioMCPClient(
    "./mcp-server",  // 可执行文件路径
    []string{},      // 启动参数
)
4.4.2 连接 SSE Server
package main

import (
	"context"
	"fmt"
	"time"

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

// MCP SSE Client 示例
// 功能:演示如何连接 SSE 传输方式的 MCP Server

func main() {
	// 创建带超时的 context
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 创建 SSE Client
	// NewSSEMCPClient 参数说明:
	// - baseURL: SSE Server 的基础 URL
	// - opts: 客户端配置选项
	c, err := client.NewSSEMCPClient(
		"http://localhost:8080",  // SSE Server 地址
		// 可选:设置自定义 HTTP 客户端
		// client.WithHTTPClient(customHTTPClient),
	)
	if err != nil {
		fmt.Printf("创建客户端失败: %v\n", err)
		return
	}
	defer c.Close()

	// 初始化连接
	fmt.Println("正在连接 SSE Server...")

	initResult, err := c.Initialize(ctx, mcp.InitializeRequest{
		ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
		ClientInfo: mcp.Implementation{
			Name:    "Go SSE Client Demo",
			Version: "1.0.0",
		},
	})
	if err != nil {
		fmt.Printf("初始化失败: %v\n", err)
		return
	}

	fmt.Printf("连接成功!Server: %s v%s\n",
		initResult.ServerInfo.Name,
		initResult.ServerInfo.Version,
	)

	// 调用工具
	fmt.Println("\n调用 hello_world 工具:")
	result, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "hello_world",
			Arguments: map[string]interface{}{
				"name": "SSE Client",
			},
		},
	})
	if err != nil {
		fmt.Printf("调用失败: %v\n", err)
		return
	}

	// 打印结果
	for _, content := range result.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Printf("结果: %s\n", textContent.Text)
		}
	}
}
4.4.3 连接 Streamable HTTP Server
package main

import (
	"context"
	"fmt"
	"time"

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

// MCP Streamable HTTP Client 示例
// 功能:演示如何连接 Streamable HTTP 传输方式的 MCP Server

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 创建 Streamable HTTP Client
	// NewStreamableHttpClient 参数说明:
	// - endpointURL: HTTP Server 的端点 URL
	// - opts: 客户端配置选项
	c, err := client.NewStreamableHttpClient(
		"http://localhost:8080/mcp",  // HTTP Server 端点
		// 可选配置
		// client.WithHTTPClient(customHTTPClient),
	)
	if err != nil {
		fmt.Printf("创建客户端失败: %v\n", err)
		return
	}
	defer c.Close()

	// 初始化连接
	fmt.Println("正在连接 Streamable HTTP Server...")

	initResult, err := c.Initialize(ctx, mcp.InitializeRequest{
		ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
		ClientInfo: mcp.Implementation{
			Name:    "Go HTTP Client Demo",
			Version: "1.0.0",
		},
	})
	if err != nil {
		fmt.Printf("初始化失败: %v\n", err)
		return
	}

	fmt.Printf("连接成功!Server: %s v%s\n",
		initResult.ServerInfo.Name,
		initResult.ServerInfo.Version,
	)

	// 获取工具列表
	fmt.Println("\n可用工具列表:")
	toolsResult, err := c.ListTools(ctx, mcp.ListToolsRequest{})
	if err != nil {
		fmt.Printf("获取工具列表失败: %v\n", err)
	} else {
		for _, tool := range toolsResult.Tools {
			fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
		}
	}

	// 调用工具
	fmt.Println("\n调用 get_server_status 工具:")
	statusResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name:      "get_server_status",
			Arguments: map[string]interface{}{},
		},
	})
	if err != nil {
		fmt.Printf("调用失败: %v\n", err)
		return
	}

	for _, content := range statusResult.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Printf("结果:\n%s\n", textContent.Text)
		}
	}

	fmt.Println("\n调用 echo 工具:")
	echoResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "echo",
			Arguments: map[string]interface{}{
				"message": "Hello from HTTP Client!",
			},
		},
	})
	if err != nil {
		fmt.Printf("调用失败: %v\n", err)
		return
	}

	for _, content := range echoResult.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Printf("结果: %s\n", textContent.Text)
		}
	}
}

4.5 传输方式选择建议

说白了,选择哪种传输方式主要看你的使用场景:

场景 推荐传输方式 原因
本地 IDE 插件(如 CodeBuddy) stdio 简单直接,无需网络配置
Web 应用需要实时推送 SSE 支持服务端主动推送消息
微服务架构 Streamable HTTP 标准 HTTP 协议,易于集成
需要跨网络访问 SSE 或 HTTP 支持网络穿透
需要多客户端连接 SSE 或 HTTP 支持并发连接
需要有状态会话 Streamable HTTP 内置会话管理

5. CodeBuddy 集成

这一节介绍如何将我们开发的 MCP Server 集成到 CodeBuddy 中。

5.1 配置文件

CodeBuddy 通过配置文件来管理 MCP Server。配置文件位置:

操作系统 配置文件路径
macOS ~/Library/Application Support/CodeBuddy/mcp_settings.json
Windows %APPDATA%\CodeBuddy\mcp_settings.json
Linux ~/.config/CodeBuddy/mcp_settings.json

5.2 配置示例

在配置文件中添加我们的 MCP Server:

{
  "mcpServers": {
    "go-mcp-demo": {
      "command": "/path/to/mcp-server",
      "args": [],
      "env": {}
    }
  }
}

配置说明:

字段 说明
go-mcp-demo Server 的唯一标识,可自定义
command MCP Server 可执行文件的完整路径
args 启动参数(可选)
env 环境变量(可选)

image-20260106185007264

5.3 完整配置示例

如果你的 MCP Server 需要额外的配置,可以这样写:

{
  "mcpServers": {
    "go-mcp-demo": {
      "command": "/path"
    }
  }
}

5.4 验证集成

配置完成后,重启 CodeBuddy,然后:

  1. 打开 CodeBuddy 的 MCP 面板
  2. 查看是否显示 go-mcp-demo Server
  3. 检查 Server 状态是否为"已连接"
  4. 尝试调用工具,如让 AI 执行计算

image-20260106185027905

尝试使用

image-20260106185100825

6. Go 项目集成 MCP Client

除了作为 Server 被 AI 客户端调用,我们也可以在自己的 Go 项目中作为 Client 调用其他 MCP Server。

6.1 MCP Client 完整示例

package main

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"time"

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

// MCP Client 完整示例
// 功能:演示如何在 Go 项目中调用 MCP Server
// 作者:
// Go版本:1.24

func main() {
	// 第一步:创建 MCP Client
	// 这里演示通过 stdio 连接到本地 MCP Server
	// MCP Server 的可执行文件路径
	serverPath := "./mcp-server"

	// 创建 stdio 客户端
	// StdioMCPClient 会启动一个子进程运行 MCP Server,并通过 stdin/stdout 通信
	c, err := client.NewStdioMCPClient(
		serverPath, // MCP Server 可执行文件路径
		[]string{}, // 启动参数
	)
	if err != nil {
		fmt.Printf("创建客户端失败: %v\n", err)
		os.Exit(1)
	}
	defer c.Close()

	// 创建带超时的 context
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// 第二步:初始化连接
	fmt.Println("正在初始化 MCP 连接...")

	// Initialize 会发送 initialize 请求,获取 Server 的能力声明
	initResult, err := c.Initialize(ctx, mcp.InitializeRequest{
		ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
		ClientInfo: mcp.Implementation{
			Name:    "Go MCP Client Demo",
			Version: "1.0.0",
		},
		Capabilities: mcp.ClientCapabilities{},
	})
	if err != nil {
		fmt.Printf("初始化失败: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("连接成功!\n")
	fmt.Printf("Server: %s v%s\n", initResult.ServerInfo.Name, initResult.ServerInfo.Version)
	fmt.Printf("协议版本: %s\n\n", initResult.ProtocolVersion)

	// 第三步:获取可用工具列表
	fmt.Println("========== 可用工具列表 ==========")

	toolsResult, err := c.ListTools(ctx, mcp.ListToolsRequest{})
	if err != nil {
		fmt.Printf("获取工具列表失败: %v\n", err)
	} else {
		for _, tool := range toolsResult.Tools {
			fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
		}
	}
	fmt.Println()

	// 第四步:调用工具
	fmt.Println("========== 调用工具示例 ==========")

	// 示例1:调用 hello_world 工具
	fmt.Println("\n1. 调用 hello_world 工具:")
	helloResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "hello_world",
			Arguments: map[string]interface{}{
				"name": "CodeBuddy",
			},
		},
	})
	if err != nil {
		fmt.Printf("   调用失败: %v\n", err)
	} else {
		printToolResult(helloResult)
	}

	// 示例2:调用 calculator 工具
	fmt.Println("\n2. 调用 calculator 工具:")
	calcResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "calculator",
			Arguments: map[string]interface{}{
				"operation": "multiply",
				"x":         12.5,
				"y":         8.0,
			},
		},
	})
	if err != nil {
		fmt.Printf("   调用失败: %v\n", err)
	} else {
		printToolResult(calcResult)
	}

	// 示例3:调用 get_current_time 工具
	fmt.Println("\n3. 调用 get_current_time 工具:")
	timeResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "get_current_time",
			Arguments: map[string]interface{}{
				"timezone": "Asia/Shanghai",
			},
		},
	})
	if err != nil {
		fmt.Printf("   调用失败: %v\n", err)
	} else {
		printToolResult(timeResult)
	}

	// 示例4:调用 string_utils 工具
	fmt.Println("\n4. 调用 string_utils 工具:")
	stringResult, err := c.CallTool(ctx, mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name: "string_utils",
			Arguments: map[string]interface{}{
				"action": "reverse",
				"text":   "Hello MCP",
			},
		},
	})
	if err != nil {
		fmt.Printf("   调用失败: %v\n", err)
	} else {
		printToolResult(stringResult)
	}

	// 第五步:获取资源列表
	fmt.Println("\n========== 可用资源列表 ==========")

	resourcesResult, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
	if err != nil {
		fmt.Printf("获取资源列表失败: %v\n", err)
	} else {
		for _, resource := range resourcesResult.Resources {
			fmt.Printf("- %s: %s\n", resource.URI, resource.Name)
		}
	}

	// 第六步:读取资源
	fmt.Println("\n========== 读取资源示例 ==========")

	fmt.Println("\n1. 读取 server://info 资源:")
	readResult, err := c.ReadResource(ctx, mcp.ReadResourceRequest{
		Params: mcp.ReadResourceParams{
			URI: "server://info",
		},
	})
	if err != nil {
		fmt.Printf("   读取失败: %v\n", err)
	} else {
		for _, content := range readResult.Contents {
			if textContent, ok := content.(mcp.TextResourceContents); ok {
				fmt.Printf("   内容:\n%s\n", textContent.Text)
			}
		}
	}

	// 第七步:获取提示词列表
	fmt.Println("\n========== 可用提示词列表 ==========")

	promptsResult, err := c.ListPrompts(ctx, mcp.ListPromptsRequest{})
	if err != nil {
		fmt.Printf("获取提示词列表失败: %v\n", err)
	} else {
		for _, prompt := range promptsResult.Prompts {
			fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
		}
	}

	// 第八步:获取提示词内容
	fmt.Println("\n========== 获取提示词示例 ==========")

	fmt.Println("\n1. 获取 code_review 提示词:")
	promptResult, err := c.GetPrompt(ctx, mcp.GetPromptRequest{
		Params: mcp.GetPromptParams{
			Name: "code_review",
			Arguments: map[string]string{
				"code":     "func Add(a, b int) int { return a + b }",
				"language": "go",
			},
		},
	})
	if err != nil {
		fmt.Printf("   获取失败: %v\n", err)
	} else {
		fmt.Printf("   描述: %s\n", promptResult.Description)
		for _, msg := range promptResult.Messages {
			if textContent, ok := msg.Content.(mcp.TextContent); ok {
				// 只显示前200个字符
				text := textContent.Text
				if len(text) > 200 {
					text = text[:200] + "..."
				}
				fmt.Printf("   内容: %s\n", text)
			}
		}
	}

	fmt.Println("\n========== 演示完成 ==========")
}

// printToolResult 打印工具调用结果
func printToolResult(result *mcp.CallToolResult) {
	if result.IsError {
		fmt.Printf("   错误: %v\n", result.Content)
		return
	}

	for _, content := range result.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Printf("   结果: %s\n", textContent.Text)
		}
	}
}

6.2 运行 Client

# 确保 MCP Server 已编译
go build -o mcp-server main.go

# 编译并运行 Client
go build -o mcp-client client.go
./mcp-client

预期输出:

正在初始化 MCP 连接...
连接成功!
Server: Go MCP Demo Server v1.0.0
协议版本: 2024-11-05

========== 可用工具列表 ==========
- hello_world: 向指定的人打招呼,返回问候语
- calculator: 执行基本的四则运算
- get_current_time: 获取当前系统时间
- string_utils: 字符串处理工具,支持多种操作

========== 调用工具示例 ==========

1. 调用 hello_world 工具:
   结果: 你好,CodeBuddy!欢迎使用 Go MCP Server!

2. 调用 calculator 工具:
   结果: 12.50 × 8.00 = 100.00

3. 调用 get_current_time 工具:
   结果: 当前时间: 2025-01-06 15:30:45
时区: Asia/Shanghai

4. 调用 string_utils 工具:
   结果: PCM olleH

========== 演示完成 ==========

7. 最佳实践

7.1 错误处理

// 推荐:使用 NewToolResultError 返回错误
if err != nil {
    return mcp.NewToolResultError(fmt.Sprintf("操作失败: %v", err)), nil
}

// 不推荐:直接返回 error(会导致 Server 异常)
if err != nil {
    return nil, err  // 避免这样做
}

7.2 参数验证

// 推荐:使用 SDK 提供的方法获取参数
name, err := request.RequireString("name")
if err != nil {
    return mcp.NewToolResultError(fmt.Sprintf("参数错误: %v", err)), nil
}

// 可选参数使用 GetString,提供默认值
timezone := request.GetString("timezone", "Asia/Shanghai")

7.3 日志输出

// MCP Server 使用 stdio 通信,日志必须输出到 stderr
fmt.Fprintln(os.Stderr, "这是日志信息")

// 不要输出到 stdout,会干扰 JSON-RPC 通信
// fmt.Println("这样会出问题")  // 错误!

7.4 超时处理

// 长时间操作应该检查 context 是否已取消
func longRunningHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    select {
    case <-ctx.Done():
        return mcp.NewToolResultError("操作超时"), nil
    default:
        // 继续执行
    }
    
    // ... 执行操作
}

8. 总结

这篇文章介绍了如何使用 Go 语言开发 MCP Server 和 Client,重点补充了三种传输方式的详细介绍。说实话,MCP 协议本身并不复杂,但是它解决了一个很实际的问题——让 AI 应用能够以标准化的方式调用外部工具和数据。

通过 mcp-go SDK,我们可以很方便地:

  1. 开发 MCP Server,提供 Tools、Resources、Prompts 三种能力
  2. 选择合适的传输方式:stdio 适合本地集成,SSE 适合实时推送,HTTP 适合微服务
  3. 将 Server 集成到 CodeBuddy 等 AI 客户端
  4. 在 Go 项目中作为 Client 调用其他 MCP Server

在实际开发中,我觉得最重要的是想清楚两件事:

  • 提供什么能力:Tools 适合执行操作,Resources 适合暴露数据,Prompts 适合定义交互模板
  • 选择什么传输方式:本地用 stdio,需要网络访问用 SSE 或 HTTP

选对了能力类型和传输方式,开发起来就会顺畅很多。

参考资料

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐