如何使用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技术小林",发送关键字"加群",拉你进去,;

相关推荐
程序媛学姐7 分钟前
SpringBoot Actuator健康检查:自定义HealthIndicator
java·spring boot·后端
程序媛学姐19 分钟前
SpringBoot Actuator指标收集:Micrometer与Prometheus集成
spring boot·后端·prometheus
欲儿29 分钟前
RabbitMQ原理及代码示例
java·spring boot·后端·rabbitmq
林 子30 分钟前
Spring Boot自动装配原理(源码详细剖析!)
spring boot·后端
编程轨迹32 分钟前
使用 Spring 和 Redis 创建处理敏感数据的服务
后端
未完结小说1 小时前
服务注册与发现(nacos)
后端
AI智能科技用户7946329781 小时前
okcc呼叫中心两个sip对接线路外呼任务怎么设置才能一个任务对应yigesip中继?
人工智能·后端
懒虫虫~1 小时前
Spring源码中关于抽象方法且是个空实现这样设计的思考
java·后端·spring
雷渊2 小时前
DDD的分层架构是怎么样的?
后端
会有猫2 小时前
阿里云OSS挂载到Linux
后端