feat(mcp): configurable MCP timeout

Carlos Alexandro Becker created

closes #604
closes https://github.com/charmbracelet/crush/discussions/754

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

internal/config/config.go       |  1 +
internal/llm/agent/mcp-tools.go |  4 +++-
schema.json                     | 10 ++++++++++
3 files changed, 14 insertions(+), 1 deletion(-)

Detailed changes

internal/config/config.go 🔗

@@ -110,6 +110,7 @@ type MCPConfig struct {
 	Type     MCPType           `json:"type" jsonschema:"required,description=Type of MCP connection,enum=stdio,enum=sse,enum=http,default=stdio"`
 	URL      string            `json:"url,omitempty" jsonschema:"description=URL for HTTP or SSE MCP servers,format=uri,example=http://localhost:3000/mcp"`
 	Disabled bool              `json:"disabled,omitempty" jsonschema:"description=Whether this MCP server is disabled,default=false"`
+	Timeout  int               `json:"timeout,omitempty" jsonschema:"description=Timeout in seconds for MCP server connections,default=15,example=30,example=60,example=120"`
 
 	// TODO: maybe make it possible to get the value from the env
 	Headers map[string]string `json:"headers,omitempty" jsonschema:"description=HTTP headers for HTTP/SSE MCP servers"`

internal/llm/agent/mcp-tools.go 🔗

@@ -1,6 +1,7 @@
 package agent
 
 import (
+	"cmp"
 	"context"
 	"encoding/json"
 	"fmt"
@@ -274,7 +275,8 @@ func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *con
 				}
 			}()
 
-			ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+			timeout := time.Duration(cmp.Or(m.Timeout, 15)) * time.Second
+			ctx, cancel := context.WithTimeout(ctx, timeout)
 			defer cancel()
 			c, err := createMcpClient(m)
 			if err != nil {

schema.json 🔗

@@ -125,6 +125,16 @@
           "description": "Whether this MCP server is disabled",
           "default": false
         },
+        "timeout": {
+          "type": "integer",
+          "description": "Timeout in seconds for MCP server connections",
+          "default": 15,
+          "examples": [
+            30,
+            60,
+            120
+          ]
+        },
         "headers": {
           "additionalProperties": {
             "type": "string"