diff --git a/pkg/mcp/toolset/toolset.go b/pkg/mcp/toolset/toolset.go index aa384da6e90..d5a7be5838e 100644 --- a/pkg/mcp/toolset/toolset.go +++ b/pkg/mcp/toolset/toolset.go @@ -11,6 +11,7 @@ import ( "os/exec" "path/filepath" "slices" + "strings" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/pkg/sftp" @@ -109,6 +110,29 @@ func (ts *ToolSet) TranslateHostPath(hostPath string) (string, error) { if !filepath.IsAbs(hostPath) { return "", fmt.Errorf("expected an absolute path, got a relative path: %q", hostPath) } - // TODO: make sure that hostPath is mounted - return hostPath, nil + + guestPath, isMounted := ts.translateToGuestPath(hostPath) + if !isMounted { + logrus.Warnf("path %q is not under any mounted directory, using as guest path", hostPath) + } + return guestPath, nil +} + +func (ts *ToolSet) translateToGuestPath(hostPath string) (string, bool) { + for _, mount := range ts.inst.Config.Mounts { + location := filepath.Clean(mount.Location) + cleanPath := filepath.Clean(hostPath) + + if cleanPath == location { + return *mount.MountPoint, true + } + + rel, err := filepath.Rel(location, cleanPath) + if err == nil && !strings.HasPrefix(rel, "..") && rel != ".." { + guestPath := filepath.Join(*mount.MountPoint, rel) + return guestPath, true + } + } + + return hostPath, false } diff --git a/pkg/mcp/toolset/toolset_test.go b/pkg/mcp/toolset/toolset_test.go new file mode 100644 index 00000000000..8aad5a4180b --- /dev/null +++ b/pkg/mcp/toolset/toolset_test.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package toolset + +import ( + "testing" + + "gotest.tools/v3/assert" + + "github.com/lima-vm/lima/v2/pkg/limatype" +) + +func TestTranslateHostPath(t *testing.T) { + mountPoint1 := "/mnt/home-user" + mountPoint2 := "/mnt/tmp" + + tests := []struct { + name string + hostPath string + toolSet ToolSet + wantGuestPath string + wantErr bool + }{ + { + name: "file in mounted directory", + hostPath: "/home/user/documents/file.txt", + toolSet: ToolSet{ + inst: &limatype.Instance{ + Config: &limatype.LimaYAML{ + Mounts: []limatype.Mount{ + {Location: "/home/user", MountPoint: &mountPoint1}, + }, + }, + }, + }, + wantGuestPath: "/mnt/home-user/documents/file.txt", + wantErr: false, + }, + { + name: "path outside mount - fallback to guest path", + hostPath: "/other/path/file.txt", + toolSet: ToolSet{ + inst: &limatype.Instance{ + Config: &limatype.LimaYAML{ + Mounts: []limatype.Mount{ + {Location: "/home/user", MountPoint: &mountPoint1}, + }, + }, + }, + }, + wantGuestPath: "/other/path/file.txt", + wantErr: false, + }, + { + name: "similar prefix but not under mount", + hostPath: "/home/user2/file.txt", + toolSet: ToolSet{ + inst: &limatype.Instance{ + Config: &limatype.LimaYAML{ + Mounts: []limatype.Mount{ + {Location: "/home/user", MountPoint: &mountPoint1}, + }, + }, + }, + }, + wantGuestPath: "/home/user2/file.txt", + wantErr: false, + }, + { + name: "multiple mounts", + hostPath: "/tmp/myfile", + toolSet: ToolSet{ + inst: &limatype.Instance{ + Config: &limatype.LimaYAML{ + Mounts: []limatype.Mount{ + {Location: "/home/user", MountPoint: &mountPoint1}, + {Location: "/tmp", MountPoint: &mountPoint2}, + }, + }, + }, + }, + wantGuestPath: "/mnt/tmp/myfile", + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.toolSet.TranslateHostPath(test.hostPath) + if test.wantErr { + assert.Assert(t, err != nil) + } else { + assert.NilError(t, err) + assert.Equal(t, test.wantGuestPath, got) + } + }) + } +}