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(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 := config.Get().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 iconColor := t.FgMuted
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 iconColor = t.FgMuted
65 description = t.S().Subtle.Render("disabled")
66 case agent.MCPStateStarting:
67 iconColor = t.Yellow
68 description = t.S().Subtle.Render("starting...")
69 case agent.MCPStateConnected:
70 iconColor = t.Success
71 if state.ToolCount > 0 {
72 extraContent = t.S().Subtle.Render(fmt.Sprintf("(%d tools)", state.ToolCount))
73 }
74 case agent.MCPStateError:
75 iconColor = t.Red
76 if state.Error != nil {
77 description = t.S().Subtle.Render(fmt.Sprintf("error: %s", state.Error.Error()))
78 } else {
79 description = t.S().Subtle.Render("error")
80 }
81 }
82 } else if l.MCP.Disabled {
83 iconColor = t.FgMuted
84 description = t.S().Subtle.Render("disabled")
85 }
86
87 mcpList = append(mcpList,
88 core.Status(
89 core.StatusOpts{
90 IconColor: iconColor,
91 Title: l.Name,
92 Description: description,
93 ExtraContent: extraContent,
94 },
95 opts.MaxWidth,
96 ),
97 )
98 }
99
100 return mcpList
101}
102
103// RenderMCPBlock renders a complete MCP block with optional truncation indicator.
104func RenderMCPBlock(opts RenderOptions, showTruncationIndicator bool) string {
105 t := styles.CurrentTheme()
106 mcpList := RenderMCPList(opts)
107
108 // Add truncation indicator if needed
109 if showTruncationIndicator && opts.MaxItems > 0 {
110 mcps := config.Get().MCP.Sorted()
111 if len(mcps) > opts.MaxItems {
112 remaining := len(mcps) - opts.MaxItems
113 if remaining == 1 {
114 mcpList = append(mcpList, t.S().Base.Foreground(t.FgMuted).Render("β¦"))
115 } else {
116 mcpList = append(mcpList,
117 t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("β¦and %d more", remaining)),
118 )
119 }
120 }
121 }
122
123 content := lipgloss.JoinVertical(lipgloss.Left, mcpList...)
124 if opts.MaxWidth > 0 {
125 return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
126 }
127 return content
128}