resources.go

 1package mcp
 2
 3import (
 4	"context"
 5	"iter"
 6	"log/slog"
 7
 8	"github.com/charmbracelet/crush/internal/config"
 9	"github.com/charmbracelet/crush/internal/csync"
10	"github.com/modelcontextprotocol/go-sdk/mcp"
11)
12
13type Resource = mcp.Resource
14
15type ResourceContents = mcp.ResourceContents
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// ListResources returns the current resources for an MCP server.
25func ListResources(ctx context.Context, cfg *config.Config, name string) ([]*Resource, error) {
26	session, err := getOrRenewClient(ctx, cfg, name)
27	if err != nil {
28		return nil, err
29	}
30
31	resources, err := getResources(ctx, session)
32	if err != nil {
33		return nil, err
34	}
35
36	resourceCount := updateResources(name, resources)
37	prev, _ := states.Get(name)
38	prev.Counts.Resources = resourceCount
39	updateState(name, StateConnected, nil, session, prev.Counts)
40	return resources, nil
41}
42
43// ReadResource reads the contents of a resource from an MCP server.
44func ReadResource(ctx context.Context, cfg *config.Config, name, uri string) ([]*ResourceContents, error) {
45	session, err := getOrRenewClient(ctx, cfg, name)
46	if err != nil {
47		return nil, err
48	}
49	result, err := session.ReadResource(ctx, &mcp.ReadResourceParams{URI: uri})
50	if err != nil {
51		return nil, err
52	}
53	return result.Contents, nil
54}
55
56// RefreshResources gets the updated list of resources from the MCP and updates the
57// global state.
58func RefreshResources(ctx context.Context, name string) {
59	session, ok := sessions.Get(name)
60	if !ok {
61		slog.Warn("Refresh resources: no session", "name", name)
62		return
63	}
64
65	resources, err := getResources(ctx, session)
66	if err != nil {
67		updateState(name, StateError, err, nil, Counts{})
68		return
69	}
70
71	resourceCount := updateResources(name, resources)
72
73	prev, _ := states.Get(name)
74	prev.Counts.Resources = resourceCount
75	updateState(name, StateConnected, nil, session, prev.Counts)
76}
77
78func getResources(ctx context.Context, c *ClientSession) ([]*Resource, error) {
79	if c.InitializeResult().Capabilities.Resources == nil {
80		return nil, nil
81	}
82	result, err := c.ListResources(ctx, &mcp.ListResourcesParams{})
83	if err != nil {
84		return nil, err
85	}
86	return result.Resources, nil
87}
88
89func updateResources(name string, resources []*Resource) int {
90	if len(resources) == 0 {
91		allResources.Del(name)
92		return 0
93	}
94	allResources.Set(name, resources)
95	return len(resources)
96}