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
79 changes: 79 additions & 0 deletions cmd/mcpservers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright 2026 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"os"

"github.com/spf13/cobra"

"github.com/dapr/cli/pkg/kubernetes"
"github.com/dapr/cli/pkg/print"

meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
mcpserversName string
mcpserversOutputFormat string
)

var McpserversCmd = &cobra.Command{
Use: "mcpservers",
Short: "List all Dapr MCPServer resources. Supported platforms: Kubernetes",
Comment on lines +32 to +34
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go naming for initialisms typically keeps them fully-capitalized (e.g., MTLSCmd). Consider renaming McpserversCmd to MCPServersCmd (and file-local references accordingly) to match the established command naming style.

Copilot uses AI. Check for mistakes.
Run: func(cmd *cobra.Command, args []string) {
if kubernetesMode {
if allNamespaces {
resourceNamespace = meta_v1.NamespaceAll
} else if resourceNamespace == "" {
resourceNamespace = meta_v1.NamespaceAll
}
err := kubernetes.PrintMCPServers(mcpserversName, resourceNamespace, mcpserversOutputFormat)
if err != nil {
print.FailureStatusEvent(os.Stderr, err.Error())
os.Exit(1)
}
}
},
PostRun: func(cmd *cobra.Command, args []string) {
kubernetes.CheckForCertExpiry()
},
Example: `
# List all Dapr MCPServer resources in Kubernetes mode
dapr mcpservers -k

# List MCPServer resources in a specific namespace
dapr mcpservers -k --namespace default

# Print a specific MCPServer resource
dapr mcpservers -k -n my-mcp-server

# List MCPServer resources across all namespaces
dapr mcpservers -k --all-namespaces

# Output as JSON
dapr mcpservers -k -o json
`,
}

func init() {
McpserversCmd.Flags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "If true, list all Dapr MCPServer resources in all namespaces")
McpserversCmd.Flags().StringVarP(&mcpserversName, "name", "n", "", "The MCPServer name to be printed (optional)")
McpserversCmd.Flags().StringVarP(&resourceNamespace, "namespace", "", "", "List MCPServer resources in a specific Kubernetes namespace")
McpserversCmd.Flags().StringVarP(&mcpserversOutputFormat, "output", "o", "list", "Output format (options: json or yaml or list)")
McpserversCmd.Flags().BoolVarP(&kubernetesMode, "kubernetes", "k", false, "List all Dapr MCPServer resources in a Kubernetes cluster")
McpserversCmd.Flags().BoolP("help", "h", false, "Print this help message")
McpserversCmd.MarkFlagRequired("kubernetes")
RootCmd.AddCommand(McpserversCmd)
}
153 changes: 153 additions & 0 deletions pkg/kubernetes/mcpservers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
Copyright 2026 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubernetes

import (
"io"
"os"
"sort"
"strings"

apierrors "k8s.io/apimachinery/pkg/api/errors"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/dapr/cli/pkg/age"
"github.com/dapr/cli/utils"
v1alpha1 "github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1"

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Build linux_arm binaries

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Run Self-Hosted E2E tests in linux_amd64_complete

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Run Self-Hosted E2E tests in linux_amd64_slim

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Run Self-Hosted E2E tests in darwin_amd64_slim

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Run Self-Hosted E2E tests in windows_amd64_slim

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:

Check failure on line 27 in pkg/kubernetes/mcpservers.go

View workflow job for this annotation

GitHub Actions / Run Self-Hosted E2E tests in darwin_amd64_complete

no required module provides package github.com/dapr/dapr/pkg/apis/mcpserver/v1alpha1; to add it:
"github.com/dapr/dapr/pkg/client/clientset/versioned"
)

// MCPServerOutput represents an MCPServer resource for table output.
type MCPServerOutput struct {
Namespace string `csv:"Namespace"`
Name string `csv:"Name"`
Transport string `csv:"TRANSPORT"`
URL string `csv:"URL"`
Scopes string `csv:"SCOPES"`
Created string `csv:"CREATED"`
Age string `csv:"AGE"`
}

// mcpServerDetailedOutput is used for JSON/YAML output.
type mcpServerDetailedOutput struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Spec v1alpha1.MCPServerSpec `json:"spec"`
Comment on lines +44 to +46
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with other *DetailedOutput structs used by utils.PrintDetail (e.g., configurationDetailedOutput), include yaml tags alongside json tags here so YAML output is guaranteed to have the intended field names regardless of the YAML marshaller implementation.

