package handler
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/gobwas/glob"
"github.com/mark3labs/mcp-go/mcp"
)
func (fs *FilesystemHandler) HandleSearchFiles(
ctx context.Context,
request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
path, err := request.RequireString("path")
if err != nil {
return nil, err
}
pattern, err := request.RequireString("pattern")
if err != nil {
return nil, err
}
// Handle empty or relative paths like "." or "./" by converting to absolute path
if path == "." || path == "./" {
// Get current working directory
cwd, err := os.Getwd()
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error resolving current directory: %v", err),
},
},
IsError: true,
}, nil
}
path = cwd
}
validPath, err := fs.validatePath(path)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error: %v", err),
},
},
IsError: true,
}, nil
}
// Check if it's a directory
info, err := os.Stat(validPath)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error: %v", err),
},
},
IsError: true,
}, nil
}
if !info.IsDir() {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: "Error: Search path must be a directory",
},
},
IsError: true,
}, nil
}
results, err := searchFiles(validPath, pattern, fs)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error searching files: %v",
err),
},
},
IsError: true,
}, nil
}
if len(results) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("No files found matching pattern '%s' in %s", pattern, path),
},
},
}, nil
}
// Format results with resource URIs
var formattedResults strings.Builder
formattedResults.WriteString(fmt.Sprintf("Found %d results:\n\n", len(results)))
for _, result := range results {
resourceURI := pathToResourceURI(result)
info, err := os.Stat(result)
if err == nil {
if info.IsDir() {
formattedResults.WriteString(fmt.Sprintf("[DIR] %s (%s)\n", result, resourceURI))
} else {
formattedResults.WriteString(fmt.Sprintf("[FILE] %s (%s) - %d bytes\n",
result, resourceURI, info.Size()))
}
} else {
formattedResults.WriteString(fmt.Sprintf("%s (%s)\n", result, resourceURI))
}
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: formattedResults.String(),
},
},
}, nil
}
func searchFiles(rootPath, pattern string, fs *FilesystemHandler) ([]string, error) {
var results []string
globPattern := glob.MustCompile(pattern)
err := filepath.Walk(
rootPath,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // Skip errors and continue
}
// Try to validate path
if _, err := fs.validatePath(path); err != nil {
return nil // Skip invalid paths
}
if globPattern.Match(info.Name()) {
results = append(results, path)
}
return nil
},
)
if err != nil {
return nil, err
}
return results, nil
}