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 _, mcp := range m.com.Config().MCP.Sorted() {
20 if state, ok := m.mcpStates[mcp.Name]; ok {
21 mcps = append(mcps, state)
22 }
23 }
24
25 title := t.Subtle.Render("MCPs")
26 if isSection {
27 title = common.Section(t, title, width)
28 }
29 list := t.Subtle.Render("None")
30 if len(mcps) > 0 {
31 list = mcpList(t, mcps, width, maxItems)
32 }
33
34 return lipgloss.NewStyle().Width(width).Render(fmt.Sprintf("%s\n\n%s", title, list))
35}
36
37// mcpCounts formats tool, prompt, and resource counts for display.
38func mcpCounts(t *styles.Styles, counts mcp.Counts) string {
39 var parts []string
40 if counts.Tools > 0 {
41 parts = append(parts, t.Subtle.Render(fmt.Sprintf("%d tools", counts.Tools)))
42 }
43 if counts.Prompts > 0 {
44 parts = append(parts, t.Subtle.Render(fmt.Sprintf("%d prompts", counts.Prompts)))
45 }
46 if counts.Resources > 0 {
47 parts = append(parts, t.Subtle.Render(fmt.Sprintf("%d resources", counts.Resources)))
48 }
49 return strings.Join(parts, " ")
50}
51
52// mcpList renders a list of MCP clients with their status and counts,
53// truncating to maxItems if needed.
54func mcpList(t *styles.Styles, mcps []mcp.ClientInfo, width, maxItems int) string {
55 if maxItems <= 0 {
56 return ""
57 }
58 var renderedMcps []string
59
60 for _, m := range mcps {
61 var icon string
62 title := m.Name
63 var description string
64 var extraContent string
65
66 switch m.State {
67 case mcp.StateStarting:
68 icon = t.ItemBusyIcon.String()
69 description = t.Subtle.Render("starting...")
70 case mcp.StateConnected:
71 icon = t.ItemOnlineIcon.String()
72 extraContent = mcpCounts(t, m.Counts)
73 case mcp.StateError:
74 icon = t.ItemErrorIcon.String()
75 description = t.Subtle.Render("error")
76 if m.Error != nil {
77 description = t.Subtle.Render(fmt.Sprintf("error: %s", m.Error.Error()))
78 }
79 case mcp.StateDisabled:
80 icon = t.ItemOfflineIcon.Foreground(t.Muted.GetBackground()).String()
81 description = t.Subtle.Render("disabled")
82 default:
83 icon = t.ItemOfflineIcon.String()
84 }
85
86 renderedMcps = append(renderedMcps, common.Status(t, common.StatusOpts{
87 Icon: icon,
88 Title: title,
89 Description: description,
90 ExtraContent: extraContent,
91 }, width))
92 }
93
94 if len(renderedMcps) > maxItems {
95 visibleItems := renderedMcps[:maxItems-1]
96 remaining := len(renderedMcps) - maxItems
97 visibleItems = append(visibleItems, t.Subtle.Render(fmt.Sprintf("β¦and %d more", remaining)))
98 return lipgloss.JoinVertical(lipgloss.Left, visibleItems...)
99 }
100 return lipgloss.JoinVertical(lipgloss.Left, renderedMcps...)
101}