Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ We collect anonymous usage data to help improve docker agent. To disable:
newEvalCmd(),
newShareCmd(),
newModelsCmd(),
newToolsetsCmd(),
newSetupCmd(),
newDoctorCmd(),
newDebugCmd(),
Expand Down
74 changes: 74 additions & 0 deletions cmd/root/toolsets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package root

import (
"encoding/json"
"fmt"
"text/tabwriter"

"github.com/spf13/cobra"

loadertoolsets "github.com/docker/docker-agent/pkg/teamloader/toolsets"
"github.com/docker/docker-agent/pkg/telemetry"
)

type toolsetsFlags struct {
format string
}

func newToolsetsCmd() *cobra.Command {
var flags toolsetsFlags

cmd := &cobra.Command{
Use: "toolsets",
Short: "List built-in toolset types",
Long: `List the built-in toolset types available for use in an agent configuration.

Each type can be referenced under 'toolsets:' in an agent YAML file, for example:

toolsets:
- type: filesystem
- type: shell`,
GroupID: "diagnose",
Args: cobra.NoArgs,
Example: ` docker agent toolsets
docker agent toolsets --format json`,
RunE: flags.run,
}

cmd.Flags().StringVar(&flags.format, "format", "table", "Output format: table, json")

return cmd
}

func (f *toolsetsFlags) run(cmd *cobra.Command, args []string) (commandErr error) {
ctx := cmd.Context()
telemetry.TrackCommand(ctx, "toolsets", args)
defer func() {
telemetry.TrackCommandError(ctx, "toolsets", args, commandErr)
}()

switch f.format {
case "table":
f.renderTable(cmd)
return nil
case "json":
return f.renderJSON(cmd)
default:
return fmt.Errorf("unknown format %q: must be %q or %q", f.format, "table", "json")
}
}

func (f *toolsetsFlags) renderTable(cmd *cobra.Command) {
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 2, 3, ' ', 0)
fmt.Fprintln(w, "TYPE\tSUMMARY")
for _, ts := range loadertoolsets.BuiltinToolsets {
fmt.Fprintf(w, "%s\t%s\n", ts.Type, ts.Summary)
}
w.Flush()
}

func (f *toolsetsFlags) renderJSON(cmd *cobra.Command) error {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(loadertoolsets.BuiltinToolsets)
}
73 changes: 73 additions & 0 deletions cmd/root/toolsets_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package root

import (
"bytes"
"encoding/json"
"strings"
"testing"

"github.com/stretchr/testify/require"

loadertoolsets "github.com/docker/docker-agent/pkg/teamloader/toolsets"
)

func TestToolsetsCommand_TableListsBuiltinTypes(t *testing.T) {
t.Parallel()

var buf bytes.Buffer
cmd := newToolsetsCmd()
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs(nil)

require.NoError(t, cmd.Execute())

out := buf.String()
require.Contains(t, out, "TYPE")
require.Contains(t, out, "SUMMARY")
require.Contains(t, out, "think")
require.Contains(t, out, "filesystem")
require.Contains(t, out, "Step-by-step reasoning scratchpad for planning")
}

func TestToolsetsCommand_JSONMatchesCatalog(t *testing.T) {
t.Parallel()

var buf bytes.Buffer
cmd := newToolsetsCmd()
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs([]string{"--format", "json"})

require.NoError(t, cmd.Execute())

var got []loadertoolsets.BuiltinToolsetInfo
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
require.Equal(t, loadertoolsets.BuiltinToolsets, got)
}

func TestToolsetsCommand_RejectsArgs(t *testing.T) {
t.Parallel()

var buf bytes.Buffer
cmd := newToolsetsCmd()
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs([]string{"unexpected"})

require.Error(t, cmd.Execute())
}

