1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| package mcpserver
import ( "context" "fmt"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "github.com/pkg/errors" "github.com/samber/lo"
"{{{ .Package }}}/app" "{{{ .Package }}}/app/util" )
type ToolHandler func(ctx context.Context, as *app.State, req mcp.CallToolRequest, args util.ValueMap, logger util.Logger) (any, error)
type Tool struct { Name string `json:"name"` Description string `json:"description,omitzero"` Icon string `json:"icon,omitzero"` Args util.FieldDescs `json:"args,omitempty"` Fn ToolHandler `json:"-"` }
func (t *Tool) ToMCP() (mcp.Tool, error) { opts := []mcp.ToolOption{mcp.WithDescription(t.Description)} for _, x := range t.Args { argOpts := []mcp.PropertyOption{mcp.Description(x.Description)} if x.Default == "" { argOpts = append(argOpts, mcp.Required()) } else { argOpts = append(argOpts, mcp.DefaultString(x.Default)) } switch x.Type { case "string", "": opts = append(opts, mcp.WithString(x.Key, argOpts...)) case "float", "float64", "int", "int64", "number": opts = append(opts, mcp.WithNumber(x.Key, argOpts...)) case "bool", "boolean": opts = append(opts, mcp.WithBoolean(x.Key, argOpts...)) default: return mcp.Tool{}, errors.Errorf("unable to parse tool argument [%s] as type [%s]", x.Key, x.Type) } } return mcp.NewTool(t.Name, opts...), nil }
func (t *Tool) Handler(as *app.State, logger util.Logger) server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { args := util.ValueMapFrom(req.GetArguments()) if t.Fn == nil { return nil, errors.Errorf("no handler for tool [%s]", t.Name) } ret, err := t.Fn(ctx, as, req, args, logger) if err != nil { return mcp.NewToolResultErrorFromErr(fmt.Sprintf("errors running tool [%s] with arguments %s", t.Name, util.ToJSONCompact(args)), err), nil } return mcp.NewToolResultText(valToText(ret)), nil } }
func (t *Tool) IconSafe() string { return util.Choose(t.Icon == "", "cog", t.Icon) }
type Tools []*Tool
func (t Tools) Get(n string) *Tool { return lo.FindOrElse(t, nil, func(x *Tool) bool { return x.Name == n }) }
func (s *Server) AddTools(as *app.State, logger util.Logger, tools ...*Tool) error { for _, tl := range tools { s.Tools = append(s.Tools, tl) m, err := tl.ToMCP() if err != nil { return err } s.MCP.AddTool(m, tl.Handler(as, logger)) } return nil }
|