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