@@ -78,6 +78,28 @@ Crush can use LSPs for additional context to help inform its decisions, just lik
 }
 ```
 
+### MCPs
+
+Crush can also use MCPs for additional context. Add LSPs to the config like so:
+
+```json
+{
+  "mcp": {
+    "context7": {
+      "url": "https://mcp.context7.com/mcp",
+      "type": "http"
+    },
+    "github": {
+      "type": "http",
+      "url": "https://api.githubcopilot.com/mcp/",
+      "headers": {
+        "Authorization": "$(echo Bearer $GH_MCP_TOKEN)"
+      }
+    }
+  }
+}
+```
+
 ### OpenAI-Compatible APIs
 
 Crush supports all OpenAI-compatible APIs. Here's an example configuration for Deepseek, which uses an OpenAI-compatible API. Don't forget to set `DEEPSEEK_API_KEY` in your environment.
  
  
  
    
    @@ -6,8 +6,10 @@ import (
 	"slices"
 	"strings"
 
+	"github.com/charmbracelet/crush/internal/env"
 	"github.com/charmbracelet/crush/internal/fur/provider"
 	"github.com/tidwall/sjson"
+	"golang.org/x/exp/slog"
 )
 
 const (
@@ -90,12 +92,12 @@ const (
 )
 
 type MCPConfig struct {
-	Command  string   `json:"command,omitempty" `
-	Env      []string `json:"env,omitempty"`
-	Args     []string `json:"args,omitempty"`
-	Type     MCPType  `json:"type"`
-	URL      string   `json:"url,omitempty"`
-	Disabled bool     `json:"disabled,omitempty"`
+	Command  string            `json:"command,omitempty" `
+	Env      map[string]string `json:"env,omitempty"`
+	Args     []string          `json:"args,omitempty"`
+	Type     MCPType           `json:"type"`
+	URL      string            `json:"url,omitempty"`
+	Disabled bool              `json:"disabled,omitempty"`
 
 	// TODO: maybe make it possible to get the value from the env
 	Headers map[string]string `json:"headers,omitempty"`
@@ -165,6 +167,37 @@ func (l LSPs) Sorted() []LSP {
 	return sorted
 }
 
+func (m MCPConfig) ResolvedEnv() []string {
+	resolver := NewShellVariableResolver(env.New())
+	for e, v := range m.Env {
+		var err error
+		m.Env[e], err = resolver.ResolveValue(v)
+		if err != nil {
+			slog.Error("error resolving environment variable", "error", err, "variable", e, "value", v)
+			continue
+		}
+	}
+
+	env := make([]string, 0, len(m.Env))
+	for k, v := range m.Env {
+		env = append(env, fmt.Sprintf("%s=%s", k, v))
+	}
+	return env
+}
+
+func (m MCPConfig) ResolvedHeaders() map[string]string {
+	resolver := NewShellVariableResolver(env.New())
+	for e, v := range m.Headers {
+		var err error
+		m.Headers[e], err = resolver.ResolveValue(v)
+		if err != nil {
+			slog.Error("error resolving header variable", "error", err, "variable", e, "value", v)
+			continue
+		}
+	}
+	return m.Headers
+}
+
 type Agent struct {
 	ID          string `json:"id,omitempty"`
 	Name        string `json:"name,omitempty"`
  
  
  
    
    @@ -37,7 +37,7 @@ type MCPClient interface {
 }
 
 func (b *mcpTool) Name() string {
-	return fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name)
+	return fmt.Sprintf("mcp_%s_%s", b.mcpName, b.tool.Name)
 }
 
 func (b *mcpTool) Info() tools.ToolInfo {
@@ -46,7 +46,7 @@ func (b *mcpTool) Info() tools.ToolInfo {
 		required = make([]string, 0)
 	}
 	return tools.ToolInfo{
-		Name:        fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
+		Name:        fmt.Sprintf("mcp_%s_%s", b.mcpName, b.tool.Name),
 		Description: b.tool.Description,
 		Parameters:  b.tool.InputSchema.Properties,
 		Required:    required,
@@ -108,14 +108,14 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
 		},
 	)
 	if !p {
-		return tools.NewTextErrorResponse("permission denied"), nil
+		return tools.ToolResponse{}, permission.ErrorPermissionDenied
 	}
 
 	switch b.mcpConfig.Type {
 	case config.MCPStdio:
 		c, err := client.NewStdioMCPClient(
 			b.mcpConfig.Command,
-			b.mcpConfig.Env,
+			b.mcpConfig.ResolvedEnv(),
 			b.mcpConfig.Args...,
 		)
 		if err != nil {
@@ -125,7 +125,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
 	case config.MCPHttp:
 		c, err := client.NewStreamableHttpClient(
 			b.mcpConfig.URL,
-			transport.WithHTTPHeaders(b.mcpConfig.Headers),
+			transport.WithHTTPHeaders(b.mcpConfig.ResolvedHeaders()),
 		)
 		if err != nil {
 			return tools.NewTextErrorResponse(err.Error()), nil
@@ -134,7 +134,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
 	case config.MCPSse:
 		c, err := client.NewSSEMCPClient(
 			b.mcpConfig.URL,
-			client.WithHeaders(b.mcpConfig.Headers),
+			client.WithHeaders(b.mcpConfig.ResolvedHeaders()),
 		)
 		if err != nil {
 			return tools.NewTextErrorResponse(err.Error()), nil
@@ -210,7 +210,7 @@ func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *con
 			case config.MCPStdio:
 				c, err := client.NewStdioMCPClient(
 					m.Command,
-					m.Env,
+					m.ResolvedEnv(),
 					m.Args...,
 				)
 				if err != nil {
@@ -224,7 +224,7 @@ func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *con
 			case config.MCPHttp:
 				c, err := client.NewStreamableHttpClient(
 					m.URL,
-					transport.WithHTTPHeaders(m.Headers),
+					transport.WithHTTPHeaders(m.ResolvedHeaders()),
 				)
 				if err != nil {
 					slog.Error("error creating mcp client", "error", err)
@@ -236,7 +236,7 @@ func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *con
 			case config.MCPSse:
 				c, err := client.NewSSEMCPClient(
 					m.URL,
-					client.WithHeaders(m.Headers),
+					client.WithHeaders(m.ResolvedHeaders()),
 				)
 				if err != nil {
 					slog.Error("error creating mcp client", "error", err)
  
  
  
    
    @@ -409,13 +409,26 @@ func (p *permissionDialogCmp) generateDefaultContent() string {
 
 	content := p.permission.Description
 
-	// Use the cache for markdown rendering
-	renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
-		r := styles.GetMarkdownRenderer(p.width - 4)
-		s, err := r.Render(content)
-		return s, err
-	})
+	content = strings.TrimSpace(content)
+	content = "\n" + content + "\n"
+	lines := strings.Split(content, "\n")
+
+	width := p.width - 4
+	var out []string
+	for _, ln := range lines {
+		ln = " " + ln // left padding
+		if len(ln) > width {
+			ln = ansi.Truncate(ln, width, "…")
+		}
+		out = append(out, t.S().Muted.
+			Width(width).
+			Foreground(t.FgBase).
+			Background(t.BgSubtle).
+			Render(ln))
+	}
 
+	// Use the cache for markdown rendering
+	renderedContent := strings.Join(out, "\n")
 	finalContent := baseStyle.
 		Width(p.contentViewPort.Width()).
 		Render(renderedContent)