Skip to content

Commit d34deb5

Browse files
committed
feat: better server mgmt in profiles
- Add ability to remove servers by URI - Add an update command for atomic add/remove
1 parent 8f13ce3 commit d34deb5

File tree

8 files changed

+592
-19
lines changed

8 files changed

+592
-19
lines changed

cmd/docker-mcp/commands/catalog_next.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func catalogNextCommand() *cobra.Command {
2626
cmd.AddCommand(pushCatalogNextCommand())
2727
cmd.AddCommand(pullCatalogNextCommand())
2828
cmd.AddCommand(tagCatalogNextCommand())
29+
cmd.AddCommand(catalogNextServerCommand())
2930

3031
return cmd
3132
}
@@ -83,6 +84,7 @@ func tagCatalogNextCommand() *cobra.Command {
8384
func showCatalogNextCommand() *cobra.Command {
8485
format := string(workingset.OutputFormatHumanReadable)
8586
pullOption := string(catalognext.PullOptionNever)
87+
var noTools bool
8688

8789
cmd := &cobra.Command{
8890
Use: "show <oci-reference> [--pull <pull-option>]",
@@ -98,13 +100,14 @@ func showCatalogNextCommand() *cobra.Command {
98100
return err
99101
}
100102
ociService := oci.NewService()
101-
return catalognext.Show(cmd.Context(), dao, ociService, args[0], workingset.OutputFormat(format), pullOption)
103+
return catalognext.Show(cmd.Context(), dao, ociService, args[0], workingset.OutputFormat(format), pullOption, noTools)
102104
},
103105
}
104106

105107
flags := cmd.Flags()
106108
flags.StringVar(&format, "format", string(workingset.OutputFormatHumanReadable), fmt.Sprintf("Supported: %s.", strings.Join(workingset.SupportedFormats(), ", ")))
107109
flags.StringVar(&pullOption, "pull", string(catalognext.PullOptionNever), fmt.Sprintf("Supported: %s, or duration (e.g. '1h', '1d'). Duration represents time since last update.", strings.Join(catalognext.SupportedPullOptions(), ", ")))
110+
flags.BoolVar(&noTools, "no-tools", false, "Exclude tools from output")
108111
return cmd
109112
}
110113

@@ -181,3 +184,62 @@ func pullCatalogNextCommand() *cobra.Command {
181184
},
182185
}
183186
}
187+
188+
func catalogNextServerCommand() *cobra.Command {
189+
cmd := &cobra.Command{
190+
Use: "server",
191+
Short: "Manage servers in catalogs",
192+
}
193+
194+
cmd.AddCommand(listCatalogNextServersCommand())
195+
196+
return cmd
197+
}
198+
199+
func listCatalogNextServersCommand() *cobra.Command {
200+
var opts struct {
201+
Filters []string
202+
Format string
203+
}
204+
205+
cmd := &cobra.Command{
206+
Use: "ls <oci-reference>",
207+
Aliases: []string{"list"},
208+
Short: "List servers in a catalog",
209+
Long: `List all servers in a catalog.
210+
211+
Use --filter to search for servers matching a query (case-insensitive substring matching on server names).
212+
Filters use key=value format (e.g., name=github).`,
213+
Example: ` # List all servers in a catalog
214+
docker mcp catalog-next server ls mcp/docker-mcp-catalog:latest
215+
216+
# Filter servers by name
217+
docker mcp catalog-next server ls mcp/docker-mcp-catalog:latest --filter name=github
218+
219+
# Combine multiple filters (using short flag)
220+
docker mcp catalog-next server ls mcp/docker-mcp-catalog:latest -f name=slack -f name=github
221+
222+
# Output in JSON format
223+
docker mcp catalog-next server ls mcp/docker-mcp-catalog:latest --format json`,
224+
Args: cobra.ExactArgs(1),
225+
RunE: func(cmd *cobra.Command, args []string) error {
226+
supported := slices.Contains(workingset.SupportedFormats(), opts.Format)
227+
if !supported {
228+
return fmt.Errorf("unsupported format: %s", opts.Format)
229+
}
230+
231+
dao, err := db.New()
232+
if err != nil {
233+
return err
234+
}
235+
236+
return catalognext.ListServers(cmd.Context(), dao, args[0], opts.Filters, workingset.OutputFormat(opts.Format))
237+
},
238+
}
239+
240+
flags := cmd.Flags()
241+
flags.StringArrayVarP(&opts.Filters, "filter", "f", []string{}, "Filter output (e.g., name=github)")
242+
flags.StringVar(&opts.Format, "format", string(workingset.OutputFormatHumanReadable), fmt.Sprintf("Supported: %s.", strings.Join(workingset.SupportedFormats(), ", ")))
243+
244+
return cmd
245+
}

