1package mcp
2
3import (
4 "fmt"
5
6 "github.com/charmbracelet/lipgloss/v2"
7
8 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/llm/agent"
10 "github.com/charmbracelet/crush/internal/tui/components/core"
11 "github.com/charmbracelet/crush/internal/tui/styles"
12)
13
14// RenderOptions contains options for rendering MCP lists.
15type RenderOptions struct {
16 MaxWidth int
17 MaxItems int
18 ShowSection bool
19 SectionName string
20}
21
22// RenderMCPList renders a list of MCP status items with the given options.
23func RenderMCPList(cfg *config.Config, opts RenderOptions) []string {
24 t := styles.CurrentTheme()
25 mcpList := []string{}
26
27 if opts.ShowSection {
28 sectionName := opts.SectionName
29 if sectionName == "" {
30 sectionName = "MCPs"
31 }
32 section := t.S().Subtle.Render(sectionName)
33 mcpList = append(mcpList, section, "")
34 }
35
36 mcps := cfg.MCP.Sorted()
37 if len(mcps) == 0 {
38 mcpList = append(mcpList, t.S().Base.Foreground(t.Border).Render("None"))
39 return mcpList
40 }
41
42 // Get MCP states
43 mcpStates := agent.GetMCPStates()
44
45 // Determine how many items to show
46 maxItems := len(mcps)
47 if opts.MaxItems > 0 {
48 maxItems = min(opts.MaxItems, len(mcps))
49 }
50
51 for i, l := range mcps {
52 if i >= maxItems {
53 break
54 }
55
56 // Determine icon and color based on state
57 icon := t.ItemOfflineIcon
58 description := l.MCP.Command
59 extraContent := ""
60
61 if state, exists := mcpStates[l.Name]; exists {
62 switch state.State {
63 case agent.MCPStateDisabled:
64 description = t.S().Subtle.Render("disabled")
65 case agent.MCPStateStarting:
66 icon = t.ItemBusyIcon
67 description = t.S().Subtle.Render("starting...")
68 case agent.MCPStateConnected:
69 icon = t.ItemOnlineIcon
70 if state.ToolCount > 0 {
71 extraContent = t.S().Subtle.Render(fmt.Sprintf("%d tools", state.ToolCount))
72 }
73 case agent.MCPStateError:
74 icon = t.ItemErrorIcon
75 if state.Error != nil {
76 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
77 } else {
78 description = t.S().Subtle.Render("error")
79 }
80 }
81 } else if l.MCP.Disabled {
82 description = t.S().Subtle.Render("disabled")
83 }
84
85 mcpList = append(mcpList,
86 core.Status(
87 core.StatusOpts{
88 Icon: icon.String(),
89 Title: l.Name,
90 Description: description,
91 ExtraContent: extraContent,
92 },
93 opts.MaxWidth,
94 ),
95 )
96 }
97
98 return mcpList
99}
100
101// RenderMCPBlock renders a complete MCP block with optional truncation indicator.
102func RenderMCPBlock(cfg *config.Config, opts RenderOptions, showTruncationIndicator bool) string {
103 t := styles.CurrentTheme()
104 mcpList := RenderMCPList(cfg, opts)
105
106 // Add truncation indicator if needed
107 if showTruncationIndicator && opts.MaxItems > 0 {
108 mcps := cfg.MCP.Sorted()
109 if len(mcps) > opts.MaxItems {
110 remaining := len(mcps) - opts.MaxItems
111 if remaining == 1 {
112 mcpList = append(mcpList, t.S().Base.Foreground(t.FgMuted).Render("β¦"))
113 } else {
114 mcpList = append(mcpList,
115 t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("β¦and %d more", remaining)),
116 )
117 }
118 }
119 }
120
121 content := lipgloss.JoinVertical(lipgloss.Left, mcpList...)
122 if opts.MaxWidth > 0 {
123 return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
124 }
125 return content
126}