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}