fix(mcp): stabilise file existence checks

Use Stat() for file_exists and sort directory listings for deterministic output.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 10:48:34 +00:00
parent 599d0b6298
commit b96b05ab0b
2 changed files with 49 additions and 14 deletions

View file

@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"slices"
"sort"
"sync"
core "dappco.re/go/core"
@ -503,6 +504,9 @@ func (s *Service) listDirectory(ctx context.Context, req *mcp.CallToolRequest, i
if err != nil {
return nil, ListDirectoryOutput{}, log.E("mcp.listDirectory", "failed to list directory", err)
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
result := make([]DirectoryEntry, 0, len(entries))
for _, e := range entries {
info, _ := e.Info()
@ -545,21 +549,15 @@ func (s *Service) renameFile(ctx context.Context, req *mcp.CallToolRequest, inpu
}
func (s *Service) fileExists(ctx context.Context, req *mcp.CallToolRequest, input FileExistsInput) (*mcp.CallToolResult, FileExistsOutput, error) {
exists := s.medium.IsFile(input.Path)
if exists {
return nil, FileExistsOutput{Exists: true, IsDir: false, Path: input.Path}, nil
info, err := s.medium.Stat(input.Path)
if err != nil {
return nil, FileExistsOutput{Exists: false, IsDir: false, Path: input.Path}, nil
}
// Check if it's a directory by attempting to list it
// List might fail if it's a file too (but we checked IsFile) or if doesn't exist.
_, err := s.medium.List(input.Path)
isDir := err == nil
// If List failed, it might mean it doesn't exist OR it's a special file or permissions.
// Assuming if List works, it's a directory.
// Refinement: If it doesn't exist, List returns error.
return nil, FileExistsOutput{Exists: isDir, IsDir: isDir, Path: input.Path}, nil
return nil, FileExistsOutput{
Exists: true,
IsDir: info.IsDir(),
Path: input.Path,
}, nil
}
func (s *Service) detectLanguage(ctx context.Context, req *mcp.CallToolRequest, input DetectLanguageInput) (*mcp.CallToolResult, DetectLanguageOutput, error) {

View file

@ -216,6 +216,43 @@ func TestMedium_Good_EnsureDir(t *testing.T) {
}
}
func TestFileExists_Good_FileAndDirectory(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if err := s.medium.EnsureDir("nested"); err != nil {
t.Fatalf("Failed to create directory: %v", err)
}
if err := s.medium.Write("nested/file.txt", "content"); err != nil {
t.Fatalf("Failed to write file: %v", err)
}
_, fileOut, err := s.fileExists(nil, nil, FileExistsInput{Path: "nested/file.txt"})
if err != nil {
t.Fatalf("fileExists(file) failed: %v", err)
}
if !fileOut.Exists {
t.Fatal("expected file to exist")
}
if fileOut.IsDir {
t.Fatal("expected file to not be reported as a directory")
}
_, dirOut, err := s.fileExists(nil, nil, FileExistsInput{Path: "nested"})
if err != nil {
t.Fatalf("fileExists(dir) failed: %v", err)
}
if !dirOut.Exists {
t.Fatal("expected directory to exist")
}
if !dirOut.IsDir {
t.Fatal("expected directory to be reported as a directory")
}
}
func TestMedium_Good_IsFile(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})