1package mcp
2
3import (
4 "context"
5 "iter"
6 "log/slog"
7
8 "git.secluded.site/crush/internal/config"
9 "git.secluded.site/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}