cmd

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 21, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AIO = &cobra.Command{
	Use:              "aio [flags] -- command [args...]",
	Short:            "Start an all-in-one minibridge frontend and backend",
	Args:             cobra.MinimumNArgs(1),
	SilenceUsage:     true,
	SilenceErrors:    true,
	TraverseChildren: true,

	RunE: func(cmd *cobra.Command, args []string) (err error) {

		ctx, cancel := context.WithCancel(cmd.Context())
		defer cancel()

		listen := viper.GetString("listen")
		mcpEndpoint := viper.GetString("endpoint-mcp")
		sseEndpoint := viper.GetString("endpoint-sse")
		messageEndpoint := viper.GetString("endpoint-messages")

		agentAuth, err := makeAgentAuth(true)
		if err != nil {
			return fmt.Errorf("unable to build auth: %w", err)
		}

		policer, penforce, err := makePolicer()
		if err != nil {
			return fmt.Errorf("unable to make policer: %w", err)
		}

		sbom, err := makeSBOM()
		if err != nil {
			return fmt.Errorf("unable to make hashes: %w", err)
		}

		tracer, err := makeTracer(ctx, "aio")
		if err != nil {
			return fmt.Errorf("unable to configure tracer: %w", err)
		}

		corsPolicy := makeCORSPolicy()

		mcpClient, err := makeMCPClient(args, true)
		if err != nil {
			return fmt.Errorf("unable to create MCP client: %w", err)
		}

		mm := startHealthServer(ctx)

		listener := memconn.NewListener()
		defer func() { _ = listener.Close() }()

		var eg errgroup.Group

		var mbackend backend.Backend
		eg.Go(func() error {

			defer cancel()

			slog.Info("Minibridge backend configured")

			mbackend = backend.NewWebSocket("self", nil, mcpClient,
				backend.OptListener(listener),
				backend.OptPolicer(policer),
				backend.OptPolicerEnforce(penforce),
				backend.OptDumpStderrOnError(viper.GetString("log-format") != "json"),
				backend.OptSBOM(sbom),
				backend.OptMetricsManager(mm),
				backend.OptTracer(tracer),
			)

			return mbackend.Start(ctx)
		})

		eg.Go(func() error {

			defer cancel()

			var mfrontend frontend.Frontend

			frontendServerTLSConfig, err := tlsConfigFromFlags(fTLSServer)
			if err != nil {
				return err
			}

			dialer := func(ctx context.Context, network, addr string) (net.Conn, error) {
				return listener.DialContext(cmd.Context(), "127.0.0.1:443")
			}

			if listen != "" {

				slog.Info("Minibridge frontend configured",
					"mcp", mcpEndpoint,
					"sse", sseEndpoint,
					"messages", messageEndpoint,
					"agent-token", agentAuth != nil,
					"mode", "http",
					"server-tls", frontendServerTLSConfig != nil,
					"server-mtls", mtlsMode(frontendServerTLSConfig),
					"listen", listen,
				)

				mfrontend = frontend.NewHTTP(listen, "ws://self/ws", frontendServerTLSConfig, nil,
					frontend.OptHTTPBackendDialer(dialer),
					frontend.OptHTTPMCPEndpoint(mcpEndpoint),
					frontend.OptHTTPSSEEndpoint(sseEndpoint),
					frontend.OptHTTPMessageEndpoint(messageEndpoint),
					frontend.OptHTTPAgentTokenPassthrough(true),
					frontend.OptHTTPCORSPolicy(corsPolicy),
					frontend.OptHTTPMetricsManager(mm),
					frontend.OptHTTPTracer(tracer),
				)
			} else {

				slog.Info("Minibridge frontend configured",
					"mode", "stdio",
				)

				mfrontend = frontend.NewStdio("ws://self/ws", nil,
					frontend.OptStdioBackendDialer(dialer),
					frontend.OptStdioRetry(false),
					frontend.OptStdioTracer(tracer),
				)
			}

			time.Sleep(300 * time.Millisecond)

			return startFrontendWithOAuth(ctx, mfrontend, agentAuth)
		})

		return eg.Wait()
	},
}
View Source
var Backend = &cobra.Command{
	Use:              "backend [flags] -- command [args...]",
	Short:            "Start a minibridge backend to expose an MCP server",
	SilenceUsage:     true,
	SilenceErrors:    true,
	TraverseChildren: true,
	Args:             cobra.MinimumNArgs(1),

	RunE: func(cmd *cobra.Command, args []string) error {

		listen := viper.GetString("listen")

		if listen == "" {
			return fmt.Errorf("--listen must be set")
		}

		backendTLSConfig, err := tlsConfigFromFlags(fTLSServer)
		if err != nil {
			return err
		}

		policer, penforce, err := makePolicer()
		if err != nil {
			return fmt.Errorf("unable to make policer: %w", err)
		}

		sbom, err := makeSBOM()
		if err != nil {
			return fmt.Errorf("unable to make hashes: %w", err)
		}

		tracer, err := makeTracer(cmd.Context(), "backend")
		if err != nil {
			return fmt.Errorf("unable to configure tracer: %w", err)
		}

		corsPolicy := makeCORSPolicy()

		mcpClient, err := makeMCPClient(args, true)
		if err != nil {
			return fmt.Errorf("unable to create MCP client: %w", err)
		}

		mm := startHealthServer(cmd.Context())

		slog.Info("Minibridge backend configured",
			"server-tls", backendTLSConfig != nil,
			"server-mtls", mtlsMode(backendTLSConfig),
			"listen", listen,
		)

		proxy := backend.NewWebSocket(listen, backendTLSConfig, mcpClient,
			backend.OptPolicer(policer),
			backend.OptPolicerEnforce(penforce),
			backend.OptDumpStderrOnError(viper.GetString("log-format") != "json"),
			backend.OptCORSPolicy(corsPolicy),
			backend.OptSBOM(sbom),
			backend.OptMetricsManager(mm),
			backend.OptTracer(tracer),
		)

		return proxy.Start(cmd.Context())
	},
}

