@@ -5,6 +5,7 @@ import (
55 "errors"
66 "fmt"
77 "log/slog"
8+ "maps"
89 "slices"
910 "strings"
1011 "time"
@@ -65,11 +66,13 @@ type agent struct {
6566 sessions session.Service
6667 messages message.Service
6768 permissions permission.Service
68− mcpTools []McpTool
69+ baseTools *csync.Map[string, tools.BaseTool]
70+ mcpTools *csync.Map[string, tools.BaseTool]
71+ lspClients *csync.Map[string, *lsp.Client]
6972
70− tools *csync.LazySlice[tools.BaseTool]
7173 // We need this to be able to update it when model changes
72− agentToolFn func() (tools.BaseTool, error)
74+ agentToolFn func() (tools.BaseTool, error)
75+ cleanupFuncs []func()
7376
7477 provider provider.Provider
7578 providerID string
@@ -171,14 +174,16 @@ func NewAgent(
171174 return nil, err
172175 }
173176
174− toolFn := func() []tools.BaseTool {
175− slog.Info("Initializing agent tools", "agent", agentCfg.ID)
177+ baseToolsFn := func() map[string]tools.BaseTool {
178+ slog.Info("Initializing agent base tools", "agent", agentCfg.ID)
176179 defer func() {
177− slog.Info("Initialized agent tools", "agent", agentCfg.ID)
180+ slog.Info("Initialized agent base tools", "agent", agentCfg.ID)
178181 }()
179182
183+ // Base tools available to all agents
180184 cwd := cfg.WorkingDir()
181− allTools := []tools.BaseTool{
185+ result := make(map[string]tools.BaseTool)
186+ for _, tool := range []tools.BaseTool{
182187 tools.NewBashTool(permissions, cwd, cfg.Options.Attribution),
183188 tools.NewDownloadTool(permissions, cwd),
184189 tools.NewEditTool(lspClients, permissions, history, cwd),
@@ -190,36 +195,25 @@ func NewAgent(
190195 tools.NewSourcegraphTool(),
191196 tools.NewViewTool(lspClients, permissions, cwd),
192197 tools.NewWriteTool(lspClients, permissions, history, cwd),
198+ } {
199+ result[tool.Name()] = tool
193200 }
201+ return result
202+ }
203+ mcpToolsFn := func() map[string]tools.BaseTool {
204+ slog.Info("Initializing agent mcp tools", "agent", agentCfg.ID)
205+ defer func() {
206+ slog.Info("Initialized agent mcp tools", "agent", agentCfg.ID)
207+ }()
194208
195209 mcpToolsOnce.Do(func() {
196− mcpTools = doGetMCPTools(ctx, permissions, cfg)
210+ doGetMCPTools(ctx, permissions, cfg)
197211 })
198212
199− withCoderTools := func(t []tools.BaseTool) []tools.BaseTool {
200− if agentCfg.ID == "coder" {
201− t = append(t, mcpTools...)
202− if lspClients.Len() > 0 {
203− t = append(t, tools.NewDiagnosticsTool(lspClients))
204− }
205− }
206− return t
207− }
208−
209− if agentCfg.AllowedTools == nil {
210− return withCoderTools(allTools)
211− }
212−
213− var filteredTools []tools.BaseTool
214− for _, tool := range allTools {
215− if slices.Contains(agentCfg.AllowedTools, tool.Name()) {
216− filteredTools = append(filteredTools, tool)
217− }
218− }
219− return withCoderTools(filteredTools)
213+ return maps.Collect(mcpTools.Seq2())
220214 }
221215
222− return &agent{
216+ a := &agent{
223217 Broker: pubsub.NewBroker[AgentEvent](),
224218 agentCfg: agentCfg,
225219 provider: agentProvider,
@@ -231,10 +225,14 @@ func NewAgent(
231225 summarizeProviderID: string(providerCfg.ID),
232226 agentToolFn: agentToolFn,
233227 activeRequests: csync.NewMap[string, context.CancelFunc](),
234− tools: csync.NewLazySlice(toolFn),
228+ mcpTools: csync.NewLazyMap(mcpToolsFn),
229+ baseTools: csync.NewLazyMap(baseToolsFn),
235230 promptQueue: csync.NewMap[string, []string](),
236231 permissions: permissions,
237− }, nil
232+ lspClients: lspClients,
233+ }
234+ a.setupEvents(ctx)
235+ return a, nil
238236}
239237
240238func (a *agent) Model() catwalk.Model {
@@ -517,7 +515,18 @@ func (a *agent) createUserMessage(ctx context.Context, sessionID, content string
517515}
518516
519517func (a *agent) getAllTools() ([]tools.BaseTool, error) {
520− allTools := slices.Collect(a.tools.Seq())
518+ var allTools []tools.BaseTool
519+ for tool := range a.baseTools.Seq() {
520+ if a.agentCfg.AllowedTools == nil || slices.Contains(a.agentCfg.AllowedTools, tool.Name()) {
521+ allTools = append(allTools, tool)
522+ }
523+ }
524+ if a.agentCfg.ID == "coder" {
525+ allTools = slices.AppendSeq(allTools, a.mcpTools.Seq())
526+ if a.lspClients.Len() > 0 {
527+ allTools = append(allTools, tools.NewDiagnosticsTool(a.lspClients))
528+ }
529+ }
521530 if a.agentToolFn != nil {
522531 agentTool, agentToolErr := a.agentToolFn()
523532 if agentToolErr != nil {
@@ -591,7 +600,7 @@ loop:
591600 default:
592601 // Continue processing
593602 var tool tools.BaseTool
594− allTools, _ := a.getAllTools()
603+ allTools, _ = a.getAllTools()
595604 for _, availableTool := range allTools {
596605 if availableTool.Info().Name == toolCall.Name {
597606 tool = availableTool
@@ -960,6 +969,12 @@ func (a *agent) CancelAll() {
960969 a.Cancel(key) // key is sessionID
961970 }
962971
972+ for _, cleanup := range a.cleanupFuncs {
973+ if cleanup != nil {
974+ cleanup()
975+ }
976+ }
977+
963978 timeout := time.After(5 * time.Second)
964979 for a.IsBusy() {
965980 select {
@@ -1071,3 +1086,48 @@ func (a *agent) UpdateModel() error {
10711086
10721087 return nil
10731088}
1089+
1090+func (a *agent) setupEvents(ctx context.Context) {
1091+ ctx, cancel := context.WithCancel(ctx)
1092+
1093+ go func() {
1094+ subCh := SubscribeMCPEvents(ctx)
1095+
1096+ for {
1097+ select {
1098+ case event, ok := <-subCh:
1099+ if !ok {
1100+ slog.Debug("MCPEvents subscription channel closed")
1101+ return
1102+ }
1103+ switch event.Payload.Type {
1104+ case MCPEventToolsListChanged:
1105+ name := event.Payload.Name
1106+ c, ok := mcpClients.Get(name)
1107+ if !ok {
1108+ slog.Warn("MCP client not found for tools update", "name", name)
1109+ continue
1110+ }
1111+ cfg := config.Get()
1112+ tools, err := getTools(ctx, name, a.permissions, c, cfg.WorkingDir())
1113+ if err != nil {
1114+ slog.Error("error listing tools", "error", err)
1115+ updateMCPState(name, MCPStateError, err, nil, 0)
1116+ _ = c.Close()
1117+ continue
1118+ }
1119+ updateMcpTools(name, tools)
1120+ a.mcpTools.Reset(maps.Collect(mcpTools.Seq2()))
1121+ updateMCPState(name, MCPStateConnected, nil, c, a.mcpTools.Len())
1122+ default:
1123+ continue
1124+ }
1125+ case <-ctx.Done():
1126+ slog.Debug("MCPEvents subscription cancelled")
1127+ return
1128+ }
1129+ }
1130+ }()
1131+
1132+ a.cleanupFuncs = append(a.cleanupFuncs, cancel)
1133+}