Suggested change
Name string `json:"name"`
Namespace string `json:"namespace"`
Spec v1alpha1.MCPServerSpec `json:"spec"`
Name string `json:"name" yaml:"name"`
Namespace string `json:"namespace" yaml:"namespace"`
Spec v1alpha1.MCPServerSpec `json:"spec" yaml:"spec"`

Copilot uses AI. Check for mistakes.
}

// PrintMCPServers prints all Dapr MCPServer resources.
func PrintMCPServers(name, namespace, outputFormat string) error {
return writeMCPServers(os.Stdout, func() (*v1alpha1.MCPServerList, error) {
client, err := DaprClient()
if err != nil {
return nil, err
}

return ListMCPServers(client, namespace)
}, name, outputFormat)
}

// ListMCPServers lists MCPServer resources from Kubernetes.
func ListMCPServers(client versioned.Interface, namespace string) (*v1alpha1.MCPServerList, error) {
list, err := client.MCPServerV1alpha1().MCPServers(namespace).List(meta_v1.ListOptions{})
// This means that the Dapr MCPServer CRD is not installed and
// therefore no MCPServer items exist.
if apierrors.IsNotFound(err) {
list = &v1alpha1.MCPServerList{
Items: []v1alpha1.MCPServer{},
}
} else if err != nil {
return nil, err
}

return list, nil
Comment on lines +61 to +74
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a dependency on the MCPServer typed client (client.MCPServerV1alpha1()), but this repo’s current github.com/dapr/dapr module version may not include the generated clientset for MCPServer yet (as noted in the PR description). Please update go.mod (or add a temporary replace) to a dapr/dapr commit that contains the MCPServer clientset changes, otherwise this will not compile in CI.

Copilot uses AI. Check for mistakes.
}

func writeMCPServers(writer io.Writer, getFunc func() (*v1alpha1.MCPServerList, error), name, outputFormat string) error {
servers, err := getFunc()
if err != nil {
return err
}

filtered := []v1alpha1.MCPServer{}
filteredSpecs := []mcpServerDetailedOutput{}
for _, s := range servers.Items {
serverName := s.GetName()
if name == "" || strings.EqualFold(serverName, name) {
filtered = append(filtered, s)
filteredSpecs = append(filteredSpecs, mcpServerDetailedOutput{
Name: serverName,
Namespace: s.GetNamespace(),
Spec: s.Spec,
})
}
}

if outputFormat == "" || outputFormat == "list" {
return printMCPServerList(writer, filtered)
}

sort.Slice(filteredSpecs, func(i, j int) bool {
return filteredSpecs[i].Namespace > filteredSpecs[j].Namespace
})
return utils.PrintDetail(writer, outputFormat, filteredSpecs)
}

func printMCPServerList(writer io.Writer, list []v1alpha1.MCPServer) error {
out := []MCPServerOutput{}
for _, s := range list {
out = append(out, MCPServerOutput{
Name: s.GetName(),
Namespace: s.GetNamespace(),
Transport: mcpTransport(&s),
URL: mcpURL(&s),
Created: s.CreationTimestamp.Format("2006-01-02 15:04.05"),
Age: age.GetAge(s.CreationTimestamp.Time),
Scopes: strings.Join(s.Scopes, ","),
})
}

sort.Slice(out, func(i, j int) bool {
return out[i].Namespace > out[j].Namespace
})
return utils.MarshalAndWriteTable(writer, out)
}

// mcpTransport returns the transport type string for the MCPServer.
func mcpTransport(s *v1alpha1.MCPServer) string {
switch {
case s.Spec.Endpoint.StreamableHTTP != nil:
return "streamable_http"
case s.Spec.Endpoint.SSE != nil:
return "sse"
case s.Spec.Endpoint.Stdio != nil:
return "stdio"
default:
return ""
}
}

// mcpURL returns the URL or command for the MCPServer.
func mcpURL(s *v1alpha1.MCPServer) string {
switch {
case s.Spec.Endpoint.StreamableHTTP != nil:
return s.Spec.Endpoint.StreamableHTTP.URL
case s.Spec.Endpoint.SSE != nil:
return s.Spec.Endpoint.SSE.URL
case s.Spec.Endpoint.Stdio != nil:
return s.Spec.Endpoint.Stdio.Command
default:
return ""
}
}
Loading
Loading