refactor debug messages

This commit is contained in:
Ed Zynda
2025-08-08 09:32:04 +03:00
parent 5d83df457b
commit d3f9759eb4
13 changed files with 1871 additions and 1529 deletions
+21 -2
View File
@@ -18,6 +18,7 @@ import (
"github.com/mark3labs/mcphost/internal/models"
"github.com/mark3labs/mcphost/internal/session"
"github.com/mark3labs/mcphost/internal/tokens"
"github.com/mark3labs/mcphost/internal/tools"
"github.com/mark3labs/mcphost/internal/ui"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -386,8 +387,15 @@ func runNormalMode(ctx context.Context) error {
}
// Create the agent using the factory
mcpAgent, err := agent.CreateAgent(ctx, &agent.AgentCreationOptions{
ModelConfig: modelConfig,
// Use a buffered debug logger to capture messages during initialization
var bufferedLogger *tools.BufferedDebugLogger
var debugLogger tools.DebugLogger
if viper.GetBool("debug") {
bufferedLogger = tools.NewBufferedDebugLogger(true)
debugLogger = bufferedLogger
}
mcpAgent, err := agent.CreateAgent(ctx, &agent.AgentCreationOptions{ModelConfig: modelConfig,
MCPConfig: mcpConfig,
SystemPrompt: systemPrompt,
MaxSteps: viper.GetInt("max-steps"),
@@ -395,6 +403,7 @@ func runNormalMode(ctx context.Context) error {
ShowSpinner: true,
Quiet: quietFlag,
SpinnerFunc: spinnerFunc,
DebugLogger: debugLogger,
})
if err != nil {
return fmt.Errorf("failed to create agent: %v", err)
@@ -441,6 +450,16 @@ func runNormalMode(ctx context.Context) error {
return fmt.Errorf("failed to setup CLI: %v", err)
}
// Display buffered debug messages if any
if bufferedLogger != nil && cli != nil {
messages := bufferedLogger.GetMessages()
if len(messages) > 0 {
// Combine all messages into a single debug output
combinedMessage := strings.Join(messages, "\n ")
cli.DisplayDebugMessage(combinedMessage)
}
}
// Display debug configuration if debug mode is enabled
if !quietFlag && cli != nil && viper.GetBool("debug") {
debugConfig := map[string]any{
+8
View File
@@ -15,6 +15,7 @@ import (
"github.com/mark3labs/mcphost/internal/config"
"github.com/mark3labs/mcphost/internal/hooks"
"github.com/mark3labs/mcphost/internal/models"
"github.com/mark3labs/mcphost/internal/tools"
"github.com/mark3labs/mcphost/internal/ui"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -596,6 +597,12 @@ func runScriptMode(ctx context.Context, mcpConfig *config.Config, prompt string,
}
// Create the agent using the factory (scripts don't need spinners)
// Use a simple debug logger for scripts
var debugLogger tools.DebugLogger
if finalDebug {
debugLogger = tools.NewSimpleDebugLogger(true)
}
mcpAgent, err := agent.CreateAgent(ctx, &agent.AgentCreationOptions{
ModelConfig: modelConfig,
MCPConfig: mcpConfig,
@@ -605,6 +612,7 @@ func runScriptMode(ctx context.Context, mcpConfig *config.Config, prompt string,
ShowSpinner: false, // Scripts don't need spinners
Quiet: quietFlag,
SpinnerFunc: nil, // No spinner function needed
DebugLogger: debugLogger,
})
if err != nil {
return fmt.Errorf("failed to create agent: %v", err)
+6
View File
@@ -23,6 +23,7 @@ type AgentConfig struct {
SystemPrompt string
MaxSteps int
StreamingEnabled bool
DebugLogger tools.DebugLogger // Optional debug logger
}
// ToolCallHandler is a function type for handling tool calls as they happen
@@ -68,6 +69,11 @@ func NewAgent(ctx context.Context, config *AgentConfig) (*Agent, error) {
// Set the model for sampling support
toolManager.SetModel(providerResult.Model)
// Set the debug logger if provided
if config.DebugLogger != nil {
toolManager.SetDebugLogger(config.DebugLogger)
}
if err := toolManager.LoadTools(ctx, config.MCPConfig); err != nil {
return nil, fmt.Errorf("failed to load MCP tools: %v", err)
}
+6 -3
View File
@@ -7,6 +7,7 @@ import (
"github.com/mark3labs/mcphost/internal/config"
"github.com/mark3labs/mcphost/internal/models"
"github.com/mark3labs/mcphost/internal/tools"
)
// SpinnerFunc is a function type for showing spinners during agent creation
@@ -19,9 +20,10 @@ type AgentCreationOptions struct {
SystemPrompt string
MaxSteps int
StreamingEnabled bool
ShowSpinner bool // For Ollama models
Quiet bool // Skip spinner if quiet
SpinnerFunc SpinnerFunc // Function to show spinner (provided by caller)
ShowSpinner bool // For Ollama models
Quiet bool // Skip spinner if quiet
SpinnerFunc SpinnerFunc // Function to show spinner (provided by caller)
DebugLogger tools.DebugLogger // Optional debug logger
}
// CreateAgent creates an agent with optional spinner for Ollama models
@@ -32,6 +34,7 @@ func CreateAgent(ctx context.Context, opts *AgentCreationOptions) (*Agent, error
SystemPrompt: opts.SystemPrompt,
MaxSteps: opts.MaxSteps,
StreamingEnabled: opts.StreamingEnabled,
DebugLogger: opts.DebugLogger,
}
var agent *Agent
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
package tools
import (
"sync"
)
// BufferedDebugLogger stores debug messages until they can be displayed
type BufferedDebugLogger struct {
enabled bool
messages []string
mu sync.Mutex
}
// NewBufferedDebugLogger creates a new buffered debug logger
func NewBufferedDebugLogger(enabled bool) *BufferedDebugLogger {
return &BufferedDebugLogger{
enabled: enabled,
messages: make([]string, 0),
}
}
// LogDebug stores a debug message
func (l *BufferedDebugLogger) LogDebug(message string) {
if !l.enabled {
return
}
l.mu.Lock()
defer l.mu.Unlock()
l.messages = append(l.messages, message)
}
// IsDebugEnabled returns whether debug logging is enabled
func (l *BufferedDebugLogger) IsDebugEnabled() bool {
return l.enabled
}
// GetMessages returns all buffered messages and clears the buffer
func (l *BufferedDebugLogger) GetMessages() []string {
l.mu.Lock()
defer l.mu.Unlock()
messages := make([]string, len(l.messages))
copy(messages, l.messages)
l.messages = l.messages[:0] // Clear the buffer
return messages
}
+49 -13
View File
@@ -55,10 +55,12 @@ type MCPConnectionPool struct {
model model.ToolCallingChatModel
ctx context.Context
cancel context.CancelFunc
debug bool
debugLogger DebugLogger
}
// NewMCPConnectionPool creates a new connection pool
func NewMCPConnectionPool(config *ConnectionPoolConfig, model model.ToolCallingChatModel) *MCPConnectionPool {
func NewMCPConnectionPool(config *ConnectionPoolConfig, model model.ToolCallingChatModel, debug bool) *MCPConnectionPool {
if config == nil {
config = DefaultConnectionPoolConfig()
}
@@ -70,12 +72,20 @@ func NewMCPConnectionPool(config *ConnectionPoolConfig, model model.ToolCallingC
model: model,
ctx: ctx,
cancel: cancel,
debug: debug,
}
go pool.startHealthCheck()
return pool
}
// SetDebugLogger sets the debug logger for the connection pool
func (p *MCPConnectionPool) SetDebugLogger(logger DebugLogger) {
p.mu.Lock()
defer p.mu.Unlock()
p.debugLogger = logger
}
// GetConnection gets a connection from the pool
func (p *MCPConnectionPool) GetConnection(ctx context.Context, serverName string, serverConfig config.MCPServerConfig) (*MCPConnection, error) {
p.mu.Lock()
@@ -90,16 +100,24 @@ func (p *MCPConnectionPool) GetConnection(ctx context.Context, serverName string
conn.mu.Lock()
conn.lastUsed = time.Now()
conn.mu.Unlock()
fmt.Printf("🔄 [POOL] Reusing connection for %s\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Reusing connection for %s", serverName))
}
return conn, nil
} else {
fmt.Printf("🔍 [POOL] Connection %s unhealthy, removing\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Connection %s unhealthy, removing", serverName))
}
}
conn.client.Close()
delete(p.connections, serverName)
}
}
fmt.Printf("🆕 [POOL] Creating new connection for %s\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Creating new connection for %s", serverName))
}
conn, err := p.createConnection(ctx, serverName, serverConfig)
if err != nil {
return nil, fmt.Errorf("failed to create connection for %s: %w", serverName, err)
@@ -125,10 +143,14 @@ func (p *MCPConnectionPool) GetConnectionWithHealthCheck(ctx context.Context, se
conn.mu.Lock()
conn.lastUsed = time.Now()
conn.mu.Unlock()
fmt.Printf("✅ [POOL] Reusing healthy connection for %s\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Reusing healthy connection for %s", serverName))
}
return conn, nil
} else {
fmt.Printf("🔍 [POOL] Connection %s failed health check, removing\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Connection %s failed health check, removing", serverName))
}
conn.client.Close()
delete(p.connections, serverName)
}
@@ -139,7 +161,9 @@ func (p *MCPConnectionPool) GetConnectionWithHealthCheck(ctx context.Context, se
}
}
fmt.Printf("🆕 [POOL] Creating new connection for %s\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Creating new connection for %s", serverName))
}
conn, err := p.createConnection(ctx, serverName, serverConfig)
if err != nil {
return nil, fmt.Errorf("failed to create connection for %s: %w", serverName, err)
@@ -198,7 +222,9 @@ func (p *MCPConnectionPool) createConnection(ctx context.Context, serverName str
lastError: nil,
}
fmt.Printf("✅ [POOL] Created connection for %s\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Created connection for %s", serverName))
}
return conn, nil
}
@@ -349,7 +375,9 @@ func (p *MCPConnectionPool) initializeClient(ctx context.Context, client client.
return fmt.Errorf("initialization timeout or failed: %v", err)
}
fmt.Printf("✅ [POOL] Initialized MCP client\n")
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Initialized MCP client"))
}
return nil
}
@@ -412,10 +440,14 @@ func (p *MCPConnectionPool) HandleConnectionError(serverName string, err error)
if isConnectionError(err) {
conn.isHealthy = false
fmt.Printf("❌ [POOL] Connection %s marked as unhealthy: %v\n", serverName, err)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Connection %s unhealthy, removing", serverName))
}
if strings.Contains(err.Error(), "404") {
fmt.Printf("🔄 [POOL] 404 error for %s, will recreate on next request\n", serverName)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] 404 error for %s, will recreate on next request", serverName))
}
}
}
}
@@ -466,11 +498,15 @@ func (p *MCPConnectionPool) Close() error {
for name, conn := range p.connections {
if err := conn.client.Close(); err != nil {
fmt.Printf("⚠️ [POOL] Failed to close connection %s: %v\n", name, err)
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug(fmt.Sprintf("[POOL] Failed to close connection %s: %v", name, err))
}
}
}
fmt.Printf("🛑 [POOL] Connection pool closed\n")
if p.debugLogger != nil && p.debugLogger.IsDebugEnabled() {
p.debugLogger.LogDebug("[POOL] Connection pool closed")
}
return nil
}
+28
View File
@@ -0,0 +1,28 @@
package tools
// DebugLogger interface for debug logging
type DebugLogger interface {
LogDebug(message string)
IsDebugEnabled() bool
}
// SimpleDebugLogger is a simple implementation that prints to stdout
type SimpleDebugLogger struct {
enabled bool
}
// NewSimpleDebugLogger creates a new simple debug logger
func NewSimpleDebugLogger(enabled bool) *SimpleDebugLogger {
return &SimpleDebugLogger{enabled: enabled}
}
// LogDebug logs a debug message
func (l *SimpleDebugLogger) LogDebug(message string) {
// Silent by default - messages will only appear when using CLI debug logger
// This prevents duplicate or unstyled debug output during initialization
}
// IsDebugEnabled returns whether debug logging is enabled
func (l *SimpleDebugLogger) IsDebugEnabled() bool {
return l.enabled
}
+28 -9
View File
@@ -26,6 +26,8 @@ type MCPToolManager struct {
toolMap map[string]*toolMapping // maps prefixed tool names to their server and original name
model model.ToolCallingChatModel // LLM model for sampling
config *config.Config
debug bool
debugLogger DebugLogger
}
// toolMapping stores the mapping between prefixed tool names and their original details
@@ -55,6 +57,14 @@ func (m *MCPToolManager) SetModel(model model.ToolCallingChatModel) {
m.model = model
}
// SetDebugLogger sets the debug logger
func (m *MCPToolManager) SetDebugLogger(logger DebugLogger) {
m.debugLogger = logger
if m.connectionPool != nil {
m.connectionPool.SetDebugLogger(logger)
}
}
// samplingHandler implements the MCP sampling handler interface
type samplingHandler struct {
model model.ToolCallingChatModel
@@ -120,7 +130,12 @@ func (h *samplingHandler) CreateMessage(ctx context.Context, request mcp.CreateM
func (m *MCPToolManager) LoadTools(ctx context.Context, config *config.Config) error {
// Initialize connection pool
m.config = config
m.connectionPool = NewMCPConnectionPool(DefaultConnectionPoolConfig(), m.model)
m.debug = config.Debug
if m.debugLogger == nil {
m.debugLogger = NewSimpleDebugLogger(config.Debug)
}
m.connectionPool = NewMCPConnectionPool(DefaultConnectionPoolConfig(), m.model, config.Debug)
m.connectionPool.SetDebugLogger(m.debugLogger)
var loadErrors []string
@@ -143,7 +158,7 @@ func (m *MCPToolManager) LoadTools(ctx context.Context, config *config.Config) e
// loadServerTools loads tools from a single MCP server
func (m *MCPToolManager) loadServerTools(ctx context.Context, serverName string, serverConfig config.MCPServerConfig) error {
// Add debug logging
debugLogConnectionInfo(serverName, serverConfig)
m.debugLogConnectionInfo(serverName, serverConfig)
// Get connection from pool
conn, err := m.connectionPool.GetConnection(ctx, serverName, serverConfig)
@@ -485,22 +500,26 @@ func (m *MCPToolManager) createBuiltinClient(ctx context.Context, serverName str
}
// debugLogConnectionInfo logs detailed connection information for debugging
func debugLogConnectionInfo(serverName string, serverConfig config.MCPServerConfig) {
fmt.Printf("🔍 [DEBUG] Connecting to MCP server: %s\n", serverName)
fmt.Printf("🔍 [DEBUG] Transport type: %s\n", serverConfig.GetTransportType())
func (m *MCPToolManager) debugLogConnectionInfo(serverName string, serverConfig config.MCPServerConfig) {
if m.debugLogger == nil || !m.debugLogger.IsDebugEnabled() {
return
}
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] Connecting to MCP server: %s", serverName))
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] Transport type: %s", serverConfig.GetTransportType()))
switch serverConfig.GetTransportType() {
case "stdio":
if len(serverConfig.Command) > 0 {
fmt.Printf("🔍 [DEBUG] Command: %s %v\n", serverConfig.Command[0], serverConfig.Command[1:])
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] Command: %s %v", serverConfig.Command[0], serverConfig.Command[1:]))
}
if len(serverConfig.Environment) > 0 {
fmt.Printf("🔍 [DEBUG] Environment variables: %d\n", len(serverConfig.Environment))
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] Environment variables: %d", len(serverConfig.Environment)))
}
case "sse", "streamable":
fmt.Printf("🔍 [DEBUG] URL: %s\n", serverConfig.URL)
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] URL: %s", serverConfig.URL))
if len(serverConfig.Headers) > 0 {
fmt.Printf("🔍 [DEBUG] Headers: %v\n", serverConfig.Headers)
m.debugLogger.LogDebug(fmt.Sprintf("[DEBUG] Headers: %v", serverConfig.Headers))
}
}
}
+22
View File
@@ -26,6 +26,7 @@ type CLI struct {
width int
height int
compactMode bool // Add compact mode flag
debug bool // Add debug mode flag
modelName string // Store current model name
lastStreamHeight int // track how far back we need to move the cursor to overwrite streaming messages
usageDisplayed bool // track if usage info was displayed after last assistant message
@@ -35,6 +36,7 @@ type CLI struct {
func NewCLI(debug bool, compact bool) (*CLI, error) {
cli := &CLI{
compactMode: compact,
debug: debug,
}
cli.updateSize()
cli.messageRenderer = NewMessageRenderer(cli.width, debug)
@@ -52,6 +54,11 @@ func (c *CLI) SetUsageTracker(tracker *UsageTracker) {
}
}
// GetDebugLogger returns a debug logger that uses the CLI for rendering
func (c *CLI) GetDebugLogger() *CLIDebugLogger {
return NewCLIDebugLogger(c)
}
// SetModelName sets the current model name for the CLI
func (c *CLI) SetModelName(modelName string) {
c.modelName = modelName
@@ -232,6 +239,21 @@ func (c *CLI) DisplayCancellation() {
c.displayContainer()
}
// DisplayDebugMessage displays debug messages using the appropriate renderer
func (c *CLI) DisplayDebugMessage(message string) {
if !c.debug {
return
}
var msg UIMessage
if c.compactMode {
msg = c.compactRenderer.RenderDebugMessage(message, time.Now())
} else {
msg = c.messageRenderer.RenderDebugMessage(message, time.Now())
}
c.messageContainer.AddMessage(msg)
c.displayContainer()
}
// DisplayDebugConfig displays configuration settings using the appropriate renderer
func (c *CLI) DisplayDebugConfig(config map[string]any) {
var msg UIMessage
+22
View File
@@ -201,6 +201,28 @@ func (r *CompactRenderer) RenderErrorMessage(errorMsg string, timestamp time.Tim
}
}
// RenderDebugMessage renders debug messages in compact format
func (r *CompactRenderer) RenderDebugMessage(message string, timestamp time.Time) UIMessage {
theme := getTheme()
symbol := lipgloss.NewStyle().Foreground(theme.Tool).Render("*")
label := lipgloss.NewStyle().Foreground(theme.Tool).Bold(true).Render("Debug")
// Truncate message if too long
content := message
if len(content) > r.width-20 {
content = content[:r.width-23] + "..."
}
line := fmt.Sprintf("%s %-8s %s", symbol, label, content)
return UIMessage{
Type: SystemMessage,
Content: line,
Height: 1,
Timestamp: timestamp,
}
}
// RenderDebugConfigMessage renders debug config in compact format
func (r *CompactRenderer) RenderDebugConfigMessage(config map[string]any, timestamp time.Time) UIMessage {
theme := getTheme()
+76
View File
@@ -0,0 +1,76 @@
package ui
import (
"fmt"
"strings"
"time"
)
// CLIDebugLogger implements the tools.DebugLogger interface using CLI rendering
type CLIDebugLogger struct {
cli *CLI
}
// NewCLIDebugLogger creates a new CLI debug logger
func NewCLIDebugLogger(cli *CLI) *CLIDebugLogger {
return &CLIDebugLogger{cli: cli}
}
// LogDebug logs a debug message using the CLI's debug message renderer
func (l *CLIDebugLogger) LogDebug(message string) {
if l.cli == nil || !l.cli.debug {
return
}
// Format the message to include all the debug info in a structured way
var formattedMessage string
// Check if this is a multi-line debug output (like connection info)
if strings.Contains(message, "[DEBUG]") || strings.Contains(message, "[POOL]") {
// Extract the tag and content
if strings.HasPrefix(message, "[DEBUG]") {
content := strings.TrimPrefix(message, "[DEBUG]")
content = strings.TrimSpace(content)
formattedMessage = fmt.Sprintf("🔍 DEBUG: %s", content)
} else if strings.HasPrefix(message, "[POOL]") {
content := strings.TrimPrefix(message, "[POOL]")
content = strings.TrimSpace(content)
// Add appropriate emoji based on the message content
if strings.Contains(content, "Creating new connection") {
formattedMessage = fmt.Sprintf("🆕 POOL: %s", content)
} else if strings.Contains(content, "Created connection") || strings.Contains(content, "Initialized") {
formattedMessage = fmt.Sprintf("✅ POOL: %s", content)
} else if strings.Contains(content, "Reusing") {
formattedMessage = fmt.Sprintf("🔄 POOL: %s", content)
} else if strings.Contains(content, "unhealthy") || strings.Contains(content, "failed") {
formattedMessage = fmt.Sprintf("❌ POOL: %s", content)
} else if strings.Contains(content, "closed") {
formattedMessage = fmt.Sprintf("🛑 POOL: %s", content)
} else if strings.Contains(content, "Failed to close") {
formattedMessage = fmt.Sprintf("⚠️ POOL: %s", content)
} else {
formattedMessage = fmt.Sprintf("🔍 POOL: %s", content)
}
} else {
formattedMessage = message
}
} else {
formattedMessage = message
}
// Use the CLI's debug message rendering
var msg UIMessage
if l.cli.compactMode {
msg = l.cli.compactRenderer.RenderDebugMessage(formattedMessage, time.Now())
} else {
msg = l.cli.messageRenderer.RenderDebugMessage(formattedMessage, time.Now())
}
l.cli.messageContainer.AddMessage(msg)
l.cli.displayContainer()
}
// IsDebugEnabled returns whether debug logging is enabled
func (l *CLIDebugLogger) IsDebugEnabled() bool {
return l.cli != nil && l.cli.debug
}
+58
View File
@@ -193,6 +193,64 @@ func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Tim
}
}
// RenderDebugMessage renders debug messages with tool response block styling
func (r *MessageRenderer) RenderDebugMessage(message string, timestamp time.Time) UIMessage {
baseStyle := lipgloss.NewStyle()
// Create the main message style with border using tool color
theme := getTheme()
style := baseStyle.
Width(r.width - 3). // Account for left margin
BorderLeft(true).
Foreground(theme.Muted).
BorderForeground(theme.Tool).
BorderStyle(lipgloss.ThickBorder()).
PaddingLeft(1).
MarginLeft(2). // Add left margin like other messages
MarginBottom(1) // Add bottom margin
// Format timestamp
timeStr := timestamp.Local().Format("02 Jan 2006 03:04 PM")
// Create header with debug icon
header := baseStyle.
Foreground(theme.Tool).
Bold(true).
Render("🔍 Debug Output")
// Process and format the message content
// Split into lines and format each one
lines := strings.Split(message, "\n")
var formattedLines []string
for _, line := range lines {
if strings.TrimSpace(line) != "" {
formattedLines = append(formattedLines, " "+line)
}
}
content := baseStyle.
Foreground(theme.Muted).
Render(strings.Join(formattedLines, "\n"))
// Create info line
info := baseStyle.
Width(r.width - 5). // Account for margins and padding
Foreground(theme.Muted).
Render(fmt.Sprintf(" MCPHost (%s)", timeStr))
// Combine all parts
fullContent := lipgloss.JoinVertical(lipgloss.Left,
header,
content,
info,
)
return UIMessage{
Content: style.Render(fullContent),
Height: lipgloss.Height(style.Render(fullContent)),
}
}
// RenderDebugConfigMessage renders debug configuration settings with tool response block styling
func (r *MessageRenderer) RenderDebugConfigMessage(config map[string]any, timestamp time.Time) UIMessage {
baseStyle := lipgloss.NewStyle()