server

package
v0.0.0-...-889f6eb Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultLogLevel = slog.LevelInfo
)

Variables

View Source
var Command = &cli.Command{
	Name:        "server",
	Aliases:     []string{"s"},
	Usage:       "Act as the proxy server",
	Description: "It will start as the proxy server and spawn the process at each new connection forwarding the stdin/stdout to the client.",
	ArgsUsage:   "-- path/to/mcpserver [mcpserver args...]",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:             "log-level",
			Usage:            fmt.Sprintf("Set the logging level. Valid values: %s", strings.Join(GetLogLevels(), ", ")),
			Aliases:          []string{"l"},
			OnlyOnce:         true,
			Value:            DefaultLogLevel.String(),
			Destination:      &logLevel,
			ValidateDefaults: true,
			Validator: func(value string) error {
				if !slices.Contains(GetLogLevels(), strings.ToUpper(value)) {
					return fmt.Errorf("invalid log level: %q", value)
				}
				return nil
			},
		},

		&cli.StringFlag{
			Name:        "bind",
			Usage:       "Address to bind the server to. Prefer local addresses if no mTLS. Use 0.0.0.0 to bind to all v4 interfaces and :: to bind to all v6 interfaces.",
			Aliases:     []string{"b"},
			Category:    "HTTP server",
			OnlyOnce:    true,
			Required:    true,
			Destination: &bindAddress,
		},
		&cli.IntFlag{
			Name:             "port",
			Usage:            "Port to use for the server.",
			Aliases:          []string{"p"},
			Category:         "HTTP server",
			OnlyOnce:         true,
			Destination:      &port,
			Value:            protocol.DefaultPort,
			ValidateDefaults: true,
			Validator: func(value int) error {
				if value < 1 {
					return errors.New("must be >= 1")
				}
				if value > 65535 {
					return errors.New("must be <= 65535")
				}
				return nil
			},
		},
		&cli.Int64Flag{
			Name:        "max-connections",
			Usage:       "Maximum number of connections to accept. Each connection spawns a new process. 0 means illimited.",
			Aliases:     []string{"n"},
			Category:    "HTTP server",
			OnlyOnce:    true,
			Destination: &maxConnections,
			Validator: func(value int64) error {
				if value < 0 {
					return errors.New("must be => 0")
				}
				return nil
			},
		},

		&cli.StringFlag{
			Name:     "cert",
			Usage:    "Path to the server certificate file",
			Aliases:  []string{"c"},
			Category: "mTLS",
			OnlyOnce: true,
		},
		&cli.StringFlag{
			Name:     "key",
			Usage:    "Path to the server key file",
			Aliases:  []string{"k"},
			Category: "mTLS",
			OnlyOnce: true,
		},
		&cli.StringFlag{
			Name:     "ca",
			Usage:    "Path to the CA certificate file",
			Aliases:  []string{"a"},
			Category: "mTLS",
			OnlyOnce: true,
		},
	},
	Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {

		if cmd.Args().Len() == 0 {
			return ctx, cli.Exit(errors.New("no command to spawn on incoming connections"), 1)
		}
		mcpServerCmdline = cmd.Args().Slice()
		var err error
		if mcpServerCmdline[0], err = exec.LookPath(mcpServerCmdline[0]); err != nil {
			return ctx, cli.Exit(fmt.Errorf("command %q not found: %w", mcpServerCmdline[0], err), 1)
		}

		certFile := cmd.String("cert")
		keyFile := cmd.String("key")
		caFile := cmd.String("ca")
		if certFile != "" || keyFile != "" || caFile != "" {
			if certFile == "" || keyFile == "" || caFile == "" {
				return ctx, cli.Exit(errors.New("--cert, --key and --ca must be specified together"), 1)
			}
			if tlsConf, err = protocol.GetServerTLSConfig(caFile, certFile, keyFile); err != nil {
				return ctx, cli.Exit(fmt.Errorf("failed to generate server TLS configuration: %w", err), 1)
			}
		}
		return ctx, nil
	},
	Action: func(ctx context.Context, cmd *cli.Command) error {

		logger = CreateLogger(logLevel)
		var scheme string
		if tlsConf == nil {
			scheme = "ws"
		} else {
			scheme = "wss"
		}

		logger.Info("starting proxy server",
			slog.String("listen", fmt.Sprintf("%s://%s:%d", scheme, bindAddress, port)),
			slog.Int64("max-connections", maxConnections),
			slog.String("command", strings.Join(mcpServerCmdline, " ")),
		)
		var requests sync.WaitGroup
		httpServer := &http.Server{
			Addr:    fmt.Sprintf("%s:%d", bindAddress, port),
			Handler: http.HandlerFunc(handleConnection(ctx, &requests)),
		}

		go cleanShutdown(ctx, httpServer)

		if tlsConf != nil {

			ln, err := net.Listen("tcp", httpServer.Addr)
			if err != nil {
				return err
			}
			if err = httpServer.Serve(tls.NewListener(ln, tlsConf)); err != nil && err != http.ErrServerClosed {
				return cli.Exit(fmt.Errorf("failed to run HTTPS server: %w", err), 2)
			}
		} else {
			if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
				return cli.Exit(fmt.Errorf("failed to run HTTP server: %w", err), 2)
			}
		}

		logger.Info("waiting for in-flight websocket requests to end")
		requests.Wait()
		return nil
	},
}

Functions

func CreateLogger

func CreateLogger(requestedLogLevel string) (logger *slog.Logger)

func GetLogLevels

func GetLogLevels() []string

Types

This section is empty.

Jump to

Keyboard shortcuts

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