类型强制与验证中间件

为mcp-go工具处理器提供类型强制转换和运行时验证的中间件,适用于需要处理LLM输入的场景。

  • by aleksadvaisly
  • Go
  • 本地部署
  • 更新于
在 GitHub 查看 aleksadvaisly/mcp-go-wrapper

使用说明

MCP Go包装器

强制+验证中间件 mcp走 工具操作员。

mcp go v0.43+处理模式生成(mcp.WithInputSchema[T]())和类型化参数绑定(request.BindArguments())本地。这个包装器位于mcp-go和您的处理程序之间,用于添加LLM在实践中需要的两件事:

  1. 批量类型强制 --LLM定期发送 "20" 而不是 20。在mcp-go绑定字符串参数之前,包装器会将字符串参数强制转换为结构中声明的类型。
  2. go游乐场/验证器运行时验证 -- validate:"required,min=3,email" 在绑定后,在处理程序运行之前检查标签。

它在Raw mcp go上添加了什么

关注 原始mcp go 带包装
模式生成 mcp.WithInputSchema[T]() 相同(mcp代表参加)
参数绑定 request.BindArguments(&args) 相同(mcp代表参加)
字符串到类型强制 无-- "20" 未能绑定到 int 绑定前自动
运行时验证 无-编写自己的检查 validate 结构标签
格式化错误 原始错误 人类可读的验证消息

安装

go get github.com/aleksadvaisly/mcp-go-wrapper

快速开始

定义参数结构体 json 和 validate 标签:

type GreetArgs struct {
    Name   string `json:"name"   validate:"required,min=1"`
    Format string `json:"format" validate:"omitempty,oneof=formal casual"`
}

三种连接方式:

1.便利性API

在一次调用中生成模式+强制+验证。最适合大多数情况。

mcpwrapper.Register[GreetArgs](w, "greet", "Greet someone",
    func(ctx context.Context, req mcp.CallToolRequest, args GreetArgs) (*mcp.CallToolResult, error) {
        return mcp.NewToolResultText("Hello " + args.Name), nil
    },
)

Register 电话 mcp.WithInputSchema[T]() 对于模式,然后用 TypedHandler[T] 用于强制和验证。

2.直接mcp-go集成(中间件模式)

使用 TypedHandler 直接与mcp go合作 AddTool。当您想要完全控制工具选项时很有用。

validate := validator.New()

mcpServer.AddTool(
    mcp.NewTool("greet",
        mcp.WithDescription("Greet someone"),
        mcp.WithInputSchema[GreetArgs](),
    ),
    mcpwrapper.TypedHandler[GreetArgs](validate, handler),
)

TypedHandler 返回a server.ToolHandlerFunc 它强制、绑定、验证,然后调用您的类型化处理程序。

3.结构化输出

退货 structuredContent 通过 mcp.NewToolResultStructuredOnly。当MCP客户端期望机器可读的结果时使用。

type CalcArgs struct {
    A  int    `json:"a"         validate:"required"`
    B  int    `json:"b"         validate:"required"`
    Op string `json:"operation" validate:"required,oneof=add subtract"`
}

type CalcResult struct {
    Result float64 `json:"result"`
}

mcpServer.AddTool(
    mcp.NewTool("calculate",
        mcp.WithDescription("Basic arithmetic"),
        mcp.WithInputSchema[CalcArgs](),
    ),
    mcpwrapper.StructuredHandler[CalcArgs, CalcResult](validate,
        func(ctx context.Context, req mcp.CallToolRequest, args CalcArgs) (CalcResult, error) {
            switch args.Op {
            case "add":
                return CalcResult{Result: float64(args.A + args.B)}, nil
            case "subtract":
                return CalcResult{Result: float64(args.A - args.B)}, nil
            default:
                return CalcResult{}, fmt.Errorf("unsupported operation: %s", args.Op)
            }
        },
    ),
)

验证标签

运行时验证使用 去操场/验证器.标签上 validate 字段标签。

标签 描述 示例
required 字段不能为零值 validate:"required"
min=<n> 最小长度/值 validate:"min=3"
max=<n> 最大长度/值 validate:"max=50"
email 有效的电子邮件格式 validate:"email"
url 有效的URL格式 validate:"url"
oneof=<vals> 值必须是列表中的一个 validate:"oneof=red blue green"
gte=<n> 大于或等于 validate:"gte=0"
lte=<n> 小于或等于 validate:"lte=100"
omitempty 如果为空,则跳过验证 validate:"omitempty,email"

用逗号组合标签:

Age int `json:"age" validate:"required,gte=0,lte=120"`

结构标签

包装器使用两种标签类型:

  • json --字段名称映射。标准Go JSON标签。由mcp-go's使用 BindArguments 以及包装器的强制逻辑。
  • validate --运行时验证规则。绑定后由go游乐场/验证器处理。

