1package tools
2
3import (
4 "context"
5 "fmt"
6
7 "charm.land/fantasy"
8 "git.secluded.site/crush/internal/agent/tools/mcp"
9 "git.secluded.site/crush/internal/permission"
10)
11
12// GetMCPTools gets all the currently available MCP tools.
13func GetMCPTools(permissions permission.Service, wd string) []*Tool {
14 var result []*Tool
15 for mcpName, tools := range mcp.Tools() {
16 for _, tool := range tools {
17 result = append(result, &Tool{
18 mcpName: mcpName,
19 tool: tool,
20 permissions: permissions,
21 workingDir: wd,
22 })
23 }
24 }
25 return result
26}
27
28// Tool is a tool from a MCP.
29type Tool struct {
30 mcpName string
31 tool *mcp.Tool
32 permissions permission.Service
33 workingDir string
34 providerOptions fantasy.ProviderOptions
35}
36
37func (m *Tool) SetProviderOptions(opts fantasy.ProviderOptions) {
38 m.providerOptions = opts
39}
40
41func (m *Tool) ProviderOptions() fantasy.ProviderOptions {
42 return m.providerOptions
43}
44
45func (m *Tool) Name() string {
46 return fmt.Sprintf("mcp_%s_%s", m.mcpName, m.tool.Name)
47}
48
49func (m *Tool) MCP() string {
50 return m.mcpName
51}
52
53func (m *Tool) MCPToolName() string {
54 return m.tool.Name
55}
56
57func (m *Tool) Info() fantasy.ToolInfo {
58 parameters := make(map[string]any)
59 required := make([]string, 0)
60
61 if input, ok := m.tool.InputSchema.(map[string]any); ok {
62 if props, ok := input["properties"].(map[string]any); ok {
63 parameters = props
64 }
65 if req, ok := input["required"].([]any); ok {
66 // Convert []any -> []string when elements are strings
67 for _, v := range req {
68 if s, ok := v.(string); ok {
69 required = append(required, s)
70 }
71 }
72 } else if reqStr, ok := input["required"].([]string); ok {
73 // Handle case where it's already []string
74 required = reqStr
75 }
76 }
77
78 return fantasy.ToolInfo{
79 Name: m.Name(),
80 Description: m.tool.Description,
81 Parameters: parameters,
82 Required: required,
83 }
84}
85
86func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolResponse, error) {
87 sessionID := GetSessionFromContext(ctx)
88 if sessionID == "" {
89 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
90 }
91 permissionDescription := fmt.Sprintf("execute %s with the following parameters:", m.Info().Name)
92 p := m.permissions.Request(
93 permission.CreatePermissionRequest{
94 SessionID: sessionID,
95 ToolCallID: params.ID,
96 Path: m.workingDir,
97 ToolName: m.Info().Name,
98 Action: "execute",
99 Description: permissionDescription,
100 Params: params.Input,
101 },
102 )
103 if !p {
104 return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
105 }
106
107 content, err := mcp.RunTool(ctx, m.mcpName, m.tool.Name, params.Input)
108 if err != nil {
109 return fantasy.NewTextErrorResponse(err.Error()), nil
110 }
111 return fantasy.NewTextResponse(content), nil
112}