如何使用Golang开发MCP服务器:从mcp-go到mcp-k8s实践

随着大语言模型(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可以:

  1. 获取可用工具及其参数列表
  2. 调用这些工具执行操作
  3. 获取操作结果并基于结果继续交互

这使得LLM能够"控制"外部系统,执行从查询数据库到操作Kubernetes资源等各种任务。

mcp-go SDK概述

mcp-go是一个用Golang实现的MCP协议SDK,它提供了构建MCP服务器所需的核心组件:

  1. 工具注册和管理
  2. 协议消息处理
  3. 多种传输方式支持(stdio、HTTP SSE等)

使用mcp-go,我们可以快速构建自己的MCP服务器,而无需关心底层协议细节。

开发MCP服务器的基本步骤

使用mcp-go开发MCP服务器通常包括以下步骤:

  1. 设计并定义工具(Tools)
  2. 实现工具的具体功能
  3. 创建服务器并注册工具
  4. 配置并启动服务器

下面我们将详细介绍每一步。

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服务器。它提供了以下工具:

  1. 资源类型查询工具

    • get_api_resources: 获取集群中所有支持的API资源类型
  2. 资源操作工具

    • 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, &params); 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服务的最佳实践:

  1. 工具命名与分类:使用清晰、一致的命名方式,按功能分类工具
  2. 参数设计:参数名称要直观,提供清晰的描述,明确标记必选参数
  3. 安全控制:对写操作提供独立的开关控制,避免不必要的权限风险
  4. 错误处理:返回详细的错误信息,帮助LLM理解失败原因
  5. 状态管理:MCP服务应保持无状态,所有必要信息通过参数传递
  6. 文档完善:详细记录每个工具的功能、参数和使用示例

结论

使用Golang和mcp-go SDK开发MCP服务器是一个相对简单的过程。通过定义和实现工具,我们可以让LLM具备与各种系统交互的能力,从而大大扩展其应用场景。

mcp-k8s项目展示了如何构建一个功能完整的MCP服务器,让LLM能够查询、创建、更新和删除Kubernetes资源,为集群管理提供了新的交互方式。

希望本文能够帮助你理解MCP协议和使用Golang开发MCP服务器的基本方法。更多详情,欢迎访问mcp-k8s项目,或查看mark3labs/mcp-go的文档。

参考资料

  1. MCP协议规范
  2. mcp-go SDK
  3. mcp-k8s项目
  4. Kubernetes client-go文档

AI&&MCP交流群

我建了一个"AI技术交流群",目的是为了方便大家交流AI相关的知识和共享资源,目前AI变化真的是太快了,大家关注公众号"AI技术小林",发送关键字"加群",拉你进去,;

相关推荐
小信啊啊18 分钟前
Go语言切片slice
开发语言·后端·golang
Victor3562 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易2 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧2 小时前
Range循环和切片
前端·后端·学习·golang
WizLC2 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3562 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法2 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长3 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈4 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao4 小时前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang