on main help sign in
chore: some mcp improvements
6805b34Kujtim Hoxha
internal/config/config.go+39−6
@@ -6,8 +6,10 @@ import (
6 "slices"
6 "slices"
7 "strings"
7 "strings"
8
8
9+ "github.com/charmbracelet/crush/internal/env"
9 "github.com/charmbracelet/crush/internal/fur/provider"
10 "github.com/charmbracelet/crush/internal/fur/provider"
10 "github.com/tidwall/sjson"
11 "github.com/tidwall/sjson"
12+ "golang.org/x/exp/slog"
11)
13)
12
14
13const (
15const (
@@ -90,12 +92,12 @@ const (
90)
92)
91
93
92type MCPConfig struct {
94type MCPConfig struct {
93− Command string `json:"command,omitempty" `
95+ Command string `json:"command,omitempty" `
94− Env []string `json:"env,omitempty"`
96+ Env map[string]string `json:"env,omitempty"`
95− Args []string `json:"args,omitempty"`
97+ Args []string `json:"args,omitempty"`
96− Type MCPType `json:"type"`
98+ Type MCPType `json:"type"`
97− URL string `json:"url,omitempty"`
99+ URL string `json:"url,omitempty"`
98− Disabled bool `json:"disabled,omitempty"`
100+ Disabled bool `json:"disabled,omitempty"`
99
101
100 // TODO: maybe make it possible to get the value from the env
102 // TODO: maybe make it possible to get the value from the env
101 Headers map[string]string `json:"headers,omitempty"`
103 Headers map[string]string `json:"headers,omitempty"`
@@ -165,6 +167,37 @@ func (l LSPs) Sorted() []LSP {
165 return sorted
167 return sorted
166}
168}
167
169
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+
168type Agent struct {
201type Agent struct {
169 ID string `json:"id,omitempty"`
202 ID string `json:"id,omitempty"`
170 Name string `json:"name,omitempty"`
203 Name string `json:"name,omitempty"`
internal/llm/agent/mcp-tools.go+11−9
@@ -36,7 +36,7 @@ type MCPClient interface {
36}
36}
37
37
38func (b *mcpTool) Name() string {
38func (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)
40}
40}
41
41
42func (b *mcpTool) Info() tools.ToolInfo {
42func (b *mcpTool) Info() tools.ToolInfo {
@@ -45,7 +45,7 @@ func (b *mcpTool) Info() tools.ToolInfo {
45 required = make([]string, 0)
45 required = make([]string, 0)
46 }
46 }
47 return tools.ToolInfo{
47 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),
49 Description: b.tool.Description,
49 Description: b.tool.Description,
50 Parameters: b.tool.InputSchema.Properties,
50 Parameters: b.tool.InputSchema.Properties,
51 Required: required,
51 Required: required,
@@ -107,14 +107,14 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
107 },
107 },
108 )
108 )
109 if !p {
109 if !p {
110− return tools.NewTextErrorResponse("permission denied"), nil
110+ return tools.ToolResponse{}, permission.ErrorPermissionDenied
111 }
111 }
112
112
113 switch b.mcpConfig.Type {
113 switch b.mcpConfig.Type {
114 case config.MCPStdio:
114 case config.MCPStdio:
115 c, err := client.NewStdioMCPClient(
115 c, err := client.NewStdioMCPClient(
116 b.mcpConfig.Command,
116 b.mcpConfig.Command,
117− b.mcpConfig.Env,
117+ b.mcpConfig.ResolvedEnv(),
118 b.mcpConfig.Args...,
118 b.mcpConfig.Args...,
119 )
119 )
120 if err != nil {
120 if err != nil {
@@ -124,7 +124,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
124 case config.MCPHttp:
124 case config.MCPHttp:
125 c, err := client.NewStreamableHttpClient(
125 c, err := client.NewStreamableHttpClient(
126 b.mcpConfig.URL,
126 b.mcpConfig.URL,
127− transport.WithHTTPHeaders(b.mcpConfig.Headers),
127+ transport.WithHTTPHeaders(b.mcpConfig.ResolvedHeaders()),
128 )
128 )
129 if err != nil {
129 if err != nil {
130 return tools.NewTextErrorResponse(err.Error()), nil
130 return tools.NewTextErrorResponse(err.Error()), nil
@@ -133,7 +133,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
133 case config.MCPSse:
133 case config.MCPSse:
134 c, err := client.NewSSEMCPClient(
134 c, err := client.NewSSEMCPClient(
135 b.mcpConfig.URL,
135 b.mcpConfig.URL,
136− client.WithHeaders(b.mcpConfig.Headers),
136+ client.WithHeaders(b.mcpConfig.ResolvedHeaders()),
137 )
137 )
138 if err != nil {
138 if err != nil {
139 return tools.NewTextErrorResponse(err.Error()), nil
139 return tools.NewTextErrorResponse(err.Error()), nil
@@ -192,11 +192,12 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
192 slog.Debug("skipping disabled mcp", "name", name)
192 slog.Debug("skipping disabled mcp", "name", name)
193 continue
193 continue
194 }
194 }
195+
195 switch m.Type {
196 switch m.Type {
196 case config.MCPStdio:
197 case config.MCPStdio:
197 c, err := client.NewStdioMCPClient(
198 c, err := client.NewStdioMCPClient(
198 m.Command,
199 m.Command,
199− m.Env,
200+ m.ResolvedEnv(),
200 m.Args...,
201 m.Args...,
201 )
202 )
202 if err != nil {
203 if err != nil {
@@ -206,9 +207,10 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
206
207
207 mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
208 mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
208 case config.MCPHttp:
209 case config.MCPHttp:
210+ slog.Info("creating mcp client", "name", name, "url", m.URL, "headers", m.ResolvedHeaders())
209 c, err := client.NewStreamableHttpClient(
211 c, err := client.NewStreamableHttpClient(
210 m.URL,
212 m.URL,
211− transport.WithHTTPHeaders(m.Headers),
213+ transport.WithHTTPHeaders(m.ResolvedHeaders()),
212 )
214 )
213 if err != nil {
215 if err != nil {
214 slog.Error("error creating mcp client", "error", err)
216 slog.Error("error creating mcp client", "error", err)
@@ -218,7 +220,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *confi
218 case config.MCPSse:
220 case config.MCPSse:
219 c, err := client.NewSSEMCPClient(
221 c, err := client.NewSSEMCPClient(
220 m.URL,
222 m.URL,
221− client.WithHeaders(m.Headers),
223+ client.WithHeaders(m.ResolvedHeaders()),
222 )
224 )
223 if err != nil {
225 if err != nil {
224 slog.Error("error creating mcp client", "error", err)
226 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 {
407
407
408 content := p.permission.Description
408 content := p.permission.Description
409
409
410− // Use the cache for markdown rendering
410+ content = strings.TrimSpace(content)
411− renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
411+ content = "\n" + content + "\n"
412− r := styles.GetMarkdownRenderer(p.width - 4)
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).
413− s, err := r.Render(content)
425+ Render(ln))
414− return s, err
415− })
426+ }
416
427
428+ // Use the cache for markdown rendering
429+ renderedContent := strings.Join(out, "\n")
417 finalContent := baseStyle.
430 finalContent := baseStyle.
418 Width(p.contentViewPort.Width()).
431 Width(p.contentViewPort.Width()).
419 Render(renderedContent)
432 Render(renderedContent)