mcp.go

 1package chat
 2
 3import (
 4	"encoding/json"
 5	"fmt"
 6	"strings"
 7
 8	"github.com/charmbracelet/crush/internal/message"
 9	"github.com/charmbracelet/crush/internal/ui/styles"
10)
11
12// MCPToolMessageItem is a message item that represents a bash tool call.
13type MCPToolMessageItem struct {
14	*baseToolMessageItem
15}
16
17var _ ToolMessageItem = (*MCPToolMessageItem)(nil)
18
19// NewMCPToolMessageItem creates a new [MCPToolMessageItem].
20func NewMCPToolMessageItem(
21	sty *styles.Styles,
22	toolCall message.ToolCall,
23	result *message.ToolResult,
24	canceled bool,
25) ToolMessageItem {
26	return newBaseToolMessageItem(sty, toolCall, result, &MCPToolRenderContext{}, canceled)
27}
28
29// MCPToolRenderContext renders bash tool messages.
30type MCPToolRenderContext struct{}
31
32// RenderTool implements the [ToolRenderer] interface.
33func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34	cappedWidth := cappedMessageWidth(width)
35	toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
36	if len(toolNameParts) != 3 {
37		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth)
38	}
39	mcpName := humanizedToolName(toolNameParts[1])
40	toolName := humanizedToolName(toolNameParts[2])
41
42	mcpName = sty.Tool.MCPName.Render(mcpName)
43	toolName = sty.Tool.MCPToolName.Render(toolName)
44
45	name := fmt.Sprintf("%s %s %s", mcpName, sty.Tool.MCPArrow.String(), toolName)
46
47	if opts.IsPending() {
48		return pendingTool(sty, name, opts.Anim, opts.Compact)
49	}
50
51	var params map[string]any
52	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
53		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
54	}
55
56	var toolParams []string
57	if len(params) > 0 {
58		parsed, _ := json.Marshal(params)
59		toolParams = append(toolParams, string(parsed))
60	}
61
62	header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
63	if opts.Compact {
64		return header
65	}
66
67	if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
68		return joinToolParts(header, earlyState)
69	}
70
71	if !opts.HasResult() || opts.Result.Content == "" {
72		return header
73	}
74
75	bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
76	body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent)
77	return joinToolParts(header, body)
78}