Skip to content
Merged
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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ docker mcp catalog ls
docker mcp catalog show docker-mcp
```

More about [the MCP Catalog](docs/catalog.md).
* more about [the MCP Catalog](docs/catalog.md).
* more about [importing from the OSS MCP Community Registry](docs/catalog.md#importing-from-the-oss-mcp-community-registry).

### MCP Gateway Operations

Expand All @@ -106,7 +107,7 @@ Enable and disable the set of MCP servers that will be available for default cli

```bash
# List enabled servers
docker mcp server list
docker mcp server ls

# Enable one or more servers
docker mcp server enable <server-name> [server-name...]
Expand Down Expand Up @@ -166,10 +167,10 @@ docker mcp --help
docker mcp tools count

# List all available MCP tools
docker mcp tools list
docker mcp tools ls

# List all available MCP tools in JSON format
docker mcp tools list --format=json
docker mcp tools ls --format=json

# Inspect a specific tool
docker mcp tools inspect <tool-name>
Expand Down
4 changes: 2 additions & 2 deletions cmd/docker-mcp/catalog/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/docker/mcp-gateway/cmd/docker-mcp/internal/telemetry"
)

func Ls(ctx context.Context, outputJSON bool) error {
func Ls(ctx context.Context, format Format) error {
// Initialize telemetry
telemetry.Init()

Expand All @@ -25,7 +25,7 @@ func Ls(ctx context.Context, outputJSON bool) error {
// Record successful operation
telemetry.RecordCatalogOperation(ctx, "ls", "all", float64(duration.Milliseconds()), true)

if outputJSON {
if format == JSON {
data, err := json.Marshal(cfg)
if err != nil {
return err
Expand Down
14 changes: 7 additions & 7 deletions cmd/docker-mcp/commands/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ command will import servers from the MCP registry URL into that catalog.`,
// If mcp-registry flag is provided, import to existing catalog
if mcpRegistry != "" {
if dryRun {
return runOfficialregistryImport(cmd.Context(), mcpRegistry, nil)
return runMcpregistryImport(cmd.Context(), mcpRegistry, nil)
}
return importMCPRegistryToCatalog(cmd.Context(), args[0], mcpRegistry)
}
Expand Down Expand Up @@ -87,7 +87,7 @@ cannot be exported as it is managed by Docker.`,

func lsCatalogCommand() *cobra.Command {
var opts struct {
JSON bool
Format catalog.Format
}
cmd := &cobra.Command{
Use: "ls",
Expand All @@ -96,15 +96,15 @@ func lsCatalogCommand() *cobra.Command {
Args: cobra.NoArgs,
Example: ` # List all catalogs
docker mcp catalog ls

# List catalogs in JSON format
docker mcp catalog ls --json`,
docker mcp catalog ls --format=json`,
RunE: func(cmd *cobra.Command, _ []string) error {
return catalog.Ls(cmd.Context(), opts.JSON)
return catalog.Ls(cmd.Context(), opts.Format)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.JSON, "json", false, "Print as JSON.")
flags.Var(&opts.Format, "format", fmt.Sprintf("Output format. Supported: %s.", catalog.SupportedFormats()))
return cmd
}

Expand Down Expand Up @@ -281,7 +281,7 @@ func importMCPRegistryToCatalog(ctx context.Context, catalogName, mcpRegistryURL

// Fetch server from MCP registry
var servers []catalogTypes.Server
if err := runOfficialregistryImport(ctx, mcpRegistryURL, &servers); err != nil {
if err := runMcpregistryImport(ctx, mcpRegistryURL, &servers); err != nil {
return fmt.Errorf("failed to fetch server from MCP registry: %w", err)
}

Expand Down
7 changes: 4 additions & 3 deletions cmd/docker-mcp/commands/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ func featureDisableCommand(dockerCli command.Cli) *cobra.Command {
// featureListCommand creates the `feature list` command
func featureListCommand(dockerCli command.Cli) *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all available features and their status",
Long: "List all available experimental features and show whether they are enabled or disabled.",
Use: "ls",
Aliases: []string{"list"},
Short: "List all available features and their status",
Long: "List all available experimental features and show whether they are enabled or disabled.",
RunE: func(_ *cobra.Command, _ []string) error {
configFile := dockerCli.ConfigFile()

Expand Down
2 changes: 1 addition & 1 deletion cmd/docker-mcp/commands/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func gatewayCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command
if len(mcpRegistryUrls) > 0 {
var mcpServers []catalogTypes.Server
for _, registryURL := range mcpRegistryUrls {
if err := runOfficialregistryImport(cmd.Context(), registryURL, &mcpServers); err != nil {
if err := runMcpregistryImport(cmd.Context(), registryURL, &mcpServers); err != nil {
return fmt.Errorf("failed to fetch server from MCP registry %s: %w", registryURL, err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/docker-mcp/commands/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/docker/mcp-gateway/cmd/docker-mcp/internal/oci"
)

func runOfficialregistryImport(ctx context.Context, serverURL string, servers *[]catalog.Server) error {
func runMcpregistryImport(ctx context.Context, serverURL string, servers *[]catalog.Server) error {
// Validate URL
parsedURL, err := url.Parse(serverURL)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"testing"
)

func TestOfficialregistryImportCommand(t *testing.T) {
func TestMcpregistryImportCommand(t *testing.T) {
// Test server that serves the Garmin MCP example JSON
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
Expand Down Expand Up @@ -69,43 +69,43 @@ func TestOfficialregistryImportCommand(t *testing.T) {

// Test the import function
ctx := context.Background()
err := runOfficialregistryImport(ctx, testServer.URL, nil)
err := runMcpregistryImport(ctx, testServer.URL, nil)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
}

func TestOfficialregistryImportCommand_InvalidURL(t *testing.T) {
func TestMcpregistryImportCommand_InvalidURL(t *testing.T) {
ctx := context.Background()

// Test invalid URL
err := runOfficialregistryImport(ctx, "not-a-url", nil)
err := runMcpregistryImport(ctx, "not-a-url", nil)
if err == nil {
t.Error("Expected error for invalid URL, got none")
}

// Test unsupported scheme
err = runOfficialregistryImport(ctx, "ftp://example.com", nil)
err = runMcpregistryImport(ctx, "ftp://example.com", nil)
if err == nil {
t.Error("Expected error for unsupported scheme, got none")
}
}

func TestOfficialregistryImportCommand_HTTPError(t *testing.T) {
func TestMcpregistryImportCommand_HTTPError(t *testing.T) {
// Test server that returns 404
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer testServer.Close()

ctx := context.Background()
err := runOfficialregistryImport(ctx, testServer.URL, nil)
err := runMcpregistryImport(ctx, testServer.URL, nil)
if err == nil {
t.Error("Expected error for 404 response, got none")
}
}

func TestOfficialregistryImportCommand_InvalidJSON(t *testing.T) {
func TestMcpregistryImportCommand_InvalidJSON(t *testing.T) {
// Test server that returns invalid JSON
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
Expand All @@ -118,7 +118,7 @@ func TestOfficialregistryImportCommand_InvalidJSON(t *testing.T) {
defer testServer.Close()

ctx := context.Background()
err := runOfficialregistryImport(ctx, testServer.URL, nil)
err := runMcpregistryImport(ctx, testServer.URL, nil)
if err == nil {
t.Error("Expected error for invalid JSON, got none")
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/docker-mcp/commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func serverCommand(docker docker.Client) *cobra.Command {

var outputJSON bool
lsCommand := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Use: "ls",
Aliases: []string{"list"},
Short: "List enabled servers",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
Expand Down
4 changes: 2 additions & 2 deletions cmd/docker-mcp/commands/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func toolsCommand(docker docker.Client) *cobra.Command {
cmd.PersistentFlags().StringSliceVar(&gatewayArgs, "gateway-arg", nil, "Additional arguments passed to the gateway")

cmd.AddCommand(&cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Use: "ls",
Aliases: []string{"list"},
Short: "List tools",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
Expand Down
9 changes: 4 additions & 5 deletions cmd/docker-mcp/internal/gateway/dynamic_mcps.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,17 +363,16 @@ func (g *Gateway) createMcpRemoveTool(_ Configuration, clientConfig *clientConfi
}
}

// mcpOfficialRegistryImportTool implements a tool for importing servers from official registry URLs
func (g *Gateway) createMcpOfficialRegistryImportTool(configuration Configuration, _ *clientConfig) *ToolRegistration {
func (g *Gateway) createMcpRegistryImportTool(configuration Configuration, _ *clientConfig) *ToolRegistration {
tool := &mcp.Tool{
Name: "mcp-official-registry-import",
Description: "Import MCP servers from an official registry URL. Fetches server definitions via HTTP GET and adds them to the local catalog.",
Name: "mcp-registry-import",
Description: "Import MCP servers from an MCP registry URL. Fetches server definitions via HTTP GET and adds them to the local catalog.",
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"url": {
Type: "string",
Description: "URL to fetch the official registry JSON from (must be a valid HTTP/HTTPS URL)",
Description: "URL to fetch the server details JSON (must be a valid HTTP/HTTPS URL)",
},
},
Required: []string{"url"},
Expand Down
10 changes: 5 additions & 5 deletions cmd/docker-mcp/internal/gateway/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,10 @@ func (g *Gateway) reloadConfiguration(ctx context.Context, configuration Configu
g.mcpServer.AddTool(mcpRemoveTool.Tool, mcpRemoveTool.Handler)
g.registeredToolNames = append(g.registeredToolNames, mcpRemoveTool.Tool.Name)

// Add mcp-official-registry-import tool
mcpOfficialRegistryImportTool := g.createMcpOfficialRegistryImportTool(configuration, clientConfig)
g.mcpServer.AddTool(mcpOfficialRegistryImportTool.Tool, mcpOfficialRegistryImportTool.Handler)
g.registeredToolNames = append(g.registeredToolNames, mcpOfficialRegistryImportTool.Tool.Name)
// Add mcp-registry-import tool
mcpRegistryImportTool := g.createMcpRegistryImportTool(configuration, clientConfig)
g.mcpServer.AddTool(mcpRegistryImportTool.Tool, mcpRegistryImportTool.Handler)
g.registeredToolNames = append(g.registeredToolNames, mcpRegistryImportTool.Tool.Name)

// Add mcp-config-set tool
mcpConfigSetTool := g.createMcpConfigSetTool(configuration, clientConfig)
Expand All @@ -345,7 +345,7 @@ func (g *Gateway) reloadConfiguration(ctx context.Context, configuration Configu
log(" > mcp-find: tool for finding MCP servers in the catalog")
log(" > mcp-add: tool for adding MCP servers to the registry")
log(" > mcp-remove: tool for removing MCP servers from the registry")
log(" > mcp-official-registry-import: tool for importing servers from official registry URLs")
log(" > mcp-registry-import: tool for importing servers from MCP registry URLs")
log(" > mcp-config-set: tool for setting configuration values for MCP servers")
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/docker-mcp/internal/oci/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func TestServerDetailParsing(t *testing.T) {
// Read test data from external JSON file
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "officialregistry", "server_garmin_mcp.json")
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "mcpregistry", "server_garmin_mcp.json")
jsonData, err := os.ReadFile(testDataPath)
if err != nil {
t.Fatalf("Failed to read test data file %s: %v", testDataPath, err)
Expand Down Expand Up @@ -109,7 +109,7 @@ func TestServerDetailParsing(t *testing.T) {

func TestServerDetailToCatalogServer(t *testing.T) {
// Read test data from external JSON file
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "officialregistry", "server_garmin_mcp.json")
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "mcpregistry", "server_garmin_mcp.json")
jsonData, err := os.ReadFile(testDataPath)
if err != nil {
t.Fatalf("Failed to read test data file %s: %v", testDataPath, err)
Expand Down Expand Up @@ -159,7 +159,7 @@ func TestServerDetailToCatalogServer(t *testing.T) {

func TestConversionForFileSystem(t *testing.T) {
// Read test data from external JSON file
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "officialregistry", "server_filesystem.json")
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "mcpregistry", "server_filesystem.json")
jsonData, err := os.ReadFile(testDataPath)
if err != nil {
t.Fatalf("Failed to read test data file %s: %v", testDataPath, err)
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestConversionForFileSystem(t *testing.T) {

func TestBasicServerConversion(t *testing.T) {
// Read test data from the basic test JSON file
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "officialregistry", "server.test.json")
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "mcpregistry", "server.test.json")
jsonData, err := os.ReadFile(testDataPath)
if err != nil {
t.Fatalf("Failed to read test data file %s: %v", testDataPath, err)
Expand Down Expand Up @@ -431,7 +431,7 @@ func TestBasicServerConversion(t *testing.T) {

func TestRemoteServerConversion(t *testing.T) {
// Read test data from remote server JSON file
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "officialregistry", "server.remote.json")
testDataPath := filepath.Join("..", "..", "..", "..", "test", "testdata", "mcpregistry", "server.remote.json")
jsonData, err := os.ReadFile(testDataPath)
if err != nil {
t.Fatalf("Failed to read test data file %s: %v", testDataPath, err)
Expand Down
28 changes: 23 additions & 5 deletions docs/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This creates a YAML file with real server definitions that you can:
docker mcp catalog ls

# List in JSON format
docker mcp catalog ls --json
docker mcp catalog ls --format=json
```

### Creating Catalogs
Expand Down Expand Up @@ -64,7 +64,14 @@ docker mcp catalog add my-custom-catalog server-name ./source-catalog.yaml
docker mcp catalog add my-custom-catalog server-name ./source-catalog.yaml --force
```

### Importing Catalogs
### Importing Servers from OSS MCP Community Registry

```bash
# replace {id} in the url below
docker mcp catalog import my-custom-catalog --mcp-registry https://registry.modelcontextprotocol.io/v0/servers/{id}
```

### Importing Other Catalogs

```bash
# Import a catalog from a local file
Expand Down Expand Up @@ -272,6 +279,19 @@ EOF
docker mcp catalog add dev-servers my-dev-server ./my-server.yaml
```

### Importing from the OSS MCP Community Registry

```bash
# 1. Create a destination catalog for your community servers
docker mcp catalog create community-catalog

# 2. import the OSS MCP community server resource
docker mcp catalog import --mcp-registry http://registry.modelcontextprotocol.io/v0/servers/71de5a2a-6cfb-4250-a196-f93080ecc860

# 3. show the imported server
docker mcp catalog show community-catalog --format=json | jq .
```

### Team Sharing Workflow

```bash
Expand All @@ -285,7 +305,6 @@ docker mcp catalog export team-servers ./team-catalog.yaml

# Team members: Import the shared catalog
docker mcp catalog import ./team-catalog.yaml
docker mcp feature enable configured-catalogs
docker mcp gateway run
```

Expand Down Expand Up @@ -319,8 +338,7 @@ docker mcp catalog add prod-servers logging ./logging-server.yaml
docker mcp catalog export prod-servers ./prod-catalog-backup.yaml

# 4. Deploy with production catalog
docker mcp feature enable configured-catalogs
docker mcp gateway run --use-configured-catalogs
docker mcp gateway run
```

## Catalog Precedence
Expand Down
Loading
Loading