随着大语言模型(LLM)与开发工具的深度融合,Model Control Protocol (MCP)协议正在成为AI与软件工具交互的重要桥梁。MCP允许LLM以结构化的方式调用工具,使AI能够执行具体的操作而不仅仅是生成文本。
本文将详细介绍如何使用Golang和mcp-go这个SDK来开发MCP服务器,并以我的开源项目mcp-k8s为具体案例,讲解如何构建一个与Kubernetes集群交互的MCP服务器。
MCP协议简介
MCP (Model Control Protocol)是一种协议,允许LLM与外部工具进行结构化交互。通过MCP,LLM可以:
- 获取可用工具及其参数列表
- 调用这些工具执行操作
- 获取操作结果并基于结果继续交互
这使得LLM能够"控制"外部系统,执行从查询数据库到操作Kubernetes资源等各种任务。
mcp-go SDK概述
mcp-go是一个用Golang实现的MCP协议SDK,它提供了构建MCP服务器所需的核心组件:
- 工具注册和管理
- 协议消息处理
- 多种传输方式支持(stdio、HTTP SSE等)
使用mcp-go,我们可以快速构建自己的MCP服务器,而无需关心底层协议细节。
开发MCP服务器的基本步骤
使用mcp-go开发MCP服务器通常包括以下步骤:
- 设计并定义工具(Tools)
- 实现工具的具体功能
- 创建服务器并注册工具
- 配置并启动服务器
下面我们将详细介绍每一步。
1. 设计并定义工具
在MCP中,工具(Tool)是LLM可以调用的功能单元。每个工具需要定义:
- 名称(Name):工具的标识符
- 描述(Description):工具的功能描述
- 参数(Parameters):工具接受的参数及其类型
- 处理函数(Handler):实现工具功能的函数
以mcp-k8s中的get_api_resources
工具为例:
go
// 定义工具
var getAPIResourcesTool = server.Tool{
Name: "get_api_resources",
Description: "获取集群中所有支持的API资源类型",
Parameters: nil, // 此工具不需要参数
Handler: handleGetAPIResources,
}
// 实现处理函数
func handleGetAPIResources(ctx context.Context, rawParams json.RawMessage) (interface{}, error) {
// 具体实现...
}
2. 实现工具的具体功能
工具的Handler函数实现具体的业务逻辑。在mcp-k8s中,这些函数主要与Kubernetes API交互。
例如,获取API资源列表的实现:
go
func handleGetAPIResources(ctx context.Context, rawParams json.RawMessage) (interface{}, error) {
// 创建k8s discovery客户端
discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeConfig)
if err != nil {
return nil, fmt.Errorf("创建discovery客户端失败: %w", err)
}
// 获取服务器上所有API组
apiGroups, err := discoveryClient.ServerGroups()
if err != nil {
return nil, fmt.Errorf("获取API组失败: %w", err)
}
// 处理结果并返回
result := processAPIGroups(apiGroups)
return result, nil
}
3. 创建服务器并注册工具
使用mcp-go创建MCP服务器并注册工具:
go
func main() {
// 创建MCP服务器
s, err := server.NewServer(
server.WithLogger(log.Default()),
)
if err != nil {
log.Fatalf("创建服务器失败: %v", err)
}
// 注册工具
s.RegisterTool(getAPIResourcesTool)
s.RegisterTool(getResourceTool)
s.RegisterTool(listResourcesTool)
// ... 注册更多工具
// 启动服务器
if err := s.Start(); err != nil {
log.Fatalf("启动服务器失败: %v", err)
}
}
4. 配置并启动服务器
mcp-go支持多种传输方式,最常用的是stdio(标准输入/输出)和SSE(Server-Sent Events)。
go
// stdio模式(默认)
s, err := server.NewServer(
server.WithLogger(log.Default()),
server.WithTransport(server.TransportStdio),
)
// SSE模式
s, err := server.NewServer(
server.WithLogger(log.Default()),
server.WithTransport(server.TransportSSE),
server.WithHost("localhost"),
server.WithPort(8080),
)
mcp-k8s: 一个完整的实践案例
mcp-k8s是一个使用mcp-go开发的、用于与Kubernetes集群交互的MCP服务器。它提供了以下工具:
-
资源类型查询工具
get_api_resources
: 获取集群中所有支持的API资源类型
-
资源操作工具
get_resource
: 获取特定资源的详细信息list_resources
: 列出某种资源类型的所有实例create_resource
: 创建新资源(可配置禁用)update_resource
: 更新现有资源(可配置禁用)delete_resource
: 删除资源(可配置禁用)
以下是mcp-k8s的核心实现:
目录结构
csharp
mcp-k8s/
├── cmd/
│ └── server/
│ └── main.go # 主程序入口
├── internal/
│ ├── config/ # 配置处理
│ ├── k8s/ # Kubernetes客户端
│ └── tools/ # MCP工具实现
├── go.mod
└── go.sum
主程序入口
go
// cmd/server/main.go
func main() {
// 解析命令行参数
kubeconfig := flag.String("kubeconfig", "", "Kubernetes配置文件路径")
enableCreate := flag.Bool("enable-create", false, "启用资源创建操作")
enableUpdate := flag.Bool("enable-update", false, "启用资源更新操作")
enableDelete := flag.Bool("enable-delete", false, "启用资源删除操作")
transport := flag.String("transport", "stdio", "传输方式: stdio或sse")
host := flag.String("host", "localhost", "SSE模式的主机名")
port := flag.Int("port", 8080, "SSE模式的端口")
flag.Parse()
// 初始化Kubernetes客户端
if err := k8s.InitClient(*kubeconfig); err != nil {
log.Fatalf("初始化K8s客户端失败: %v", err)
}
// 创建MCP服务器
serverOpts := []server.Option{
server.WithLogger(log.Default()),
}
// 配置传输方式
if *transport == "sse" {
serverOpts = append(serverOpts,
server.WithTransport(server.TransportSSE),
server.WithHost(*host),
server.WithPort(*port),
)
} else {
serverOpts = append(serverOpts, server.WithTransport(server.TransportStdio))
}
s, err := server.NewServer(serverOpts...)
if err != nil {
log.Fatalf("创建服务器失败: %v", err)
}
// 注册工具
tools.RegisterTools(s, &tools.Options{
EnableCreate: *enableCreate,
EnableUpdate: *enableUpdate,
EnableDelete: *enableDelete,
})
// 启动服务器
if err := s.Start(); err != nil {
log.Fatalf("启动服务器失败: %v", err)
}
}
工具实现示例
以get_resource
工具为例:
go
// internal/tools/get_resource.go
var GetResourceTool = server.Tool{
Name: "get_resource",
Description: "获取特定资源的详细信息",
Parameters: []server.Parameter{
{
Name: "apiVersion",
Description: "资源的API版本",
Type: "string",
Required: true,
},
{
Name: "kind",
Description: "资源类型",
Type: "string",
Required: true,
},
{
Name: "name",
Description: "资源名称",
Type: "string",
Required: true,
},
{
Name: "namespace",
Description: "资源所在的命名空间(如适用)",
Type: "string",
Required: false,
},
},
Handler: handleGetResource,
}
type GetResourceParams struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
func handleGetResource(ctx context.Context, rawParams json.RawMessage) (interface{}, error) {
var params GetResourceParams
if err := json.Unmarshal(rawParams, ¶ms); err != nil {
return nil, fmt.Errorf("解析参数失败: %w", err)
}
// 获取动态客户端
dynamicClient, err := k8s.GetDynamicClient()
if err != nil {
return nil, err
}
// 获取资源GVR (Group Version Resource)
gvr, namespaced, err := k8s.GetGVR(params.APIVersion, params.Kind)
if err != nil {
return nil, err
}
// 确定是命名空间级别还是集群级别的资源
var object *unstructured.Unstructured
if namespaced {
// 如果是命名空间级别资源但未提供命名空间,使用默认命名空间
namespace := params.Namespace
if namespace == "" {
namespace = "default"
}
object, err = dynamicClient.Resource(gvr).Namespace(namespace).Get(ctx, params.Name, metav1.GetOptions{})
} else {
object, err = dynamicClient.Resource(gvr).Get(ctx, params.Name, metav1.GetOptions{})
}
if err != nil {
return nil, fmt.Errorf("获取资源失败: %w", err)
}
return object.Object, nil
}
注册所有工具
go
// internal/tools/tools.go
type Options struct {
EnableCreate bool
EnableUpdate bool
EnableDelete bool
}
func RegisterTools(s *server.Server, opts *Options) {
// 注册查询工具(始终启用)
s.RegisterTool(GetAPIResourcesTool)
s.RegisterTool(GetResourceTool)
s.RegisterTool(ListResourcesTool)
// 根据配置注册写操作工具
if opts.EnableCreate {
s.RegisterTool(CreateResourceTool)
}
if opts.EnableUpdate {
s.RegisterTool(UpdateResourceTool)
}
if opts.EnableDelete {
s.RegisterTool(DeleteResourceTool)
}
}
使用方法
mcp-k8s支持两种通信模式:
1. Stdio模式(默认)
在stdio模式下,mcp-k8s通过标准输入/输出流与客户端通信。
json
// MCP客户端配置
{
"mcpServers": {
"mcp-k8s": {
"command": "/path/to/mcp-k8s",
"args": [
"-kubeconfig", "/path/to/kubeconfig",
"-enable-create",
"-enable-delete",
"-enable-update"
]
}
}
}
2. SSE模式
在SSE模式下,mcp-k8s暴露HTTP端点供MCP客户端连接。
bash
# 运行SSE模式服务
./bin/mcp-k8s -kubeconfig=/path/to/kubeconfig -transport=sse -port=8080 -host=localhost -enable-create -enable-delete -enable-update
json
// MCP客户端配置
{
"mcpServers": {
"mcp-k8s": {
"url": "http://localhost:8080/sse",
"args": []
}
}
}
MCP服务开发的最佳实践
基于开发mcp-k8s的经验,我总结了以下开发MCP服务的最佳实践:
- 工具命名与分类:使用清晰、一致的命名方式,按功能分类工具
- 参数设计:参数名称要直观,提供清晰的描述,明确标记必选参数
- 安全控制:对写操作提供独立的开关控制,避免不必要的权限风险
- 错误处理:返回详细的错误信息,帮助LLM理解失败原因
- 状态管理:MCP服务应保持无状态,所有必要信息通过参数传递
- 文档完善:详细记录每个工具的功能、参数和使用示例
结论
使用Golang和mcp-go SDK开发MCP服务器是一个相对简单的过程。通过定义和实现工具,我们可以让LLM具备与各种系统交互的能力,从而大大扩展其应用场景。
mcp-k8s项目展示了如何构建一个功能完整的MCP服务器,让LLM能够查询、创建、更新和删除Kubernetes资源,为集群管理提供了新的交互方式。
希望本文能够帮助你理解MCP协议和使用Golang开发MCP服务器的基本方法。更多详情,欢迎访问mcp-k8s项目,或查看mark3labs/mcp-go的文档。
参考资料
AI&&MCP交流群
我建了一个"AI技术交流群",目的是为了方便大家交流AI相关的知识和共享资源,目前AI变化真的是太快了,大家关注公众号"AI技术小林",发送关键字"加群",拉你进去,;