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