1package model
2
3import (
4 "fmt"
5 "strings"
6
7 "charm.land/lipgloss/v2"
8 "github.com/charmbracelet/crush/internal/agent/tools/mcp"
9 "github.com/charmbracelet/crush/internal/ui/common"
10 "github.com/charmbracelet/crush/internal/ui/styles"
11)
12
13// mcpInfo renders the MCP status section showing active MCP clients and their
14// tool/prompt counts.
15func (m *UI) mcpInfo(width, maxItems int, isSection bool) string {
16 var mcps []mcp.ClientInfo
17 t := m.com.Styles
18
19 for _, state := range m.mcpStates {
20 mcps = append(mcps, state)
21 }
22
23 title := t.Subtle.Render("MCPs")
24 if isSection {
25 title = common.Section(t, title, width)
26 }
27 list := t.Subtle.Render("None")
28 if len(mcps) > 0 {
29 list = mcpList(t, mcps, width, maxItems)
30 }
31
32 return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
33}
34
35// mcpCounts formats tool and prompt counts for display.
36func mcpCounts(t *styles.Styles, counts mcp.Counts) string {
37 parts := []string{}
38 if counts.Tools > 0 {
39 parts = append(parts, t.Subtle.Render(fmt.Sprintf("%d tools", counts.Tools)))
40 }
41 if counts.Prompts > 0 {
42 parts = append(parts, t.Subtle.Render(fmt.Sprintf("%d prompts", counts.Prompts)))
43 }
44 return strings.Join(parts, " ")
45}
46
47// mcpList renders a list of MCP clients with their status and counts,
48// truncating to maxItems if needed.
49func mcpList(t *styles.Styles, mcps []mcp.ClientInfo, width, maxItems int) string {
50 var renderedMcps []string
51
52 for _, m := range mcps {
53 var icon string
54 title := m.Name
55 var description string
56 var extraContent string
57
58 switch m.State {
59 case mcp.StateStarting:
60 icon = t.ItemBusyIcon.String()
61 description = t.Subtle.Render("starting...")
62 case mcp.StateConnected:
63 icon = t.ItemOnlineIcon.String()
64 extraContent = mcpCounts(t, m.Counts)
65 case mcp.StateError:
66 icon = t.ItemErrorIcon.String()
67 description = t.Subtle.Render("error")
68 if m.Error != nil {
69 description = t.Subtle.Render(fmt.Sprintf("error: %s", m.Error.Error()))
70 }
71 case mcp.StateDisabled:
72 icon = t.ItemOfflineIcon.Foreground(t.Muted.GetBackground()).String()
73 description = t.Subtle.Render("disabled")
74 default:
75 icon = t.ItemOfflineIcon.String()
76 }
77
78 renderedMcps = append(renderedMcps, common.Status(t, common.StatusOpts{
79 Icon: icon,
80 Title: title,
81 Description: description,
82 ExtraContent: extraContent,
83 }, width))
84 }
85
86 if len(renderedMcps) > maxItems {
87 visibleItems := renderedMcps[:maxItems-1]
88 remaining := len(renderedMcps) - maxItems
89 visibleItems = append(visibleItems, t.Subtle.Render(fmt.Sprintf("β¦and %d more", remaining)))
90 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
91 }
92 return lipgloss.JoinVertical(lipgloss.Left, renderedMcps...)
93}