package handler
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/mark3labs/mcp-go/mcp"
)
func (fs *FilesystemHandler) HandleCopyFile(
ctx context.Context,
request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
source, err := request.RequireString("source")
if err != nil {
return nil, err
}
destination, err := request.RequireString("destination")
if err != nil {
return nil, err
}
// Handle empty or relative paths for source
if source == "." || source == "./" {
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
}
source = cwd
}
if destination == "." || destination == "./" {
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
}
destination = cwd
}
validSource, err := fs.validatePath(source)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error with source path: %v", err),
},
},
IsError: true,
}, nil
}
// Check if source exists
srcInfo, err := os.Stat(validSource)
if os.IsNotExist(err) {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error: Source does not exist: %s", source),
},
},
IsError: true,
}, nil
} else if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error accessing source: %v", err),
},
},
IsError: true,
}, nil
}
validDest, err := fs.validatePath(destination)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error with destination path: %v", err),
},
},
IsError: true,
}, nil
}
// Create parent directory for destination if it doesn't exist
destDir := filepath.Dir(validDest)
if err := os.MkdirAll(destDir, 0755); err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error creating destination directory: %v", err),
},
},
IsError: true,
}, nil
}
// Perform the copy operation based on whether source is a file or directory
if srcInfo.IsDir() {
// It's a directory, copy recursively
if err := copyDir(validSource, validDest); err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error copying directory: %v", err),
},
},
IsError: true,
}, nil
}
} else {
// It's a file, copy directly
if err := copyFile(validSource, validDest); err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error copying file: %v", err),
},
},
IsError: true,
}, nil
}
}
resourceURI := pathToResourceURI(validDest)
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf(
"Successfully copied %s to %s",
source,
destination,
),
},
mcp.EmbeddedResource{
Type: "resource",
Resource: mcp.TextResourceContents{
URI: resourceURI,
MIMEType: "text/plain",
Text: fmt.Sprintf("Copied file: %s", validDest),
},
},
},
}, nil
}
// copyFile copies a single file from src to dst
func copyFile(src, dst string) error {
// Open the source file
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
// Create the destination file
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
// Copy the contents
if _, err := io.Copy(destFile, sourceFile); err != nil {
return err
}
// Get source file mode
sourceInfo, err := os.Stat(src)
if err != nil {
return err
}
// Set the same file mode on destination
return os.Chmod(dst, sourceInfo.Mode())
}
// copyDir recursively copies a directory tree from src to dst
func copyDir(src, dst string) error {
// Get properties of source dir
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
// Create the destination directory with the same permissions
if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
return err
}
// Read directory entries
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
// Handle symlinks
if entry.Type()&os.ModeSymlink != 0 {
// For simplicity, we'll skip symlinks in this implementation
continue
}
// Recursively copy subdirectories or copy files
if entry.IsDir() {
if err = copyDir(srcPath, dstPath); err != nil {
return err
}
} else {
if err = copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}