mcp-tools.go

  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}