tool.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // Package kit 是 fwh-agent-mcp 的工具框架:工具定义、参数辅助、统一结果与中间件。
  2. //
  3. // 设计见 docs/ai-assistant-agentserver/agent-mcp.md 第 3 节。工具不感知调用者:
  4. // 没有 userId / sessionId 参数,也不记录调用人。
  5. package kit
  6. import (
  7. "context"
  8. "encoding/json"
  9. "fmt"
  10. "strings"
  11. "github.com/mark3labs/mcp-go/mcp"
  12. "github.com/mark3labs/mcp-go/server"
  13. )
  14. // Meta 工具的静态信息,供注册表与约束测试使用。
  15. type Meta struct {
  16. Name string
  17. Title string
  18. Description string
  19. ReadOnly bool
  20. }
  21. // IsExport 导出类工具(*_export)使用更长的超时并以 CSV 资源返回。
  22. func (m Meta) IsExport() bool {
  23. return strings.HasSuffix(m.Name, "_export")
  24. }
  25. // Registrar 可注册到 MCPServer 的工具。
  26. type Registrar interface {
  27. Meta() Meta
  28. Tool() mcp.Tool
  29. Register(s *server.MCPServer)
  30. }
  31. // Def 一个类型化工具的定义。T 为参数结构体,schema 由 mcp.WithInputSchema[T] 生成:
  32. // `jsonschema:"..."` 标签即字段描述,没有 omitempty 的字段为必填;枚举与取值范围写进描述并在 Handler 中校验。
  33. type Def[T any] struct {
  34. Name string // <领域>_<动作>,小写下划线,≤ 40 字符
  35. Title string // 中文短标题(审批卡片与后台显示)
  36. Description string // 用途、适用场景、返回字段与单位
  37. ReadOnly bool
  38. Handler func(ctx context.Context, args T) (*mcp.CallToolResult, error)
  39. // Properties 可选:补充生成的 schema 中各字段的约束(枚举、取值范围、数组长度等),
  40. // key 为字段名,value 合并进该字段的 schema(同名键覆盖)。服务端按补充后的 schema 校验参数。
  41. Properties map[string]map[string]any
  42. }
  43. func (d Def[T]) Meta() Meta {
  44. return Meta{Name: d.Name, Title: d.Title, Description: d.Description, ReadOnly: d.ReadOnly}
  45. }
  46. func (d Def[T]) Tool() mcp.Tool {
  47. t := mcp.NewTool(d.Name,
  48. mcp.WithDescription(d.Description),
  49. mcp.WithInputSchema[T](),
  50. mcp.WithTitleAnnotation(d.Title),
  51. mcp.WithReadOnlyHintAnnotation(d.ReadOnly),
  52. mcp.WithDestructiveHintAnnotation(false),
  53. mcp.WithIdempotentHintAnnotation(d.ReadOnly),
  54. mcp.WithOpenWorldHintAnnotation(false),
  55. )
  56. if len(d.Properties) > 0 {
  57. t.RawInputSchema = patchProperties(t.RawInputSchema, d.Name, d.Properties)
  58. }
  59. return t
  60. }
  61. // patchProperties 把 patch 合并进 schema.properties 中的对应字段;字段不存在视为定义错误,直接 panic(启动即暴露)。
  62. func patchProperties(raw json.RawMessage, tool string, patch map[string]map[string]any) json.RawMessage {
  63. var schema map[string]any
  64. if err := json.Unmarshal(raw, &schema); err != nil {
  65. panic(fmt.Sprintf("%s: 解析 inputSchema 失败: %v", tool, err))
  66. }
  67. props, _ := schema["properties"].(map[string]any)
  68. for name, extra := range patch {
  69. field, ok := props[name].(map[string]any)
  70. if !ok {
  71. panic(fmt.Sprintf("%s: inputSchema 中没有字段 %s", tool, name))
  72. }
  73. for k, v := range extra {
  74. field[k] = v
  75. }
  76. }
  77. out, err := json.Marshal(schema)
  78. if err != nil {
  79. panic(fmt.Sprintf("%s: 序列化 inputSchema 失败: %v", tool, err))
  80. }
  81. return out
  82. }
  83. func (d Def[T]) Register(s *server.MCPServer) {
  84. s.AddTool(d.Tool(), mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, args T) (*mcp.CallToolResult, error) {
  85. return d.Handler(ctx, args)
  86. }))
  87. }
  88. // NoArgs 无参数工具的参数类型。
  89. type NoArgs struct{}