list_mcp_resources.go

  1package tools
  2
  3import (
  4	"context"
  5	_ "embed"
  6	"fmt"
  7	"sort"
  8	"strings"
  9
 10	"charm.land/fantasy"
 11	"git.secluded.site/crush/internal/agent/tools/mcp"
 12	"git.secluded.site/crush/internal/config"
 13	"git.secluded.site/crush/internal/filepathext"
 14	"git.secluded.site/crush/internal/permission"
 15)
 16
 17type ListMCPResourcesParams struct {
 18	MCPName string `json:"mcp_name" description:"The MCP server name"`
 19}
 20
 21type ListMCPResourcesPermissionsParams struct {
 22	MCPName string `json:"mcp_name"`
 23}
 24
 25const ListMCPResourcesToolName = "list_mcp_resources"
 26
 27//go:embed list_mcp_resources.md
 28var listMCPResourcesDescription []byte
 29
 30func NewListMCPResourcesTool(cfg *config.Config, permissions permission.Service) fantasy.AgentTool {
 31	return fantasy.NewParallelAgentTool(
 32		ListMCPResourcesToolName,
 33		string(listMCPResourcesDescription),
 34		func(ctx context.Context, params ListMCPResourcesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
 35			params.MCPName = strings.TrimSpace(params.MCPName)
 36			if params.MCPName == "" {
 37				return fantasy.NewTextErrorResponse("mcp_name parameter is required"), nil
 38			}
 39
 40			sessionID := GetSessionFromContext(ctx)
 41			if sessionID == "" {
 42				return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for listing MCP resources")
 43			}
 44
 45			relPath := filepathext.SmartJoin(cfg.WorkingDir(), params.MCPName)
 46			p, err := permissions.Request(ctx,
 47				permission.CreatePermissionRequest{
 48					SessionID:   sessionID,
 49					Path:        relPath,
 50					ToolCallID:  call.ID,
 51					ToolName:    ListMCPResourcesToolName,
 52					Action:      "list",
 53					Description: fmt.Sprintf("List MCP resources from %s", params.MCPName),
 54					Params:      ListMCPResourcesPermissionsParams(params),
 55				},
 56			)
 57			if err != nil {
 58				return fantasy.ToolResponse{}, err
 59			}
 60			if !p {
 61				return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
 62			}
 63
 64			resources, err := mcp.ListResources(ctx, cfg, params.MCPName)
 65			if err != nil {
 66				return fantasy.NewTextErrorResponse(err.Error()), nil
 67			}
 68			if len(resources) == 0 {
 69				return fantasy.NewTextResponse("No resources found"), nil
 70			}
 71
 72			lines := make([]string, 0, len(resources))
 73			for _, resource := range resources {
 74				if resource == nil {
 75					continue
 76				}
 77				title := resource.Title
 78				if title == "" {
 79					title = resource.Name
 80				}
 81				if title == "" {
 82					title = resource.URI
 83				}
 84				line := fmt.Sprintf("- %s", title)
 85				if resource.URI != "" {
 86					line = fmt.Sprintf("%s (%s)", line, resource.URI)
 87				}
 88				if resource.Description != "" {
 89					line = fmt.Sprintf("%s: %s", line, resource.Description)
 90				}
 91				if resource.MIMEType != "" {
 92					line = fmt.Sprintf("%s [mime: %s]", line, resource.MIMEType)
 93				}
 94				if resource.Size > 0 {
 95					line = fmt.Sprintf("%s [size: %d]", line, resource.Size)
 96				}
 97				lines = append(lines, line)
 98			}
 99
100			sort.Strings(lines)
101			return fantasy.NewTextResponse(strings.Join(lines, "\n")), nil
102		},
103	)
104}