on main help sign in
Merge pull request #208 from charmbracelet/get-tools-mu

refactor: use sync primitives in GetMcpTools

commit9423d107f30f1cfe958b79e31fba1ff7cfabaa2d authorKujtim Hoxha <kujtimii.h@gmail.com> dateWed 16 Jul 2025, 14:05 (1 year ago) parent46e31c50427603
3 files changed+63−62
cmd/root.go−20 internal/llm/agent/agent.go+1−1 internal/llm/agent/mcp-tools.go+62−41
cmd/root.go−20
@@ -6,14 +6,11 @@ import (
66 "io"
77 "log/slog"
88 "os"
9− "time"
109
1110 tea "github.com/charmbracelet/bubbletea/v2"
1211 "github.com/charmbracelet/crush/internal/app"
1312 "github.com/charmbracelet/crush/internal/config"
1413 "github.com/charmbracelet/crush/internal/db"
15− "github.com/charmbracelet/crush/internal/llm/agent"
16− "github.com/charmbracelet/crush/internal/log"
1714 "github.com/charmbracelet/crush/internal/tui"
1815 "github.com/charmbracelet/crush/internal/version"
1916 "github.com/charmbracelet/fang"
@@ -92,9 +89,6 @@ to assist developers in writing, debugging, and understanding code directly from
9289 }
9390 defer app.Shutdown()
9491
95− // Initialize MCP tools early for both modes
96− initMCPTools(ctx, app, cfg)
97−
9892 prompt, err = maybePrependStdin(prompt)
9993 if err != nil {
10094 slog.Error(fmt.Sprintf("Failed to read from stdin: %v", err))
@@ -126,20 +120,6 @@ to assist developers in writing, debugging, and understanding code directly from
126120 },
127121}
128122
129−func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
130− go func() {
131− defer log.RecoverPanic("MCP-goroutine", nil)
132−
133− // Create a context with timeout for the initial MCP tools fetch
134− ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
135− defer cancel()
136−
137− // Set this up once with proper error handling
138− agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
139− slog.Info("MCP message handling goroutine exiting")
140− }()
141−}
142−
143123func Execute() {
144124 if err := fang.Execute(
145125 context.Background(),
internal/llm/agent/agent.go+1−1
@@ -94,7 +94,7 @@ func NewAgent(
9494) (Service, error) {
9595 ctx := context.Background()
9696 cfg := config.Get()
97− otherTools := GetMcpTools(ctx, permissions, cfg)
97+ otherTools := GetMCPTools(ctx, permissions, cfg)
9898 if len(lspClients) > 0 {
9999 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
100100 }
internal/llm/agent/mcp-tools.go+62−41
@@ -5,6 +5,7 @@ import (
55 "encoding/json"
66 "fmt"
77 "log/slog"
8+ "sync"
89
910 "github.com/charmbracelet/crush/internal/config"
1011 "github.com/charmbracelet/crush/internal/llm/tools"
@@ -154,8 +155,6 @@ func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpC
154155 }
155156}
156157
157−var mcpTools []tools.BaseTool
158−
159158func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient, workingDir string) []tools.BaseTool {
160159 var stdioTools []tools.BaseTool
161160 initRequest := mcp.InitializeRequest{}
@@ -183,50 +182,72 @@ func getTools(ctx context.Context, name string, m config.MCPConfig, permissions
183182 return stdioTools
184183}
185184
186−func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
187− if len(mcpTools) > 0 {
188− return mcpTools
189− }
185+var (
186+ mcpToolsOnce sync.Once
187+ mcpTools []tools.BaseTool
188+)
189+
190+func GetMCPTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
191+ mcpToolsOnce.Do(func() {
192+ mcpTools = doGetMCPTools(ctx, permissions, cfg)
193+ })
194+ return mcpTools
195+}
196+
197+func doGetMCPTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
198+ var mu sync.Mutex
199+ var wg sync.WaitGroup
200+ var result []tools.BaseTool
190201 for name, m := range cfg.MCP {
191202 if m.Disabled {
192203 slog.Debug("skipping disabled mcp", "name", name)
193204 continue
194205 }
195− switch m.Type {
196− case config.MCPStdio:
197− c, err := client.NewStdioMCPClient(
198− m.Command,
199− m.Env,
200− m.Args...,
201− )
202− if err != nil {
203− slog.Error("error creating mcp client", "error", err)
204− continue
206+ wg.Add(1)
207+ go func(name string, m config.MCPConfig) {
208+ defer wg.Done()
209+ switch m.Type {
210+ case config.MCPStdio:
211+ c, err := client.NewStdioMCPClient(
212+ m.Command,
213+ m.Env,
214+ m.Args...,
215+ )
216+ if err != nil {
217+ slog.Error("error creating mcp client", "error", err)
218+ return
219+ }
220+
221+ mu.Lock()
222+ result = append(result, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
223+ mu.Unlock()
224+ case config.MCPHttp:
225+ c, err := client.NewStreamableHttpClient(
226+ m.URL,
227+ transport.WithHTTPHeaders(m.Headers),
228+ )
229+ if err != nil {
230+ slog.Error("error creating mcp client", "error", err)
231+ return
232+ }
233+ mu.Lock()
234+ result = append(result, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
235+ mu.Unlock()
236+ case config.MCPSse:
237+ c, err := client.NewSSEMCPClient(
238+ m.URL,
239+ client.WithHeaders(m.Headers),
240+ )
241+ if err != nil {
242+ slog.Error("error creating mcp client", "error", err)
243+ return
244+ }
245+ mu.Lock()
246+ result = append(result, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
247+ mu.Unlock()
205248 }
206−
207− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
208− case config.MCPHttp:
209− c, err := client.NewStreamableHttpClient(
210− m.URL,
211− transport.WithHTTPHeaders(m.Headers),
212− )
213− if err != nil {
214− slog.Error("error creating mcp client", "error", err)
215− continue
216− }
217− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
218− case config.MCPSse:
219− c, err := client.NewSSEMCPClient(
220− m.URL,
221− client.WithHeaders(m.Headers),
222− )
223− if err != nil {
224− slog.Error("error creating mcp client", "error", err)
225− continue
226− }
227− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
228− }
249+ }(name, m)
229250 }
230−
231− return mcpTools
251+ wg.Wait()
252+ return result
232253}