Backend is the cobra command to run the server.

View Source
var Completion = &cobra.Command{
	Use:                   "completion [bash|zsh|fish|powershell]",
	Short:                 "Generate completion script",
	DisableFlagsInUseLine: true,
	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
	Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
	RunE: func(cmd *cobra.Command, args []string) error {
		switch args[0] {
		case "bash":
			return cmd.Root().GenBashCompletion(os.Stdout)
		case "zsh":
			return cmd.Root().GenZshCompletion(os.Stdout)
		case "fish":
			return cmd.Root().GenFishCompletion(os.Stdout, true)
		case "powershell":
			return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
		}

		return nil
	},
}
View Source
var Frontend = &cobra.Command{
	Use:              "frontend",
	Short:            "Start a minibridge frontend to connect to a minibridge backend",
	SilenceUsage:     true,
	SilenceErrors:    true,
	TraverseChildren: true,

	RunE: func(cmd *cobra.Command, args []string) error {

		listen := viper.GetString("listen")
		backendURL := viper.GetString("backend")
		mcpEndpoint := viper.GetString("endpoint-mcp")
		sseEndpoint := viper.GetString("endpoint-sse")
		messageEndpoint := viper.GetString("endpoint-messages")
		agentAuthPassthrough := viper.GetBool("agent-auth-passthrough")

		if backendURL == "" {
			return fmt.Errorf("--backend must be set")
		}
		if !strings.HasPrefix(backendURL, "wss://") && !strings.HasPrefix(backendURL, "ws://") {
			return fmt.Errorf("--backend must use wss:// or ws:// scheme")
		}
		if !strings.HasSuffix(backendURL, "/ws") {
			backendURL = backendURL + "/ws"
		}

		agentAuth, err := makeAgentAuth(true)
		if err != nil {
			return fmt.Errorf("unable to build auth: %w", err)
		}

		clientTLSConfig, err := tlsConfigFromFlags(fTLSClient)
		if err != nil {
			return err
		}

		tracer, err := makeTracer(cmd.Context(), "backend")
		if err != nil {
			return fmt.Errorf("unable to configure tracer: %w", err)
		}

		corsPolicy := makeCORSPolicy()

		mm := startHealthServer(cmd.Context())

		var mfrontend frontend.Frontend

		if listen != "" {

			serverTLSConfig, err := tlsConfigFromFlags(fTLSServer)
			if err != nil {
				return err
			}

			slog.Info("Minibridge frontend configured",
				"backend", backendURL,
				"mcp", mcpEndpoint,
				"sse", sseEndpoint,
				"messages", messageEndpoint,
				"mode", "http",
				"server-tls", serverTLSConfig != nil,
				"server-mtls", mtlsMode(serverTLSConfig),
				"client-tls", clientTLSConfig != nil,
				"listen", listen,
			)

			mfrontend = frontend.NewHTTP(listen, backendURL, serverTLSConfig, clientTLSConfig,
				frontend.OptHTTPMCPEndpoint(mcpEndpoint),
				frontend.OptHTTPSSEEndpoint(sseEndpoint),
				frontend.OptHTTPMessageEndpoint(messageEndpoint),
				frontend.OptHTTPAgentTokenPassthrough(agentAuthPassthrough),
				frontend.OptHTTPCORSPolicy(corsPolicy),
				frontend.OptHTTPMetricsManager(mm),
				frontend.OptHTTPTracer(tracer),
			)

		} else {

			slog.Info("Minibridge frontend configured",
				"backend", backendURL,
				"mode", "stdio",
			)

			mfrontend = frontend.NewStdio(backendURL, clientTLSConfig,
				frontend.OptStdioTracer(tracer),
				frontend.OptStdioRetry(false),
			)
		}

		return startFrontendWithOAuth(cmd.Context(), mfrontend, agentAuth)
	},
}

