resource.go

 1package tools
 2
 3import (
 4	"context"
 5	_ "embed"
 6	"log/slog"
 7	"strings"
 8
 9	"charm.land/fantasy"
10	"github.com/charmbracelet/crush/internal/agent/tools/mcp"
11)
12
13type ReadMCPResourceParams struct {
14	Name string `json:"name" description:"MCP name"`
15	URI  string `json:"uri,omitempty" description:"Resource URI"`
16}
17
18type ListMCPResourceParams struct {
19	Name string `json:"name" description:"MCP name"`
20}
21
22const (
23	ReadMCPResourceToolName = "read_mcp_resource"
24	ListMCPResourceToolName = "list_mcp_resources"
25)
26
27//go:embed read_mcp_resource.md
28var readMCPResourceDescription []byte
29
30//go:embed list_mcp_resource.md
31var listMCPResourceDescription []byte
32
33func NewReadMCPResourceTool() fantasy.AgentTool {
34	return fantasy.NewAgentTool(
35		ReadMCPResourceToolName,
36		string(readMCPResourceDescription),
37		func(ctx context.Context, input ReadMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
38			resource, err := mcp.ReadResource(ctx, input.Name, input.URI)
39			if err != nil {
40				return fantasy.NewTextErrorResponse(err.Error()), nil
41			}
42			var sb strings.Builder
43			for _, part := range resource {
44				if !strings.HasPrefix(part.MIMEType, "text/") {
45					slog.Warn("ignoring resource of type", "type", part.MIMEType)
46					continue
47				}
48				sb.WriteString(part.Text)
49			}
50			return fantasy.NewTextResponse(sb.String()), nil
51		},
52	)
53}
54
55func NewListMCPResourceTool() fantasy.AgentTool {
56	return fantasy.NewAgentTool(
57		ListMCPResourceToolName,
58		string(listMCPResourceDescription),
59		func(ctx context.Context, input ListMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
60			for name, resources := range mcp.Resources() {
61				if name != input.Name {
62					continue
63				}
64				var sb strings.Builder
65				sb.WriteString("Resources available for " + input.Name + ":\n\n")
66				for _, res := range resources {
67					sb.WriteString("- " + res.URI + "\n")
68				}
69				return fantasy.NewTextResponse(sb.String()), nil
70			}
71			return fantasy.NewTextResponse("No resources found for " + input.Name), nil
72		},
73	)
74}