diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index cad002666..73095764d 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -59,6 +59,23 @@ var ( return ghmcp.RunStdioServer(stdioServerConfig) }, } + + webCmd = &cobra.Command{ + Use: "web", + Short: "Start web server with login page", + Long: `Start a web server that serves a simple login page with username and password fields.`, + RunE: func(_ *cobra.Command, _ []string) error { + port := viper.GetString("port") + if port == "" { + port = "8080" + } + + webServerConfig := ghmcp.WebServerConfig{ + Port: port, + } + return ghmcp.RunWebServer(webServerConfig) + }, + } ) func init() { @@ -76,6 +93,9 @@ func init() { rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file") rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)") + // Add web-specific flags + webCmd.Flags().String("port", "8080", "Port to serve the web interface on") + // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) _ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets")) @@ -84,9 +104,11 @@ func init() { _ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging")) _ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations")) _ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host")) + _ = viper.BindPFlag("port", webCmd.Flags().Lookup("port")) // Add subcommands rootCmd.AddCommand(stdioCmd) + rootCmd.AddCommand(webCmd) } func initConfig() { diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index d993b130a..6816aa8bd 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -11,6 +11,7 @@ import ( "os/signal" "strings" "syscall" + "time" "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/github" @@ -251,6 +252,239 @@ func RunStdioServer(cfg StdioServerConfig) error { return nil } +type WebServerConfig struct { + // Port to serve the web interface on + Port string +} + +// RunWebServer starts a simple HTTP server with a login page +func RunWebServer(cfg WebServerConfig) error { + // Create app context + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + mux := http.NewServeMux() + + // Serve the login page + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "text/html") + _, _ = w.Write([]byte(loginPageHTML)) + }) + + // Serve basic CSS + mux.HandleFunc("/style.css", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/css") + _, _ = w.Write([]byte(loginPageCSS)) + }) + + // Handle login form submission (just echo back for now) + mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + username := r.FormValue("username") + _ = r.FormValue("password") // password not used in this basic implementation + + // For now, just display a simple response + w.Header().Set("Content-Type", "text/html") + fmt.Fprintf(w, ` + + + Login Attempt + + + +
+

Login Attempt

+

Username: %s

+

Password: ***

+

Note: This is just a UI mockup. No actual authentication is performed.

+ Back to Login +
+ + + `, username) + }) + + server := &http.Server{ + Addr: ":" + cfg.Port, + Handler: mux, + ReadHeaderTimeout: 30 * time.Second, // 30 seconds, prevents Slowloris attacks + } + + // Start server in a goroutine + errC := make(chan error, 1) + go func() { + fmt.Printf("Starting web server on http://localhost:%s\n", cfg.Port) + errC <- server.ListenAndServe() + }() + + // Wait for shutdown signal + select { + case <-ctx.Done(): + fmt.Println("Shutting down web server...") + return server.Shutdown(context.Background()) + case err := <-errC: + if err != nil && err != http.ErrServerClosed { + return fmt.Errorf("error running web server: %w", err) + } + } + + return nil +} + +// loginPageHTML contains the HTML for the simple login page +const loginPageHTML = ` + + + + + GitHub MCP Server - Login + + + +
+
+

GitHub MCP Server

+

Login

+
+
+ + +
+
+ + +
+ +
+

This is a basic login interface for future authentication features.

+
+
+ +` + +// loginPageCSS contains the CSS styling for the login page +const loginPageCSS = ` +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: #f6f8fa; + color: #24292f; + line-height: 1.5; +} + +.container { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 20px; +} + +.login-form { + background: white; + border: 1px solid #d0d7de; + border-radius: 6px; + padding: 32px; + box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2); + max-width: 400px; + width: 100%; +} + +h1 { + text-align: center; + margin-bottom: 8px; + color: #0969da; + font-size: 24px; +} + +h2 { + text-align: center; + margin-bottom: 24px; + color: #656d76; + font-size: 20px; + font-weight: normal; +} + +.form-group { + margin-bottom: 16px; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: 600; + font-size: 14px; +} + +input[type="text"], +input[type="password"] { + width: 100%; + padding: 12px; + border: 1px solid #d0d7de; + border-radius: 6px; + font-size: 14px; + transition: border-color 0.2s; +} + +input[type="text"]:focus, +input[type="password"]:focus { + outline: none; + border-color: #0969da; + box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.1); +} + +.login-btn { + width: 100%; + background-color: #238636; + color: white; + border: none; + border-radius: 6px; + padding: 12px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} + +.login-btn:hover { + background-color: #2ea043; +} + +.login-btn:active { + background-color: #1a7f37; +} + +.note { + text-align: center; + margin-top: 24px; + font-size: 12px; + color: #656d76; + font-style: italic; +} + +a { + color: #0969da; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} +` + type apiHost struct { baseRESTURL *url.URL graphqlURL *url.URL pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy