on main help sign in
chore: some mcp improvements
commit6805b3494b733479235c801c8b54ed2f6873ab93 authorKujtim Hoxha <kujtimii.h@gmail.com> dateWed 16 Jul 2025, 13:55 (1 year ago) parent483303d
3 files changed+69−21
internal/config/config.go+39−6 internal/llm/agent/mcp-tools.go+11−9 internal/tui/components/dialogs/permissions/permissions.go+19−6
internal/config/config.go+39−6
@@ -6,8 +6,10 @@ import (
66 "slices"
77 "strings"
88
9+ "github.com/charmbracelet/crush/internal/env"
910 "github.com/charmbracelet/crush/internal/fur/provider"
1011 "github.com/tidwall/sjson"
12+ "golang.org/x/exp/slog"
1113)
1214
1315const (
@@ -90,12 +92,12 @@ const (
9092)
9193
9294type MCPConfig struct {
93− Command string `json:"command,omitempty" `
94− Env []string `json:"env,omitempty"`
95− Args []string `json:"args,omitempty"`
96− Type MCPType `json:"type"`
97− URL string `json:"url,omitempty"`
98− Disabled bool `json:"disabled,omitempty"`
95+ Command string `json:"command,omitempty" `
96+ Env map[string]string `json:"env,omitempty"`
97+ Args []string `json:"args,omitempty"`
98+ Type MCPType `json:"type"`
99+ URL string `json:"url,omitempty"`
100+ Disabled bool `json:"disabled,omitempty"`
99101
100102 // TODO: maybe make it possible to get the value from the env
101103 Headers map[string]string `json:"headers,omitempty"`
@@ -165,6 +167,37 @@ func (l LSPs) Sorted() []LSP {
165167 return sorted
166168}
167169
170+func (m MCPConfig) ResolvedEnv() []string {
171+ resolver := NewShellVariableResolver(env.New())
172+ for e, v := range m.Env {
173+ var err error
174+ m.Env[e], err = resolver.ResolveValue(v)
175+ if err != nil {
176+ slog.Error("error resolving environment variable", "error", err, "variable", e, "value", v)
177+ continue
178+ }
179+ }
180+
181+ env := make([]string, 0, len(m.Env))
182+ for k, v := range m.Env {
183+ env = append(env, fmt.Sprintf("%s=%s", k, v))
184+ }
185+ return env
186+}
187+
188+func (m MCPConfig) ResolvedHeaders() map[string]string {
189+ resolver := NewShellVariableResolver(env.New())
190+ for e, v := range m.Headers {
191+ var err error
192+ m.Headers[e], err = resolver.ResolveValue(v)
193+ if err != nil {
194+ slog.Error("error resolving header variable", "error", err, "variable", e, "value", v)
195+ continue
196+ }
197+ }
198+ return m.Headers
199+}
200+
168201type Agent struct {
169202 ID string `json:"id,omitempty"`
170203 Name string `json:"name,omitempty"`
internal/llm/agent/mcp-tools.go+11−9
@@ -36,7 +36,7 @@ type MCPClient interface {
3636}
3737
3838func (b *mcpTool) Name() string {
39− return fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name)
39+ return fmt.Sprintf("mcp_%s_%s", b.mcpName, b.tool.Name)
4040}
4141
4242func (b *mcpTool) Info() tools.ToolInfo {
@@ -45,7 +45,7 @@ func (b *mcpTool) Info() tools.ToolInfo {
4545 required = make([]string, 0)
4646 }
4747 return tools.ToolInfo{
48− Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
48+ Name: fmt.Sprintf("mcp_%s_%s", b.mcpName, b.tool.Name),
4949 Description: b.tool.Description,
5050 Parameters: b.tool.InputSchema.Properties,
5151 Required: required,
@@ -107,14 +107,14 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
107107 },
108108 )
109109 if !p {
110− return tools.NewTextErrorResponse("permission denied"), nil
110+ return tools.ToolResponse{}, permission.ErrorPermissionDenied
111111 }
112112
113113 switch b.mcpConfig.Type {
114114 case config.MCPStdio:
115115 c, err := client.NewStdioMCPClient(
116116 b.mcpConfig.Command,
117− b.mcpConfig.Env,
117+ b.mcpConfig.ResolvedEnv(),
118118 b.mcpConfig.Args...,
119119 )
120120 if err != nil {
@@ -124,7 +124,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
124124 case config.MCPHttp:
125125 c, err := client.NewStreamableHttpClient(
126126 b.mcpConfig.URL,
127− transport.WithHTTPHeaders(b.mcpConfig.Headers),
127+ transport.WithHTTPHeaders(b.mcpConfig.ResolvedHeaders()),
128128 )
129129 if err != nil {
130130 return tools.NewTextErrorResponse(err.Error()), nil
@@ -133,7 +133,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
133133 case config.MCPSse:
134134 c, err := client.NewSSEMCPClient(
135135 b.mcpConfig.URL,
136− client.WithHeaders(b.mcpConfig.Headers),
136+ client.WithHeaders(b.mcpConfig.ResolvedHeaders()),
137137 )
138138 if err != nil {
139139 return tools.NewTextErrorResponse(err.Error()), nil
@@ -192,11 +192,12 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
192192 slog.Debug("skipping disabled mcp", "name", name)
193193 continue
194194 }
195+
195196 switch m.Type {
196197 case config.MCPStdio:
197198 c, err := client.NewStdioMCPClient(
198199 m.Command,
199− m.Env,
200+ m.ResolvedEnv(),
200201 m.Args...,
201202 )
202203 if err != nil {
@@ -206,9 +207,10 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
206207
207208 mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
208209 case config.MCPHttp:
210+ slog.Info("creating mcp client", "name", name, "url", m.URL, "headers", m.ResolvedHeaders())
209211 c, err := client.NewStreamableHttpClient(
210212 m.URL,
211− transport.WithHTTPHeaders(m.Headers),
213+ transport.WithHTTPHeaders(m.ResolvedHeaders()),
212214 )
213215 if err != nil {
214216 slog.Error("error creating mcp client", "error", err)
@@ -218,7 +220,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
218220 case config.MCPSse:
219221 c, err := client.NewSSEMCPClient(
220222 m.URL,
221− client.WithHeaders(m.Headers),
223+ client.WithHeaders(m.ResolvedHeaders()),
222224 )
223225 if err != nil {
224226 slog.Error("error creating mcp client", "error", err)
internal/tui/components/dialogs/permissions/permissions.go+19−6
@@ -407,13 +407,26 @@ func (p *permissionDialogCmp) generateDefaultContent() string {
407407
408408 content := p.permission.Description
409409
410− // Use the cache for markdown rendering
411− renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
412− r := styles.GetMarkdownRenderer(p.width - 4)
413− s, err := r.Render(content)
414− return s, err
415− })
410+ content = strings.TrimSpace(content)
411+ content = "\n" + content + "\n"
412+ lines := strings.Split(content, "\n")
413+
414+ width := p.width - 4
415+ var out []string
416+ for _, ln := range lines {
417+ ln = " " + ln // left padding
418+ if len(ln) > width {
419+ ln = ansi.Truncate(ln, width, "…")
420+ }
421+ out = append(out, t.S().Muted.
422+ Width(width).
423+ Foreground(t.FgBase).
424+ Background(t.BgSubtle).
425+ Render(ln))
426+ }
416427
428+ // Use the cache for markdown rendering
429+ renderedContent := strings.Join(out, "\n")
417430 finalContent := baseStyle.
418431 Width(p.contentViewPort.Width()).
419432 Render(renderedContent)