模式生成(jsonschema 标签、描述、枚举、最小/最大约束)由mcp-go处理 WithInputSchema[T](),使用 invopop/jsonschema 引擎盖下。

架构修补: omitempty 从中删除字段 required

invopop/jsonschema 将所有结构体字段标记为 required 默认情况下。这对于可选字段是错误的——MCP客户端将拒绝缺少这些字段的调用。使用时,包装器会自动修复此问题 Register[T] 或 RegisterCobra[T].

字段结束的规则 required:

场景 In required? 为什么
validate:"required,min=1" 是 明确要求
validate:"omitempty,gte=1" 否 显式省略
validate:"omitempty,required" 是的 required 赢得青睐 omitempty
validate:"email" (无住宿) 是 无住宿=需要住宿
validate:"required_if=Mode adv" 是的 required_if 不是 required (完全匹配)
没有 validate 标签全部 是 只有omitempty从必填项中删除
json:"field,omitempty" 没有 invopop/jsonschema 也尊重json格式

例子:

type SearchArgs struct {
    Query  string `json:"query"  validate:"required,min=1"`           // -> required
    Limit  int    `json:"limit"  validate:"omitempty,gte=1"`          // -> NOT required
    Offset int    `json:"offset" validate:"omitempty"`                 // -> NOT required
    Format string `json:"format" validate:"email"`                     // -> required (no omitempty)
    Mode   string `json:"mode"   validate:"required_if=Format json"`  // -> required (required_if != required)
}

结果模式 required: ["query", "format", "mode"].

当所有字段都是可选的时,架构包含 required: [] (空数组,不为null或缺失)。这是MCP协议所要求的。

此修补程序仅适用于使用 Register[T] 或 RegisterCobra[T]。如果您使用 TypedHandler 直接与 mcp.NewTool,您自己管理模式。

结合模式和验证标签的示例:

type CreateUserArgs struct {
    Email string `json:"email"    jsonschema:"description=User email"  validate:"required,email"`
    Age   int    `json:"age"      jsonschema:"minimum=0,maximum=120"   validate:"required,gte=0,lte=120"`
    Role  string `json:"role"     jsonschema:"enum=admin,enum=user"    validate:"required,oneof=admin user"`
}

这里 jsonschema mcp-go读取工具模式的标签; validate 标签在调用时由包装器读取。

Cobra集成

RegisterCobra 从中提取工具名称 cmd.Use 以及来自 cmd.Short (回到 cmd.Long).

greetCmd := &cobra.Command{
    Use:   "greet",
    Short: "Greet someone by name",
}

mcpwrapper.RegisterCobra[GreetArgs](w, greetCmd,
    func(ctx context.Context, req mcp.CallToolRequest, args GreetArgs) (*mcp.CallToolResult, error) {
        return mcp.NewToolResultText("Hello " + args.Name), nil
    },
)

整合模式: serve 命令

在将MCP支持添加到现有CLI应用程序时,创建一个新的 serve 子命令,而不是修改主应用程序:

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "Start MCP server",
    Run: func(cmd *cobra.Command, args []string) {
        log.SetOutput(os.Stderr)

        mcpServer := server.NewMCPServer(
            "my-app", "1.0.0",
            server.WithInstructions("Describe what your server does."),
        )
        w := mcpwrapper.New(mcpServer)

        mcpwrapper.RegisterCobra[MyArgs](w, myCmd, myHandler)

        if err := server.ServeStdio(mcpServer); err != nil {
            log.Fatal(err)
        }
    },
}

这可以在添加MCP功能的同时使CLI正常工作:

  • ./my-app command --作为常规CLI运行
  • ./my-app serve --启动MCP服务器以进行AI集成

关键:标准输出与标准错误

MCP协议使用stdio(stdin/stdout)进行JSON-RPC通信。任何非协议输出到stdout都会破坏连接。

做 --对所有日志记录和调试输出使用stderr:

log.SetOutput(os.Stderr)
fmt.Fprintln(os.Stderr, "message")

不要 --将任何内容写入stdout:

fmt.Println("message")     // BREAKS PROTOCOL
log.Println("message")     // BREAKS PROTOCOL (default log writes to stderr, but verify)
fmt.Printf("debug: %v", x) // BREAKS PROTOCOL

MCP客户端在stdout上只需要有效的JSON-RPC。如果您混合使用日志行:

Starting server...
{"jsonrpc":"2.0","id":1,"method":"tools/list"}

客户端解析失败,连接中断。

关键:服务器。使用说明()

始终添加 server.WithInstructions() 创建MCP服务器时:

mcpServer := server.NewMCPServer(
    "my-app", "1.0.0",
    server.WithInstructions("Describe your server's purpose and capabilities."),
)

