| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- // 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{}
|