resources.go

 1package mcp
 2
 3import (
 4	"context"
 5	"iter"
 6	"log/slog"
 7
 8	"github.com/charmbracelet/crush/internal/csync"
 9	"github.com/modelcontextprotocol/go-sdk/mcp"
10)
11
12type (
13	Resource         = mcp.Resource
14	ResourceContents = mcp.ResourceContents
15)
16
17var allResources = csync.NewMap[string, []*Resource]()
18
19// Resources returns all available MCP resources.
20func Resources() iter.Seq2[string, []*Resource] {
21	return allResources.Seq2()
22}
23
24// ReadResource retrieves the content of an MCP resource with the given arguments.
25func ReadResource(ctx context.Context, clientName, uri string) ([]*ResourceContents, error) {
26	c, err := getOrRenewClient(ctx, clientName)
27	if err != nil {
28		return nil, err
29	}
30	result, err := c.ReadResource(ctx, &mcp.ReadResourceParams{
31		URI: uri,
32	})
33	if err != nil {
34		return nil, err
35	}
36	return result.Contents, nil
37}
38
39// RefreshResources gets the updated list of resources from the MCP and updates the
40// global state.
41func RefreshResources(ctx context.Context, name string) {
42	session, ok := sessions.Get(name)
43	if !ok {
44		slog.Warn("refresh resources: no session", "name", name)
45		return
46	}
47
48	resources, err := getResources(ctx, session)
49	if err != nil {
50		updateState(name, StateError, err, nil, Counts{})
51		return
52	}
53
54	updateResources(name, resources)
55
56	prev, _ := states.Get(name)
57	prev.Counts.Resources = len(resources)
58	updateState(name, StateConnected, nil, session, prev.Counts)
59}
60
61func getResources(ctx context.Context, c *mcp.ClientSession) ([]*Resource, error) {
62	if c.InitializeResult().Capabilities.Resources == nil {
63		return nil, nil
64	}
65	result, err := c.ListResources(ctx, &mcp.ListResourcesParams{})
66	if err != nil {
67		return nil, err
68	}
69	return result.Resources, nil
70}
71
72func updateResources(mcpName string, resources []*Resource) {
73	if len(resources) == 0 {
74		allResources.Del(mcpName)
75		return
76	}
77	allResources.Set(mcpName, resources)
78}