mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add coder_workspace_ls MCP tool (#19652)
This commit is contained in:
@@ -42,6 +42,7 @@ const (
|
||||
ToolNameWorkspaceBash = "coder_workspace_bash"
|
||||
ToolNameChatGPTSearch = "search"
|
||||
ToolNameChatGPTFetch = "fetch"
|
||||
ToolNameWorkspaceLS = "coder_workspace_ls"
|
||||
ToolNameWorkspaceReadFile = "coder_workspace_read_file"
|
||||
ToolNameWorkspaceWriteFile = "coder_workspace_write_file"
|
||||
ToolNameWorkspaceEditFile = "coder_workspace_edit_file"
|
||||
@@ -213,6 +214,7 @@ var All = []GenericTool{
|
||||
WorkspaceBash.Generic(),
|
||||
ChatGPTSearch.Generic(),
|
||||
ChatGPTFetch.Generic(),
|
||||
WorkspaceLS.Generic(),
|
||||
WorkspaceReadFile.Generic(),
|
||||
WorkspaceWriteFile.Generic(),
|
||||
WorkspaceEditFile.Generic(),
|
||||
@@ -1373,6 +1375,62 @@ type MinimalTemplate struct {
|
||||
ActiveUserCount int `json:"active_user_count"`
|
||||
}
|
||||
|
||||
type WorkspaceLSArgs struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type WorkspaceLSFile struct {
|
||||
Path string `json:"path"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
}
|
||||
|
||||
type WorkspaceLSResponse struct {
|
||||
Contents []WorkspaceLSFile `json:"contents"`
|
||||
}
|
||||
|
||||
var WorkspaceLS = Tool[WorkspaceLSArgs, WorkspaceLSResponse]{
|
||||
Tool: aisdk.Tool{
|
||||
Name: ToolNameWorkspaceLS,
|
||||
Description: `List directories in a workspace.`,
|
||||
Schema: aisdk.Schema{
|
||||
Properties: map[string]any{
|
||||
"workspace": map[string]any{
|
||||
"type": "string",
|
||||
"description": "The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used.",
|
||||
},
|
||||
"path": map[string]any{
|
||||
"type": "string",
|
||||
"description": "The absolute path of the directory in the workspace to list.",
|
||||
},
|
||||
},
|
||||
Required: []string{"path", "workspace"},
|
||||
},
|
||||
},
|
||||
UserClientOptional: true,
|
||||
Handler: func(ctx context.Context, deps Deps, args WorkspaceLSArgs) (WorkspaceLSResponse, error) {
|
||||
conn, err := newAgentConn(ctx, deps.coderClient, args.Workspace)
|
||||
if err != nil {
|
||||
return WorkspaceLSResponse{}, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
res, err := conn.LS(ctx, args.Path, workspacesdk.LSRequest{})
|
||||
if err != nil {
|
||||
return WorkspaceLSResponse{}, err
|
||||
}
|
||||
|
||||
contents := make([]WorkspaceLSFile, len(res.Contents))
|
||||
for i, f := range res.Contents {
|
||||
contents[i] = WorkspaceLSFile{
|
||||
Path: f.AbsolutePathString,
|
||||
IsDir: f.IsDir,
|
||||
}
|
||||
}
|
||||
return WorkspaceLSResponse{Contents: contents}, nil
|
||||
},
|
||||
}
|
||||
|
||||
type WorkspaceReadFileArgs struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Path string `json:"path"`
|
||||
|
||||
@@ -454,6 +454,52 @@ func TestTools(t *testing.T) {
|
||||
require.Equal(t, "owner format works", result.Output)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceLS", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, workspace, agentToken := setupWorkspaceForAgent(t)
|
||||
fs := afero.NewMemMapFs()
|
||||
_ = agenttest.New(t, client.URL, agentToken, func(opts *agent.Options) {
|
||||
opts.Filesystem = fs
|
||||
})
|
||||
coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()
|
||||
tb, err := toolsdk.NewDeps(client)
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpdir := os.TempDir()
|
||||
|
||||
dirPath := filepath.Join(tmpdir, "dir1/dir2")
|
||||
err = fs.MkdirAll(dirPath, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
filePath := filepath.Join(tmpdir, "dir1", "foo")
|
||||
err = afero.WriteFile(fs, filePath, []byte("foo bar"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = testTool(t, toolsdk.WorkspaceLS, tb, toolsdk.WorkspaceLSArgs{
|
||||
Workspace: workspace.Name,
|
||||
Path: "relative",
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "path must be absolute")
|
||||
|
||||
res, err := testTool(t, toolsdk.WorkspaceLS, tb, toolsdk.WorkspaceLSArgs{
|
||||
Workspace: workspace.Name,
|
||||
Path: filepath.Dir(dirPath),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []toolsdk.WorkspaceLSFile{
|
||||
{
|
||||
Path: dirPath,
|
||||
IsDir: true,
|
||||
},
|
||||
{
|
||||
Path: filePath,
|
||||
IsDir: false,
|
||||
},
|
||||
}, res.Contents)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceReadFile", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user