没有指令,人工智能代理将无法理解服务器的用途或何时使用其工具。指令出现在MCP中 initialize 响应,以及客户端如何发现服务器的功能。

建筑

+-------------------+
|  MCP Client       |  (Claude, Cursor, etc.)
|  (JSON-RPC)       |
+--------+----------+
         |
         v
+-------------------+
|  mcp-go           |  Schema, transport, binding
|  (protocol layer) |
+--------+----------+
         |
         v
+-------------------+
|  mcp-go-wrapper   |  <- Coercion + validation middleware
|  (this library)   |
+--------+----------+
         |
         v
+-------------------+
|  Your Handler     |  Receives typed, validated args
+-------------------+

包装器是一个中间件层。它不会替换或包装mcp-go服务器,而是包装单个工具处理程序函数。

错误处理

验证错误

作为工具错误(非Go错误)返回,以便MCP客户端看到它们:

{
  "content": [{"type": "text", "text": "validation failed: Name: is required; Format: must be one of: formal casual"}],
  "isError": true
}

强制错误

{
  "content": [{"type": "text", "text": "coercion failed: field age: strconv.ParseInt: parsing \"abc\": invalid syntax"}],
  "isError": true
}

处理程序错误

对于 StructuredHandler,处理程序错误被包装为工具错误:

{
  "content": [{"type": "text", "text": "handler error: division by zero"}],
  "isError": true
}

对于 TypedHandler,错误处理由您的处理程序决定,因为它返回 (*mcp.CallToolResult, error) 直接。

API 参考

包装器

func New(mcpServer *server.MCPServer) *Wrapper
func NewWithValidator(mcpServer *server.MCPServer, v *validator.Validate) *Wrapper

New 使用默认验证器创建包装器。 NewWithValidator 允许您通过预配置 *validator.Validate (例如注册了自定义验证功能)。

中间件功能

func TypedHandler[T any](v *validator.Validate, handler mcp.TypedToolHandlerFunc[T]) server.ToolHandlerFunc
func StructuredHandler[TArgs any, TResult any](v *validator.Validate, handler mcp.StructuredToolHandlerFunc[TArgs, TResult]) server.ToolHandlerFunc

两者均返回a server.ToolHandlerFunc 它执行强制->绑定->验证->处理程序。

便利功能

func Register[T any](w *Wrapper, name, description string, handler mcp.TypedToolHandlerFunc[T])
func RegisterCobra[T any](w *Wrapper, cmd *cobra.Command, handler mcp.TypedToolHandlerFunc[T]) error

Register 电话 mcp.NewTool 随着 WithInputSchema[T]() 并包裹处理器。 RegisterCobra 执行相同的操作,但从Cobra命令派生名称和描述。

对于AI代理

阅读此README后,您应该能够将MCP支持自主集成到Go CLI应用程序中。

步骤:

  1. 分析目标CLI --识别命令、它们的参数和业务逻辑
  2. 创建参数结构 --定义类型化结构 json, jsonschema,以及 validate 每个命令的标签
  3. 实现处理程序 --写 mcp.TypedToolHandlerFunc[T] 调用现有命令逻辑的函数
  4. 添加一个 serve 子命令 --创建一个新的Cobra命令来启动MCP服务器(不要修改主应用程序入口点)
  5. 注册工具 --使用 mcpwrapper.Register[T]() 或 mcpwrapper.RegisterCobra[T]() 公开命令
  6. 设置服务器 --用以下方式初始化 server.WithInstructions(),配置stdio传输,设置 log.SetOutput(os.Stderr)

示例 serve 命令:

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "Start MCP server",
    Run: func(cmd *cobra.Command, cliArgs []string) {
        log.SetOutput(os.Stderr)

        mcpServer := server.NewMCPServer(
            "my-app", "1.0.0",
            server.WithInstructions("Describe your server here."),
        )
        w := mcpwrapper.New(mcpServer)

        mcpwrapper.Register[SearchArgs](w, "search", "Search documents", searchHandler)
        mcpwrapper.Register[CreateArgs](w, "create", "Create a document", createHandler)

        if err := server.ServeStdio(mcpServer); err != nil {
            log.Fatal(err)
        }
    },
}

经办人签名:

func searchHandler(ctx context.Context, req mcp.CallToolRequest, args SearchArgs) (*mcp.CallToolResult, error) {
    results := doSearch(args.Query, args.Limit)
    return mcp.NewToolResultText(formatResults(results)), nil
}

依赖项

许可证

麻省理工学院

贡献

欢迎捐款。请确保:

  • 测试通过(go test ./...)
  • 代码已格式化(go fmt ./...)
  • 示例仍然有效

学分

建立在优秀之上 mcp走 Mark3 Labs的图书馆。