mcp-tools.go

  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}