Detailed changes
@@ -100,7 +100,7 @@ to assist developers in writing, debugging, and understanding code directly from
defer app.Shutdown()
// Initialize MCP tools early for both modes
- initMCPTools(ctx, app)
+ initMCPTools(ctx, app, cfg)
prompt, err = maybePrependStdin(prompt)
if err != nil {
@@ -192,7 +192,7 @@ func attemptTUIRecovery(program *tea.Program) {
program.Quit()
}
-func initMCPTools(ctx context.Context, app *app.App) {
+func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
go func() {
defer log.RecoverPanic("MCP-goroutine", nil)
@@ -201,7 +201,7 @@ func initMCPTools(ctx context.Context, app *app.App) {
defer cancel()
// Set this up once with proper error handling
- agent.GetMcpTools(ctxWithTimeout, app.Permissions)
+ agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
slog.Info("MCP message handling goroutine exiting")
}()
}
@@ -94,21 +94,22 @@ func NewAgent(
) (Service, error) {
ctx := context.Background()
cfg := config.Get()
- otherTools := GetMcpTools(ctx, permissions)
+ otherTools := GetMcpTools(ctx, permissions, cfg)
if len(lspClients) > 0 {
otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
}
+ cwd := cfg.WorkingDir()
allTools := []tools.BaseTool{
- tools.NewBashTool(permissions),
- tools.NewEditTool(lspClients, permissions, history),
- tools.NewFetchTool(permissions),
- tools.NewGlobTool(),
- tools.NewGrepTool(),
- tools.NewLsTool(),
+ tools.NewBashTool(permissions, cwd),
+ tools.NewEditTool(lspClients, permissions, history, cwd),
+ tools.NewFetchTool(permissions, cwd),
+ tools.NewGlobTool(cwd),
+ tools.NewGrepTool(cwd),
+ tools.NewLsTool(cwd),
tools.NewSourcegraphTool(),
- tools.NewViewTool(lspClients),
- tools.NewWriteTool(lspClients, permissions, history),
+ tools.NewViewTool(lspClients, cwd),
+ tools.NewWriteTool(lspClients, permissions, history, cwd),
}
if agentCfg.ID == "coder" {
@@ -22,6 +22,7 @@ type mcpTool struct {
tool mcp.Tool
mcpConfig config.MCPConfig
permissions permission.Service
+ workingDir string
}
type MCPClient interface {
@@ -98,7 +99,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
p := b.permissions.Request(
permission.CreatePermissionRequest{
SessionID: sessionID,
- Path: config.Get().WorkingDir(),
+ Path: b.workingDir,
ToolName: b.Info().Name,
Action: "execute",
Description: permissionDescription,
@@ -143,18 +144,19 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
return tools.NewTextErrorResponse("invalid mcp type"), nil
}
-func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPConfig) tools.BaseTool {
+func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPConfig, workingDir string) tools.BaseTool {
return &mcpTool{
mcpName: name,
tool: tool,
mcpConfig: mcpConfig,
permissions: permissions,
+ workingDir: workingDir,
}
}
var mcpTools []tools.BaseTool
-func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient) []tools.BaseTool {
+func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient, workingDir string) []tools.BaseTool {
var stdioTools []tools.BaseTool
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
@@ -175,17 +177,17 @@ func getTools(ctx context.Context, name string, m config.MCPConfig, permissions
return stdioTools
}
for _, t := range tools.Tools {
- stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
+ stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m, workingDir))
}
defer c.Close()
return stdioTools
}
-func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.BaseTool {
+func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
if len(mcpTools) > 0 {
return mcpTools
}
- for name, m := range config.Get().MCP {
+ for name, m := range cfg.MCP {
switch m.Type {
case config.MCPStdio:
c, err := client.NewStdioMCPClient(
@@ -198,7 +200,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
continue
}
- mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
case config.MCPHttp:
c, err := client.NewStreamableHttpClient(
m.URL,
@@ -208,7 +210,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
slog.Error("error creating mcp client", "error", err)
continue
}
- mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
case config.MCPSse:
c, err := client.NewSSEMCPClient(
m.URL,
@@ -218,7 +220,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
slog.Error("error creating mcp client", "error", err)
continue
}
- mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
}
}
@@ -384,7 +384,7 @@ func getEnvironmentInfo() string {
isGit := isGitRepo(cwd)
platform := runtime.GOOS
date := time.Now().Format("1/2/2006")
- ls := tools.NewLsTool()
+ ls := tools.NewLsTool(cwd)
r, _ := ls.Run(context.Background(), tools.ToolCall{
Input: `{"path":"."}`,
})
@@ -8,7 +8,6 @@ import (
"strings"
"time"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/permission"
"github.com/charmbracelet/crush/internal/shell"
)
@@ -29,6 +28,7 @@ type BashResponseMetadata struct {
}
type bashTool struct {
permissions permission.Service
+ workingDir string
}
const (
@@ -244,9 +244,10 @@ Important:
- Never update git config`, bannedCommandsStr, MaxOutputLength)
}
-func NewBashTool(permission permission.Service) BaseTool {
+func NewBashTool(permission permission.Service, workingDir string) BaseTool {
return &bashTool{
permissions: permission,
+ workingDir: workingDir,
}
}
@@ -317,7 +318,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
p := b.permissions.Request(
permission.CreatePermissionRequest{
SessionID: sessionID,
- Path: config.Get().WorkingDir(),
+ Path: b.workingDir,
ToolName: BashToolName,
Action: "execute",
Description: fmt.Sprintf("Execute command: %s", params.Command),
@@ -337,7 +338,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
defer cancel()
}
stdout, stderr, err := shell.
- GetPersistentShell(config.Get().WorkingDir()).
+ GetPersistentShell(b.workingDir).
Exec(ctx, params.Command)
interrupted := shell.IsInterrupt(err)
exitCode := shell.ExitCode(err)
@@ -10,7 +10,6 @@ import (
"strings"
"time"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/history"
@@ -41,6 +40,7 @@ type editTool struct {
lspClients map[string]*lsp.Client
permissions permission.Service
files history.Service
+ workingDir string
}
const (
@@ -99,11 +99,12 @@ WINDOWS NOTES:
Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
)
-func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
+func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
return &editTool{
lspClients: lspClients,
permissions: permissions,
files: files,
+ workingDir: workingDir,
}
}
@@ -144,8 +145,7 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
}
if !filepath.IsAbs(params.FilePath) {
- wd := config.Get().WorkingDir()
- params.FilePath = filepath.Join(wd, params.FilePath)
+ params.FilePath = filepath.Join(e.workingDir, params.FilePath)
}
var response ToolResponse
@@ -206,9 +206,9 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
_, additions, removals := diff.GenerateDiff(
"",
content,
- strings.TrimPrefix(filePath, config.Get().WorkingDir()),
+ strings.TrimPrefix(filePath, e.workingDir),
)
- rootDir := config.Get().WorkingDir()
+ rootDir := e.workingDir
permissionPath := filepath.Dir(filePath)
if strings.HasPrefix(filePath, rootDir) {
permissionPath = rootDir
@@ -318,10 +318,10 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
_, additions, removals := diff.GenerateDiff(
oldContent,
newContent,
- strings.TrimPrefix(filePath, config.Get().WorkingDir()),
+ strings.TrimPrefix(filePath, e.workingDir),
)
- rootDir := config.Get().WorkingDir()
+ rootDir := e.workingDir
permissionPath := filepath.Dir(filePath)
if strings.HasPrefix(filePath, rootDir) {
permissionPath = rootDir
@@ -441,9 +441,9 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
_, additions, removals := diff.GenerateDiff(
oldContent,
newContent,
- strings.TrimPrefix(filePath, config.Get().WorkingDir()),
+ strings.TrimPrefix(filePath, e.workingDir),
)
- rootDir := config.Get().WorkingDir()
+ rootDir := e.workingDir
permissionPath := filepath.Dir(filePath)
if strings.HasPrefix(filePath, rootDir) {
permissionPath = rootDir
@@ -11,7 +11,6 @@ import (
md "github.com/JohannesKaufmann/html-to-markdown"
"github.com/PuerkitoBio/goquery"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/permission"
)
@@ -30,6 +29,7 @@ type FetchPermissionsParams struct {
type fetchTool struct {
client *http.Client
permissions permission.Service
+ workingDir string
}
const (
@@ -65,7 +65,7 @@ TIPS:
- Set appropriate timeouts for potentially slow websites`
)
-func NewFetchTool(permissions permission.Service) BaseTool {
+func NewFetchTool(permissions permission.Service, workingDir string) BaseTool {
return &fetchTool{
client: &http.Client{
Timeout: 30 * time.Second,
@@ -76,6 +76,7 @@ func NewFetchTool(permissions permission.Service) BaseTool {
},
},
permissions: permissions,
+ workingDir: workingDir,
}
}
@@ -133,7 +134,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
p := t.permissions.Request(
permission.CreatePermissionRequest{
SessionID: sessionID,
- Path: config.Get().WorkingDir(),
+ Path: t.workingDir,
ToolName: FetchToolName,
Action: "fetch",
Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
@@ -11,7 +11,6 @@ import (
"sort"
"strings"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fsext"
)
@@ -68,10 +67,14 @@ type GlobResponseMetadata struct {
Truncated bool `json:"truncated"`
}
-type globTool struct{}
+type globTool struct {
+ workingDir string
+}
-func NewGlobTool() BaseTool {
- return &globTool{}
+func NewGlobTool(workingDir string) BaseTool {
+ return &globTool{
+ workingDir: workingDir,
+ }
}
func (g *globTool) Name() string {
@@ -108,7 +111,7 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
searchPath := params.Path
if searchPath == "" {
- searchPath = config.Get().WorkingDir()
+ searchPath = g.workingDir
}
files, truncated, err := globFiles(params.Pattern, searchPath, 100)
@@ -16,7 +16,6 @@ import (
"sync"
"time"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fsext"
)
@@ -89,7 +88,9 @@ type GrepResponseMetadata struct {
Truncated bool `json:"truncated"`
}
-type grepTool struct{}
+type grepTool struct {
+ workingDir string
+}
const (
GrepToolName = "grep"
@@ -136,8 +137,10 @@ TIPS:
- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
)
-func NewGrepTool() BaseTool {
- return &grepTool{}
+func NewGrepTool(workingDir string) BaseTool {
+ return &grepTool{
+ workingDir: workingDir,
+ }
}
func (g *grepTool) Name() string {
@@ -200,7 +203,7 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
searchPath := params.Path
if searchPath == "" {
- searchPath = config.Get().WorkingDir()
+ searchPath = g.workingDir
}
matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100)
@@ -8,7 +8,6 @@ import (
"path/filepath"
"strings"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fsext"
)
@@ -29,7 +28,9 @@ type LSResponseMetadata struct {
Truncated bool `json:"truncated"`
}
-type lsTool struct{}
+type lsTool struct {
+ workingDir string
+}
const (
LSToolName = "ls"
@@ -70,8 +71,10 @@ TIPS:
- Combine with other tools for more effective exploration`
)
-func NewLsTool() BaseTool {
- return &lsTool{}
+func NewLsTool(workingDir string) BaseTool {
+ return &lsTool{
+ workingDir: workingDir,
+ }
}
func (l *lsTool) Name() string {
@@ -107,11 +110,11 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
searchPath := params.Path
if searchPath == "" {
- searchPath = config.Get().WorkingDir()
+ searchPath = l.workingDir
}
if !filepath.IsAbs(searchPath) {
- searchPath = filepath.Join(config.Get().WorkingDir(), searchPath)
+ searchPath = filepath.Join(l.workingDir, searchPath)
}
if _, err := os.Stat(searchPath); os.IsNotExist(err) {
@@ -10,7 +10,6 @@ import (
"path/filepath"
"strings"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/lsp"
)
@@ -22,6 +21,7 @@ type ViewParams struct {
type viewTool struct {
lspClients map[string]*lsp.Client
+ workingDir string
}
type ViewResponseMetadata struct {
@@ -71,9 +71,10 @@ TIPS:
- When viewing large files, use the offset parameter to read specific sections`
)
-func NewViewTool(lspClients map[string]*lsp.Client) BaseTool {
+func NewViewTool(lspClients map[string]*lsp.Client, workingDir string) BaseTool {
return &viewTool{
- lspClients,
+ lspClients: lspClients,
+ workingDir: workingDir,
}
}
@@ -117,7 +118,7 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
// Handle relative paths
filePath := params.FilePath
if !filepath.IsAbs(filePath) {
- filePath = filepath.Join(config.Get().WorkingDir(), filePath)
+ filePath = filepath.Join(v.workingDir, filePath)
}
// Check if file exists
@@ -10,7 +10,6 @@ import (
"strings"
"time"
- "github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/history"
@@ -33,6 +32,7 @@ type writeTool struct {
lspClients map[string]*lsp.Client
permissions permission.Service
files history.Service
+ workingDir string
}
type WriteResponseMetadata struct {
@@ -77,11 +77,12 @@ TIPS:
- Always include descriptive comments when making changes to existing code`
)
-func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
+func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
return &writeTool{
lspClients: lspClients,
permissions: permissions,
files: files,
+ workingDir: workingDir,
}
}
@@ -123,7 +124,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
filePath := params.FilePath
if !filepath.IsAbs(filePath) {
- filePath = filepath.Join(config.Get().WorkingDir(), filePath)
+ filePath = filepath.Join(w.workingDir, filePath)
}
fileInfo, err := os.Stat(filePath)
@@ -168,10 +169,10 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
diff, additions, removals := diff.GenerateDiff(
oldContent,
params.Content,
- strings.TrimPrefix(filePath, config.Get().WorkingDir()),
+ strings.TrimPrefix(filePath, w.workingDir),
)
- rootDir := config.Get().WorkingDir()
+ rootDir := w.workingDir
permissionPath := filepath.Dir(filePath)
if strings.HasPrefix(filePath, rootDir) {
permissionPath = rootDir