cmd/docker-mcp/commands/workingset.go

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ func workingsetServerCommand() *cobra.Command {
368368
cmd.AddCommand(listServersCommand())
369369
cmd.AddCommand(addServerCommand())
370370
cmd.AddCommand(removeServerCommand())
371+
cmd.AddCommand(updateServerCommand())
371372

372373
return cmd
373374
}
@@ -410,29 +411,95 @@ func addServerCommand() *cobra.Command {
410411

411412
func removeServerCommand() *cobra.Command {
412413
var names []string
414+
var servers []string
413415

414416
cmd := &cobra.Command{
415-
Use: "remove <profile-id> --name <name1> --name <name2> ...",
417+
Use: "remove <profile-id> [--name <name1> ...] [--server <uri1> ...]",
416418
Aliases: []string{"rm"},
417419
Short: "Remove MCP servers from a profile",
418-
Long: "Remove MCP servers from a profile by server name.",
419-
Example: ` # Remove servers by name
420+
Long: "Remove MCP servers from a profile by server name or server URI.",
421+
Example: ` # Remove by name
420422
docker mcp profile server remove dev-tools --name github --name slack
421423
422-
# Remove a single server
423-
docker mcp profile server remove dev-tools --name github`,
424+
# Remove by URI (same as used for add)
425+
docker mcp profile server remove dev-tools --server catalog://mcp/docker-mcp-catalog/github+slack
426+
427+
# Remove by direct image reference
428+
docker mcp profile server remove dev-tools --server docker://mcp/github:latest
429+
430+
# Mix multiple URIs
431+
docker mcp profile server remove dev-tools --server catalog://mcp/docker-mcp-catalog/github --server docker://custom-server:latest`,
424432
Args: cobra.ExactArgs(1),
425433
RunE: func(cmd *cobra.Command, args []string) error {
434+
// Validation: can't specify both
435+
if len(names) > 0 && len(servers) > 0 {
436+
return fmt.Errorf("cannot specify both --name and --server flags")
437+
}
438+
if len(names) == 0 && len(servers) == 0 {
439+
return fmt.Errorf("must specify either --name or --server flag")
440+
}
441+
426442
dao, err := db.New()
427443
if err != nil {
428444
return err
429445
}
446+
447+
// If servers provided, resolve to names first
448+
if len(servers) > 0 {
449+
registryClient := registryapi.NewClient()
450+
ociService := oci.NewService()
451+
names, err = workingset.ResolveServerURIsToNames(cmd.Context(), dao, registryClient, ociService, servers)
452+
if err != nil {
453+
return fmt.Errorf("failed to resolve server URIs: %w\nHint: Use --name flag with server names from 'docker mcp profile show %s'", err, args[0])
454+
}
455+
}
456+
430457
return workingset.RemoveServers(cmd.Context(), dao, args[0], names)
431458
},
432459
}
433460

434461
flags := cmd.Flags()
435462
flags.StringArrayVar(&names, "name", []string{}, "Server name to remove (can be specified multiple times)")
463+
flags.StringArrayVar(&servers, "server", []string{}, "Server URI to remove - same format as add command (can be specified multiple times)")
464+
465+
return cmd
466+
}
467+
468+
func updateServerCommand() *cobra.Command {
469+
var addServers []string
470+
var removeServers []string
471+
472+
cmd := &cobra.Command{
473+
Use: "update <profile-id> [--add <uri1> --add <uri2> ...] [--remove <uri1> --remove <uri2> ...]",
474+
Short: "Update servers in a profile (add and remove atomically)",
475+
Long: "Atomically add and remove MCP servers in a single operation. Both operations are applied together or fail together.",
476+
Example: ` # Add and remove servers in one atomic operation
477+
docker mcp profile server update dev-tools --add catalog://mcp/docker-mcp-catalog/github --remove catalog://mcp/docker-mcp-catalog/slack
478+
479+
# Add multiple servers while removing others
480+
docker mcp profile server update my-profile --add docker://server1:latest --add docker://server2:latest --remove docker://old-server:latest
481+
482+
# Mix different URI types
483+
docker mcp profile server update dev-tools --add catalog://mcp/docker-mcp-catalog/github --add http://registry.modelcontextprotocol.io/v0/servers/71de5a2a-6cfb-4250-a196-f93080ecc860 --remove docker://old:latest`,
484+
Args: cobra.ExactArgs(1),
485+
RunE: func(cmd *cobra.Command, args []string) error {
486+
if len(addServers) == 0 && len(removeServers) == 0 {
487+
return fmt.Errorf("must specify at least one --add or --remove flag")
488+
}
489+
490+
dao, err := db.New()
491+
if err != nil {
492+
return err
493+
}
494+
registryClient := registryapi.NewClient()
495+
ociService := oci.NewService()
496+
return workingset.UpdateServers(cmd.Context(), dao, registryClient, ociService, args[0], addServers, removeServers)
497+
},
498+
}
499+
500+
flags := cmd.Flags()
501+
flags.StringArrayVar(&addServers, "add", []string{}, "Server URI to add: https:// (MCP Registry) or docker:// (Docker Image) or catalog:// (Catalog). Can be specified multiple times.")
502+
flags.StringArrayVar(&removeServers, "remove", []string{}, "Server URI to remove - same format as add. Can be specified multiple times.")
436503

437504
return cmd
438505
}

docs/profiles.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,44 @@ docker mcp profile server add dev-tools \
110110

111111
### Removing Servers from a Profile
112112

113-
Remove servers from a profile by their server name:
113+
Remove servers from a profile by their server name or by the same URI used to add them:
114114

115115
```bash
116-
# Remove servers by name
116+
# Remove by name
117117
docker mcp profile server remove dev-tools \
118118
--name github \
119119
--name slack
120120

121-
# Remove a single server
122-
docker mcp profile server remove dev-tools --name github
121+
# Remove by URI (same format as used for add)
122+
docker mcp profile server remove dev-tools \
123+
--server catalog://mcp/docker-mcp-catalog/github+slack
124+
125+
# Remove by direct image reference
126+
docker mcp profile server remove dev-tools \
127+
--server docker://mcp/github:latest
128+
129+
# Mix multiple URIs
130+
docker mcp profile server remove dev-tools \
131+
--server catalog://mcp/docker-mcp-catalog/github \
132+
--server docker://custom-server:latest
123133

124134
# Using alias
125135
docker mcp profile server rm dev-tools --name github
126136
```
127137

128-
**Server Names:**
138+
**Removal Options:**
129139
- Use `--name` flag to specify server names to remove (can be specified multiple times)
140+
- Use `--server` flag to specify server URIs to remove - same format as the add command (can be specified multiple times)
141+
- You must specify either `--name` or `--server`, but not both
142+
- When using `--server`, the CLI will resolve the URI to find the matching server names and remove them
130143
- Server names are determined by the server's snapshot (not the image name or source URL)
131144
- Use `docker mcp profile show <profile-id>` to see available server names in a profile
132145

146+
**When to use each option:**
147+
- Use `--name` when you know the server's name (faster, no resolution needed)
148+
- Use `--server` for consistency with how you added the server
149+
- Use `--server` with catalog URIs to remove multiple servers at once (e.g., `catalog://.../github+slack`)
150+
133151
### Listing Servers Across Profiles
134152

135153
View all servers grouped by profile, with filtering capabilities:
@@ -814,6 +832,10 @@ Error: server 'github' not found in profile
814832
- Use `docker mcp profile show <profile-id> --format yaml` to see current servers in the profile
815833
- Ensure you're using the correct server name in the snapshot (not the image name or source URL)
816834
- Server names are case-sensitive
835+
- Alternatively, use `--server` flag with the same URI you used to add the server:
836+
```bash
837+
docker mcp profile server remove <profile-id> --server docker://mcp/github:latest
838+
```
817839

818840
### Invalid Tool Name Format
819841

0 commit comments

Comments
 (0)