Frontend is the cobra command to run the client.

View Source
var Root = &cobra.Command{
	Use:              "minibridge",
	Short:            "Secure your MCP Servers",
	SilenceUsage:     true,
	SilenceErrors:    true,
	TraverseChildren: true,
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {

		if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil {
			return err
		}

		if err := viper.BindPFlags(cmd.Flags()); err != nil {
			return err
		}

		bootstrap.ConfigureLogger("minibridge", conf.LoggingConf{
			LogLevel:  viper.GetString("log-level"),
			LogFormat: viper.GetString("log-format"),
		})

		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		if viper.GetBool("version") {
			fmt.Println(version.Short())
			os.Exit(0)
		}
		return cmd.Usage()
	},
}
View Source
var Scan = &cobra.Command{
	Use:              "scan [dump|sbom|check file.sbom] -- command [args...]",
	Short:            "Scan an MCP server for resources, prompts, etc and generate sbom",
	SilenceUsage:     true,
	SilenceErrors:    true,
	TraverseChildren: true,
	Args:             cobra.MinimumNArgs(2),

	RunE: func(cmd *cobra.Command, args []string) error {

		timeout := viper.GetDuration("timeout")

		exclusions := &scan.Exclusions{
			Prompts:   viper.GetBool("exclude-prompts"),
			Resources: viper.GetBool("exclude-resources"),
			Tools:     viper.GetBool("exclude-tools"),
		}

		var ctx context.Context
		var cancel context.CancelFunc

		if timeout > 0 {
			ctx, cancel = context.WithTimeout(cmd.Context(), timeout)
		} else {
			ctx, cancel = context.WithCancel(cmd.Context())
		}
		defer cancel()

		var err error
		var mcpCommand string
		var mcpArgs []string
		if args[0] == "check" {
			mcpCommand = args[2]
			mcpArgs = args[3:]
		} else {
			mcpCommand = args[1]
			mcpArgs = args[2:]
		}

		mcpClient, err := makeMCPClient(append([]string{mcpCommand}, mcpArgs...), false)
		if err != nil {
			return err
		}

		agentAuth, err := makeAgentAuth(false)
		if err != nil {
			return fmt.Errorf("unable to build auth: %w", err)
		}

		stream, err := mcpClient.Start(ctx, client.OptionAuth(agentAuth))
		if err != nil {
			return fmt.Errorf("unable to start MCP server: %w", err)
		}

		dump, err := scan.DumpAll(ctx, stream, exclusions)
		if err != nil {
			return fmt.Errorf("unable to dump tools: %w", err)
		}

		cancel()

		var toolHashes scan.Hashes

		if !exclusions.Tools {
			toolHashes, err = scan.HashTools(dump.Tools)
			if err != nil {
				return fmt.Errorf("unable to hash tools: %w", err)
			}
		}

		var promptHashes scan.Hashes

		if !exclusions.Prompts {
			promptHashes, err = scan.HashPrompts(dump.Prompts)
			if err != nil {
				return fmt.Errorf("unable to hash prompts: %w", err)
			}
		}

		sbom := scan.SBOM{
			Tools:   toolHashes,
			Prompts: promptHashes,
		}

		switch args[0] {

		case "check":

			refSBOM, err := scan.LoadSBOM(args[1])
			if err != nil {
				return fmt.Errorf("unable to load sbom: %w", err)
			}

			if err := refSBOM.Tools.Matches(sbom.Tools); err != nil {
				return fmt.Errorf("tools sbom does not match: %w", err)
			}

			if err := refSBOM.Prompts.Matches(sbom.Prompts); err != nil {
				return fmt.Errorf("prompts sbom does not match: %w", err)
			}

		case "sbom":

			enc := json.NewEncoder(os.Stdout)
			enc.SetIndent("", "  ")
			if err := enc.Encode(sbom); err != nil {
				return fmt.Errorf("unable to encode sbom: %w", err)
			}

		case "dump":

			enc := json.NewEncoder(os.Stdout)
			enc.SetIndent("", "  ")
			if err := enc.Encode(dump); err != nil {
				return fmt.Errorf("unable to encode dump: %w", err)
			}

		default:
			return fmt.Errorf("first command must be either dump, sbom or check")
		}

		return nil
	},
}

Scan is the cobra command to run the server.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL