diff --git a/cmd/main.go b/cmd/main.go index b338bb0..b216a6c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,229 +17,19 @@ limitations under the License. package main import ( - "crypto/tls" - "flag" - "os" - "path/filepath" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/certwatcher" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/filters" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" - - kagentdevv1alpha1 "github.com/kagent-dev/kmcp/api/v1alpha1" - "github.com/kagent-dev/kmcp/pkg/controller" + "github.com/kagent-dev/kmcp/pkg/app" // +kubebuilder:scaffold:imports ) -var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") -) - -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(kagentdevv1alpha1.AddToScheme(scheme)) - // +kubebuilder:scaffold:scheme -} - -// nolint:gocyclo func main() { - var metricsAddr string - var metricsCertPath, metricsCertName, metricsCertKey string - var webhookCertPath, webhookCertName, webhookCertKey string - var enableLeaderElection bool - var probeAddr string - var secureMetrics bool - var enableHTTP2 bool - var tlsOpts []func(*tls.Config) - flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ - "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&secureMetrics, "metrics-secure", true, - "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") - flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") - flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") - flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") - flag.StringVar(&metricsCertPath, "metrics-cert-path", "", - "The directory that contains the metrics server certificate.") - flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") - flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") - flag.BoolVar(&enableHTTP2, "enable-http2", false, - "If set, HTTP/2 will be enabled for the metrics and webhook servers") - opts := zap.Options{ - Development: true, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - // if the enable-http2 flag is false (the default), http/2 should be disabled - // due to its vulnerabilities. More specifically, disabling http/2 will - // prevent from being vulnerable to the HTTP/2 Stream Cancellation and - // Rapid Reset CVEs. For more information see: - // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 - // - https://github.com/advisories/GHSA-4374-p667-p6c8 - disableHTTP2 := func(c *tls.Config) { - setupLog.Info("disabling http/2") - c.NextProtos = []string{"http/1.1"} - } - - if !enableHTTP2 { - tlsOpts = append(tlsOpts, disableHTTP2) - } - - // Create watchers for metrics and webhooks certificates - var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher - - // Initial webhook TLS options - webhookTLSOpts := tlsOpts - - if len(webhookCertPath) > 0 { - setupLog.Info("Initializing webhook certificate watcher using provided certificates", - "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) - - var err error - webhookCertWatcher, err = certwatcher.New( - filepath.Join(webhookCertPath, webhookCertName), - filepath.Join(webhookCertPath, webhookCertKey), - ) - if err != nil { - setupLog.Error(err, "Failed to initialize webhook certificate watcher") - os.Exit(1) - } - - webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { - config.GetCertificate = webhookCertWatcher.GetCertificate - }) - } - - webhookServer := webhook.NewServer(webhook.Options{ - TLSOpts: webhookTLSOpts, - }) - - // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. - // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.0/pkg/metrics/server - // - https://book.kubebuilder.io/reference/metrics.html - metricsServerOptions := metricsserver.Options{ - BindAddress: metricsAddr, - SecureServing: secureMetrics, - TLSOpts: tlsOpts, - } - - if secureMetrics { - // FilterProvider is used to protect the metrics endpoint with authn/authz. - // These configurations ensure that only authorized users and service accounts - // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.0/pkg/metrics/filters#WithAuthenticationAndAuthorization - metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization - } - - // If the certificate is not specified, controller-runtime will automatically - // generate self-signed certificates for the metrics server. While convenient for development and testing, - // this setup is not recommended for production. - // - // TODO(user): If you enable certManager, uncomment the following lines: - // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates - // managed by cert-manager for the metrics server. - // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. - if len(metricsCertPath) > 0 { - setupLog.Info("Initializing metrics certificate watcher using provided certificates", - "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) - - var err error - metricsCertWatcher, err = certwatcher.New( - filepath.Join(metricsCertPath, metricsCertName), - filepath.Join(metricsCertPath, metricsCertKey), - ) - if err != nil { - setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) - os.Exit(1) - } - - metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { - config.GetCertificate = metricsCertWatcher.GetCertificate - }) - } - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Metrics: metricsServerOptions, - WebhookServer: webhookServer, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "90217b08.kagent.dev", - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, + app.Start(func() (*app.ExtensionConfig, error) { + return &app.ExtensionConfig{ + PluginFactories: nil, + RegisterSchemes: nil, + }, nil }) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - if err = (&controller.MCPServerReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Plugins: nil, // Plugins are for 3rd party extensions to open source. - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "MCPServer") - os.Exit(1) - } - // +kubebuilder:scaffold:builder - - if metricsCertWatcher != nil { - setupLog.Info("Adding metrics certificate watcher to manager") - if err := mgr.Add(metricsCertWatcher); err != nil { - setupLog.Error(err, "unable to add metrics certificate watcher to manager") - os.Exit(1) - } - } - - if webhookCertWatcher != nil { - setupLog.Info("Adding webhook certificate watcher to manager") - if err := mgr.Add(webhookCertWatcher); err != nil { - setupLog.Error(err, "unable to add webhook certificate watcher to manager") - os.Exit(1) - } - } - - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) - } - - setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) - } } diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 0000000..015a7d5 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,321 @@ +/* +Copyright 2025. + +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 app + +import ( + "crypto/tls" + "flag" + "os" + "path/filepath" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + kagentdevv1alpha1 "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/kagent-dev/kmcp/pkg/controller" + "github.com/kagent-dev/kmcp/pkg/controller/transportadapter" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(kagentdevv1alpha1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme + + ctrl.SetLogger(zap.New(zap.UseDevMode(true))) +} + +type Config struct { + Metrics struct { + Addr string + CertPath string + CertName string + CertKey string + } + Webhook struct { + CertPath string + CertName string + CertKey string + } + LeaderElection bool + ProbeAddr string + SecureMetrics bool + EnableHTTP2 bool +} + +func (cfg *Config) SetFlags(commandLine *flag.FlagSet) { + commandLine.StringVar(&cfg.Metrics.Addr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + commandLine.StringVar(&cfg.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + commandLine.BoolVar(&cfg.LeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + commandLine.BoolVar(&cfg.SecureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + commandLine.StringVar(&cfg.Metrics.CertPath, "metrics-cert-path", "", + "The directory that contains the metrics server certificate.") + commandLine.StringVar( + &cfg.Metrics.CertName, + "metrics-cert-name", + "tls.crt", + "The name of the metrics server certificate file.", + ) + commandLine.StringVar(&cfg.Metrics.CertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") + commandLine.StringVar( + &cfg.Webhook.CertPath, + "webhook-cert-path", + "", + "The directory that contains the webhook certificate.", + ) + commandLine.StringVar( + &cfg.Webhook.CertName, + "webhook-cert-name", + "tls.crt", + "The name of the webhook certificate file.", + ) + commandLine.StringVar(&cfg.Webhook.CertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") + commandLine.BoolVar(&cfg.EnableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") +} + +// PluginFactory creates a TranslatorPlugin when provided with the client and scheme. +// This allows plugins to be initialized after the manager is created, giving them access to +// the Kubernetes client and scheme. Plugins should create their own logger. +type PluginFactory func(client.Client, *runtime.Scheme) transportadapter.TranslatorPlugin + +type ExtensionConfig struct { + // PluginFactories are factories that create translator plugins for extending MCPServer translation behavior. + // These factories are called after the manager is created, allowing plugins to access the client and scheme. + PluginFactories []PluginFactory + // RegisterSchemes is an optional function to register additional API types to the runtime scheme. + // This is called before the manager is created, allowing extensions to add their own CRDs. + RegisterSchemes func(*runtime.Scheme) error +} + +type GetExtensionConfig func() (*ExtensionConfig, error) + +// nolint:gocyclo +func Start(getExtensionConfig GetExtensionConfig) { + var cfg Config + var tlsOpts []func(*tls.Config) + + cfg.SetFlags(flag.CommandLine) + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !cfg.EnableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + // Create watchers for metrics and webhooks certificates + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher + + // Initial webhook TLS options + webhookTLSOpts := tlsOpts + + if len(cfg.Webhook.CertPath) > 0 { + //nolint:lll + setupLog.Info("Initializing webhook certificate watcher using provided certificates", + "webhook-cert-path", cfg.Webhook.CertPath, "webhook-cert-name", cfg.Webhook.CertName, "webhook-cert-key", cfg.Webhook.CertKey) + + var err error + webhookCertWatcher, err = certwatcher.New( + filepath.Join(cfg.Webhook.CertPath, cfg.Webhook.CertName), + filepath.Join(cfg.Webhook.CertPath, cfg.Webhook.CertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize webhook certificate watcher") + os.Exit(1) + } + + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { + config.GetCertificate = webhookCertWatcher.GetCertificate + }) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: webhookTLSOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.0/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: cfg.Metrics.Addr, + SecureServing: cfg.SecureMetrics, + TLSOpts: tlsOpts, + } + + if cfg.SecureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.0/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + // If the certificate is not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + // + // TODO(user): If you enable certManager, uncomment the following lines: + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates + // managed by cert-manager for the metrics server. + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. + if len(cfg.Metrics.CertPath) > 0 { + //nolint:lll + setupLog.Info("Initializing metrics certificate watcher using provided certificates", + "metrics-cert-path", cfg.Metrics.CertPath, "metrics-cert-name", cfg.Metrics.CertName, "metrics-cert-key", cfg.Metrics.CertKey) + + var err error + metricsCertWatcher, err = certwatcher.New( + filepath.Join(cfg.Metrics.CertPath, cfg.Metrics.CertName), + filepath.Join(cfg.Metrics.CertPath, cfg.Metrics.CertKey), + ) + if err != nil { + setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) + os.Exit(1) + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = metricsCertWatcher.GetCertificate + }) + } + + // Get extension config before creating the manager + // Schemes must be registered before the manager is created + extensionCfg, err := getExtensionConfig() + if err != nil { + setupLog.Error(err, "unable to get extension config") + os.Exit(1) + } + + // Register extension schemes if provided + if extensionCfg.RegisterSchemes != nil { + if err := extensionCfg.RegisterSchemes(scheme); err != nil { + setupLog.Error(err, "unable to register extension schemes") + os.Exit(1) + } + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: cfg.ProbeAddr, + LeaderElection: cfg.LeaderElection, + LeaderElectionID: "90217b08.kagent.dev", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + plugins := make([]transportadapter.TranslatorPlugin, 0, len(extensionCfg.PluginFactories)) + for _, factory := range extensionCfg.PluginFactories { + plugin := factory(mgr.GetClient(), mgr.GetScheme()) + plugins = append(plugins, plugin) + } + + if err = (&controller.MCPServerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Plugins: plugins, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MCPServer") + os.Exit(1) + } + // +kubebuilder:scaffold:builder + + if metricsCertWatcher != nil { + setupLog.Info("Adding metrics certificate watcher to manager") + if err := mgr.Add(metricsCertWatcher); err != nil { + setupLog.Error(err, "unable to add metrics certificate watcher to manager") + os.Exit(1) + } + } + + if webhookCertWatcher != nil { + setupLog.Info("Adding webhook certificate watcher to manager") + if err := mgr.Add(webhookCertWatcher); err != nil { + setupLog.Error(err, "unable to add webhook certificate watcher to manager") + os.Exit(1) + } + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/pkg/cli/internal/commands/deploy.go b/pkg/cli/internal/commands/deploy.go index 3beeeca..bdc1673 100644 --- a/pkg/cli/internal/commands/deploy.go +++ b/pkg/cli/internal/commands/deploy.go @@ -112,6 +112,8 @@ func init() { } // package subcommand - supports both npm and uvx +// +//nolint:lll // Long example lines in command description var packageDeployCmd = &cobra.Command{ Use: "package", Short: "Deploy an MCP server using a package manager (npx, uvx)", @@ -137,17 +139,32 @@ func init() { // package subcommand flags packageDeployCmd.Flags().StringVar(&packageName, "deployment-name", "", "Name for the deployment (required)") packageDeployCmd.Flags().StringVar(&packageManager, "manager", "", "Package manager to use (npx or uvx) (required)") - packageDeployCmd.Flags().StringSliceVar(&packageSecrets, "secrets", []string{}, "List of Kubernetes secret names to mount") + packageDeployCmd.Flags().StringSliceVar( + &packageSecrets, + "secrets", + []string{}, + "List of Kubernetes secret names to mount", + ) // Add common deployment flags - packageDeployCmd.Flags().StringSliceVar(&deployArgs, "args", []string{}, "Arguments to pass to the package manager (e.g., package names) (required)") + packageDeployCmd.Flags().StringSliceVar( + &deployArgs, + "args", + []string{}, + "Arguments to pass to the package manager (e.g., package names) (required)", + ) packageDeployCmd.Flags().StringSliceVar(&deployEnv, "env", []string{}, "Environment variables (KEY=VALUE)") packageDeployCmd.Flags().BoolVar(&deployDryRun, "dry-run", false, "Generate manifest without applying to cluster") packageDeployCmd.Flags().StringVarP(&deployNamespace, "namespace", "n", "", "Kubernetes namespace") packageDeployCmd.Flags().StringVar(&deployImage, "image", "", "Docker image to deploy (overrides default)") packageDeployCmd.Flags().StringVar(&deployTransport, "transport", "", "Transport type (stdio, http)") packageDeployCmd.Flags().IntVar(&deployPort, "port", 0, "Container port (default: 3000)") - packageDeployCmd.Flags().BoolVar(&deployNoInspector, "no-inspector", false, "Do not start the MCP inspector after deployment") + packageDeployCmd.Flags().BoolVar( + &deployNoInspector, + "no-inspector", + false, + "Do not start the MCP inspector after deployment", + ) packageDeployCmd.Flags().StringVarP(&deployOutput, "output", "o", "", "Output file for the generated YAML") // Mark required flags @@ -156,7 +173,7 @@ func init() { _ = packageDeployCmd.MarkFlagRequired("args") } -func runPackageDeploy(_ *cobra.Command, args []string) error { +func runPackageDeploy(_ *cobra.Command, _ []string) error { // Validate package manager if packageManager != "npx" && packageManager != "uvx" { return fmt.Errorf("unsupported package manager: %s (must be 'npx' or 'uvx')", packageManager) @@ -530,7 +547,12 @@ func getDefaultArgs(framework string, targetPort int) []string { return []string{"dist/index.js"} case manifest.FrameworkJava: if deployTransport == transportHTTP { - return []string{"-jar", "app.jar", "--transport", "http", "--host", "0.0.0.0", "--port", fmt.Sprintf("%d", targetPort)} + return []string{ + "-jar", "app.jar", + "--transport", "http", + "--host", "0.0.0.0", + "--port", fmt.Sprintf("%d", targetPort), + } } return []string{"-jar", "app.jar"} default: diff --git a/pkg/cli/internal/commands/run.go b/pkg/cli/internal/commands/run.go index 0dcf251..fd83b18 100644 --- a/pkg/cli/internal/commands/run.go +++ b/pkg/cli/internal/commands/run.go @@ -244,7 +244,10 @@ func runTypeScript(projectDir string, manifest *manifest.ProjectManifest) error // Check if npm is available if _, err := exec.LookPath("npm"); err != nil { npmInstallURL := "https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" - return fmt.Errorf("npm is required to run TypeScript projects locally. Please install Node.js and npm: %s", npmInstallURL) + return fmt.Errorf( + "npm is required to run TypeScript projects locally. Please install Node.js and npm: %s", + npmInstallURL, + ) } // Install dependencies first @@ -317,7 +320,10 @@ func runJava(projectDir string, manifest *manifest.ProjectManifest) error { if noInspector { // Run the server directly if runTransport == transportHTTP { - fmt.Printf("Running server directly: mvn exec:java -Dexec.mainClass=\"com.example.Main\" --transport http --host 0.0.0.0 --port 3000\n") + fmt.Printf( + "Running server directly: mvn exec:java -Dexec.mainClass=\"com.example.Main\" " + + "--transport http --host 0.0.0.0 --port 3000\n", + ) fmt.Printf("Server is running on http://localhost:3000\n") fmt.Printf("Health check: http://localhost:3000/health\n") fmt.Printf("MCP endpoint: http://localhost:3000/mcp\n") diff --git a/pkg/cli/internal/commands/secrets.go b/pkg/cli/internal/commands/secrets.go index 6ef21c8..f000a92 100644 --- a/pkg/cli/internal/commands/secrets.go +++ b/pkg/cli/internal/commands/secrets.go @@ -30,6 +30,8 @@ var ( ) // syncCmd creates or updates a Kubernetes secret from an environment file +// +//nolint:lll // Long example lines in command description var syncCmd = &cobra.Command{ Use: "sync [environment]", Short: "Sync secrets to a Kubernetes environment from a local .env file", diff --git a/pkg/cli/internal/themes/logo.go b/pkg/cli/internal/themes/logo.go index 757fd3a..6f4fffb 100644 --- a/pkg/cli/internal/themes/logo.go +++ b/pkg/cli/internal/themes/logo.go @@ -7,10 +7,10 @@ import ( ) //go:embed kmcp-ascii.txt -var kmcpLogoAscii string +var kmcpLogoASCII string func KmcpLogo() string { - return kmcpLogoAscii + return kmcpLogoASCII } func ColoredKmcpLogo() string { diff --git a/pkg/cli/root.go b/pkg/cli/root.go index fa2a762..8684586 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -27,7 +27,7 @@ KMCP is a CLI tool for building and managing Model Context Protocol (MCP) server It provides a unified development experience for creating, building, and deploying MCP servers locally and to Kubernetes clusters.`, themes.ColoredKmcpLogo()), Version: version, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() }, }