func TestToolsetsCommand_RejectsUnknownFormat(t *testing.T) {
t.Parallel()

var buf bytes.Buffer
cmd := newToolsetsCmd()
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetArgs([]string{"--format", "yaml"})

err := cmd.Execute()
require.Error(t, err)
require.Contains(t, strings.ToLower(err.Error()), "format")
}
18 changes: 18 additions & 0 deletions docs/features/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ $ docker agent models --provider openai
$ docker agent models --format json | jq
```

### `docker agent toolsets`

List the built-in toolset types available for use in an agent configuration. Each type can be referenced under `toolsets:` in an agent YAML file. Use this to discover what's available without leaving the terminal.

```bash
$ docker agent toolsets [flags]
```

| Flag | Default | Description |
| ---------------- | ------- | --------------------------------- |
| `--format <fmt>` | `table` | Output format: `table` or `json`. |

```bash
# Examples
$ docker agent toolsets # human-readable table
$ docker agent toolsets --format json | jq # machine-readable (type, summary, docs URL)
```

### `docker agent setup`

Set up a model interactively. Three paths: pick a cloud provider, paste its API key, and choose where to store it (macOS Keychain, `pass`, or the docker agent env file `~/.config/cagent/.env`); check Docker Model Runner and pull a local model (no API key needed); or register a custom OpenAI-compatible provider (endpoint URL, API format, and API key variable) saved to your [user configuration](../../providers/custom/index.md#global-providers-user-configuration) so its models work everywhere via `--model <name>/<model>`. Ends with the exact command to start chatting. Secret values are never printed.
Expand Down
43 changes: 43 additions & 0 deletions pkg/teamloader/toolsets/catalog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package toolsets

const docsBaseURL = "https://docker.github.io/docker-agent/tools/"

type BuiltinToolsetInfo struct {
Type string `json:"type"`
Summary string `json:"summary"`
Docs string `json:"docs"`
}

var BuiltinToolsets = []BuiltinToolsetInfo{
builtinToolset("a2a", "a2a", "Connect to remote agents via the Agent-to-Agent (A2A) protocol"),
builtinToolset("api", "api", "Create custom tools that call HTTP APIs"),
builtinToolset("background_agents", "background-agents", "Dispatch work to sub-agents concurrently and collect results"),
builtinToolset("background_jobs", "background-jobs", "Run and manage long-running shell commands"),
builtinToolset("fetch", "fetch", "Read content from HTTP/HTTPS URLs"),
builtinToolset("filesystem", "filesystem", "Read, write, list, search, and navigate files and directories"),
builtinToolset("lsp", "lsp", "Connect to Language Server Protocol servers for code intelligence"),
builtinToolset("mcp", "mcp", "Extend agents with external tools via the Model Context Protocol"),
builtinToolset("mcp_catalog", "mcp-catalog", "Discover and activate remote MCP servers from the Docker MCP Catalog"),
builtinToolset("memory", "memory", "Persistent key-value storage for cross-session recall"),
builtinToolset("model_picker", "model-picker", "Let the agent pick between several models per turn"),
builtinToolset("open_url", "open-url", "Open a fixed URL in the user's default browser"),
builtinToolset("openapi", "openapi", "Generate tools automatically from an OpenAPI specification"),
builtinToolset("plan", "plan", "Shared persistent scratchpad for multi-agent collaboration"),
builtinToolset("rag", "rag", "Search document knowledge bases with hybrid retrieval (RAG)"),
builtinToolset("script", "script", "Define custom shell scripts as named tools with typed parameters"),
builtinToolset("session_context", "session_context", "Reference a previous session as context in the current one"),
builtinToolset("session_plan", "session_plan", "Per-session plan tracker for the draft/review/execute workflow"),
builtinToolset("shell", "shell", "Execute shell commands in the user's environment"),
builtinToolset("tasks", "tasks", "Persistent task database with priorities and dependencies"),
builtinToolset("think", "think", "Step-by-step reasoning scratchpad for planning"),
builtinToolset("todo", "todo", "Manage a task list for complex multi-step workflows"),
builtinToolset("user_prompt", "user-prompt", "Ask the user questions and collect interactive input"),
}

func builtinToolset(toolsetType, docsSlug, summary string) BuiltinToolsetInfo {
return BuiltinToolsetInfo{
Type: toolsetType,
Summary: summary,
Docs: docsBaseURL + docsSlug,
}
}
32 changes: 32 additions & 0 deletions pkg/teamloader/toolsets/catalog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package toolsets

import (
"maps"
"slices"
"testing"

"github.com/stretchr/testify/require"
)

func TestBuiltinToolsetsCatalogMatchesRegistry(t *testing.T) {
t.Parallel()

registryTypes := slices.Sorted(maps.Keys(DefaultToolsetCreators()))

catalogTypes := make([]string, 0, len(BuiltinToolsets))
for _, ts := range BuiltinToolsets {
catalogTypes = append(catalogTypes, ts.Type)
}
slices.Sort(catalogTypes)

require.Equal(t, registryTypes, catalogTypes,
"catalog and registry are out of sync; update pkg/teamloader/toolsets/catalog.go to document exactly the registered toolset types")
}

func TestBuiltinToolsetsHaveSummaries(t *testing.T) {
t.Parallel()

for _, ts := range BuiltinToolsets {
require.NotEmptyf(t, ts.Summary, "toolset %q must have a summary", ts.Type)
}
}
Loading