// Package kit 是 fwh-agent-mcp 的工具框架:工具定义、参数辅助、统一结果与中间件。 // // 设计见 docs/ai-assistant-agentserver/agent-mcp.md 第 3 节。工具不感知调用者: // 没有 userId / sessionId 参数,也不记录调用人。 package kit import ( "context" "encoding/json" "fmt" "strings" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Meta 工具的静态信息,供注册表与约束测试使用。 type Meta struct { Name string Title string Description string ReadOnly bool } // IsExport 导出类工具(*_export)使用更长的超时并以 CSV 资源返回。 func (m Meta) IsExport() bool { return strings.HasSuffix(m.Name, "_export") } // Registrar 可注册到 MCPServer 的工具。 type Registrar interface { Meta() Meta Tool() mcp.Tool Register(s *server.MCPServer) } // Def 一个类型化工具的定义。T 为参数结构体,schema 由 mcp.WithInputSchema[T] 生成: // `jsonschema:"..."` 标签即字段描述,没有 omitempty 的字段为必填;枚举与取值范围写进描述并在 Handler 中校验。 type Def[T any] struct { Name string // <领域>_<动作>,小写下划线,≤ 40 字符 Title string // 中文短标题(审批卡片与后台显示) Description string // 用途、适用场景、返回字段与单位 ReadOnly bool Handler func(ctx context.Context, args T) (*mcp.CallToolResult, error) // Properties 可选:补充生成的 schema 中各字段的约束(枚举、取值范围、数组长度等), // key 为字段名,value 合并进该字段的 schema(同名键覆盖)。服务端按补充后的 schema 校验参数。 Properties map[string]map[string]any } func (d Def[T]) Meta() Meta { return Meta{Name: d.Name, Title: d.Title, Description: d.Description, ReadOnly: d.ReadOnly} } func (d Def[T]) Tool() mcp.Tool { t := mcp.NewTool(d.Name, mcp.WithDescription(d.Description), mcp.WithInputSchema[T](), mcp.WithTitleAnnotation(d.Title), mcp.WithReadOnlyHintAnnotation(d.ReadOnly), mcp.WithDestructiveHintAnnotation(false), mcp.WithIdempotentHintAnnotation(d.ReadOnly), mcp.WithOpenWorldHintAnnotation(false), ) if len(d.Properties) > 0 { t.RawInputSchema = patchProperties(t.RawInputSchema, d.Name, d.Properties) } return t } // patchProperties 把 patch 合并进 schema.properties 中的对应字段;字段不存在视为定义错误,直接 panic(启动即暴露)。 func patchProperties(raw json.RawMessage, tool string, patch map[string]map[string]any) json.RawMessage { var schema map[string]any if err := json.Unmarshal(raw, &schema); err != nil { panic(fmt.Sprintf("%s: 解析 inputSchema 失败: %v", tool, err)) } props, _ := schema["properties"].(map[string]any) for name, extra := range patch { field, ok := props[name].(map[string]any) if !ok { panic(fmt.Sprintf("%s: inputSchema 中没有字段 %s", tool, name)) } for k, v := range extra { field[k] = v } } out, err := json.Marshal(schema) if err != nil { panic(fmt.Sprintf("%s: 序列化 inputSchema 失败: %v", tool, err)) } return out } func (d Def[T]) Register(s *server.MCPServer) { s.AddTool(d.Tool(), mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, args T) (*mcp.CallToolResult, error) { return d.Handler(ctx, args) })) } // NoArgs 无参数工具的参数类型。 type NoArgs struct{}