diff --git a/internal/llm/agent/mcp-tools.go b/internal/llm/agent/mcp-tools.go index e263152bd6394d1d9fb923bdd69d669564152ec6..743fbe989d6ebacf8187fde3d90a9b0534240d5e 100644 --- a/internal/llm/agent/mcp-tools.go +++ b/internal/llm/agent/mcp-tools.go @@ -69,8 +69,9 @@ type MCPEvent struct { // MCPCounts number of available tools, prompts, etc. type MCPCounts struct { - Tools int - Prompts int + Tools int + Prompts int + Resources int } // MCPClientInfo holds information about an MCP client's state @@ -84,14 +85,16 @@ type MCPClientInfo struct { } var ( - mcpToolsOnce sync.Once - mcpTools = csync.NewMap[string, tools.BaseTool]() - mcpClient2Tools = csync.NewMap[string, []tools.BaseTool]() - mcpClients = csync.NewMap[string, *mcp.ClientSession]() - mcpStates = csync.NewMap[string, MCPClientInfo]() - mcpBroker = pubsub.NewBroker[MCPEvent]() - mcpPrompts = csync.NewMap[string, *mcp.Prompt]() - mcpClient2Prompts = csync.NewMap[string, []*mcp.Prompt]() + mcpToolsOnce sync.Once + mcpTools = csync.NewMap[string, tools.BaseTool]() + mcpClient2Tools = csync.NewMap[string, []tools.BaseTool]() + mcpClients = csync.NewMap[string, *mcp.ClientSession]() + mcpStates = csync.NewMap[string, MCPClientInfo]() + mcpBroker = pubsub.NewBroker[MCPEvent]() + mcpPrompts = csync.NewMap[string, *mcp.Prompt]() + mcpClient2Prompts = csync.NewMap[string, []*mcp.Prompt]() + mcpResources = csync.NewMap[string, *mcp.Resource]() + mcpClient2Resources = csync.NewMap[string, []*mcp.Resource]() ) type McpTool struct { @@ -327,12 +330,22 @@ func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *con return } + resources, err := getResources(ctx, c) + if err != nil { + slog.Error("error listing resources", "error", err) + updateMCPState(name, MCPStateError, err, nil, MCPCounts{}) + c.Close() + return + } + updateMcpTools(name, tools) updateMcpPrompts(name, prompts) + updateMcpResources(name, resources) mcpClients.Set(name, c) counts := MCPCounts{ - Tools: len(tools), - Prompts: len(prompts), + Tools: len(tools), + Prompts: len(prompts), + Resources: len(resources), } updateMCPState(name, MCPStateConnected, nil, c, counts) }(name, m) @@ -496,6 +509,32 @@ func updateMcpPrompts(mcpName string, prompts []*mcp.Prompt) { } } +func getResources(ctx context.Context, c *mcp.ClientSession) ([]*mcp.Resource, error) { + if c.InitializeResult().Capabilities.Resources == nil { + return nil, nil + } + result, err := c.ListResources(ctx, &mcp.ListResourcesParams{}) + if err != nil { + return nil, err + } + return result.Resources, nil +} + +// updateMcpResources updates the global mcpResources and mcpClient2Resources maps. +func updateMcpResources(mcpName string, resources []*mcp.Resource) { + if len(resources) == 0 { + mcpClient2Resources.Del(mcpName) + } else { + mcpClient2Resources.Set(mcpName, resources) + } + for clientName, resources := range mcpClient2Resources.Seq2() { + for _, p := range resources { + key := clientName + ":" + p.Name + mcpResources.Set(key, p) + } + } +} + // GetMCPPrompts returns all available MCP prompts. func GetMCPPrompts() map[string]*mcp.Prompt { return maps.Collect(mcpPrompts.Seq2()) diff --git a/internal/tui/components/mcp/mcp.go b/internal/tui/components/mcp/mcp.go index 91afa66c152f8fab807291aa5ebc601a9c60fcba..5147c4cd1e2997e9f5b654bb42b3b5a2e4fc1861 100644 --- a/internal/tui/components/mcp/mcp.go +++ b/internal/tui/components/mcp/mcp.go @@ -74,6 +74,9 @@ func RenderMCPList(opts RenderOptions) []string { if count := state.Counts.Prompts; count > 0 { extraContent = append(extraContent, t.S().Subtle.Render(fmt.Sprintf("%d prompts", count))) } + if count := state.Counts.Resources; count > 0 { + extraContent = append(extraContent, t.S().Subtle.Render(fmt.Sprintf("%d resources", count))) + } case agent.MCPStateError: icon = t.ItemErrorIcon if state.Error != nil {