Skip to content

Commit e2d0d03

Browse files
CopilotAbalure
andcommitted
Add simple login webpage with web server command
Co-authored-by: Abalure <177906163+Abalure@users.noreply.github.com>
1 parent c9949f6 commit e2d0d03

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

cmd/github-mcp-server/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ var (
5959
return ghmcp.RunStdioServer(stdioServerConfig)
6060
},
6161
}
62+
63+
webCmd = &cobra.Command{
64+
Use: "web",
65+
Short: "Start web server with login page",
66+
Long: `Start a web server that serves a simple login page with username and password fields.`,
67+
RunE: func(_ *cobra.Command, _ []string) error {
68+
port := viper.GetString("port")
69+
if port == "" {
70+
port = "8080"
71+
}
72+
73+
webServerConfig := ghmcp.WebServerConfig{
74+
Port: port,
75+
}
76+
return ghmcp.RunWebServer(webServerConfig)
77+
},
78+
}
6279
)
6380

6481
func init() {
@@ -76,6 +93,9 @@ func init() {
7693
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
7794
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
7895

96+
// Add web-specific flags
97+
webCmd.Flags().String("port", "8080", "Port to serve the web interface on")
98+
7999
// Bind flag to viper
80100
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
81101
_ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets"))
@@ -84,9 +104,11 @@ func init() {
84104
_ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging"))
85105
_ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
86106
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
107+
_ = viper.BindPFlag("port", webCmd.Flags().Lookup("port"))
87108

88109
// Add subcommands
89110
rootCmd.AddCommand(stdioCmd)
111+
rootCmd.AddCommand(webCmd)
90112
}
91113

92114
func initConfig() {

github-mcp-server

-18.1 MB
Binary file not shown.

internal/ghmcp/server.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os/signal"
1212
"strings"
1313
"syscall"
14+
"time"
1415

1516
"github.com/github/github-mcp-server/pkg/errors"
1617
"github.com/github/github-mcp-server/pkg/github"
@@ -251,6 +252,239 @@ func RunStdioServer(cfg StdioServerConfig) error {
251252
return nil
252253
}
253254

255+
type WebServerConfig struct {
256+
// Port to serve the web interface on
257+
Port string
258+
}
259+
260+
// RunWebServer starts a simple HTTP server with a login page
261+
func RunWebServer(cfg WebServerConfig) error {
262+
// Create app context
263+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
264+
defer stop()
265+
266+
mux := http.NewServeMux()
267+
268+
// Serve the login page
269+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
270+
if r.URL.Path != "/" {
271+
http.NotFound(w, r)
272+
return
273+
}
274+
w.Header().Set("Content-Type", "text/html")
275+
_, _ = w.Write([]byte(loginPageHTML))
276+
})
277+
278+
// Serve basic CSS
279+
mux.HandleFunc("/style.css", func(w http.ResponseWriter, _ *http.Request) {
280+
w.Header().Set("Content-Type", "text/css")
281+
_, _ = w.Write([]byte(loginPageCSS))
282+
})
283+
284+
// Handle login form submission (just echo back for now)
285+
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
286+
if r.Method != http.MethodPost {
287+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
288+
return
289+
}
290+
291+
username := r.FormValue("username")
292+
_ = r.FormValue("password") // password not used in this basic implementation
293+
294+
// For now, just display a simple response
295+
w.Header().Set("Content-Type", "text/html")
296+
fmt.Fprintf(w, `
297+
<html>
298+
<head>
299+
<title>Login Attempt</title>
300+
<link rel="stylesheet" href="/style.css">
301+
</head>
302+
<body>
303+
<div class="container">
304+
<h1>Login Attempt</h1>
305+
<p>Username: %s</p>
306+
<p>Password: ***</p>
307+
<p><em>Note: This is just a UI mockup. No actual authentication is performed.</em></p>
308+
<a href="/">Back to Login</a>
309+
</div>
310+
</body>
311+
</html>
312+
`, username)
313+
})
314+
315+
server := &http.Server{
316+
Addr: ":" + cfg.Port,
317+
Handler: mux,
318+
ReadHeaderTimeout: 30 * time.Second, // 30 seconds, prevents Slowloris attacks
319+
}
320+
321+
// Start server in a goroutine
322+
errC := make(chan error, 1)
323+
go func() {
324+
fmt.Printf("Starting web server on http://localhost:%s\n", cfg.Port)
325+
errC <- server.ListenAndServe()
326+
}()
327+
328+
// Wait for shutdown signal
329+
select {
330+
case <-ctx.Done():
331+
fmt.Println("Shutting down web server...")
332+
return server.Shutdown(context.Background())
333+
case err := <-errC:
334+
if err != nil && err != http.ErrServerClosed {
335+
return fmt.Errorf("error running web server: %w", err)
336+
}
337+
}
338+
339+
return nil
340+
}
341+
342+
// loginPageHTML contains the HTML for the simple login page
343+
const loginPageHTML = `<!DOCTYPE html>
344+
<html lang="en">
345+
<head>
346+
<meta charset="UTF-8">
347+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
348+
<title>GitHub MCP Server - Login</title>
349+
<link rel="stylesheet" href="/style.css">
350+
</head>
351+
<body>
352+
<div class="container">
353+
<div class="login-form">
354+
<h1>GitHub MCP Server</h1>
355+
<h2>Login</h2>
356+
<form action="/login" method="post">
357+
<div class="form-group">
358+
<label for="username">Username:</label>
359+
<input type="text" id="username" name="username" required>
360+
</div>
361+
<div class="form-group">
362+
<label for="password">Password:</label>
363+
<input type="password" id="password" name="password" required>
364+
</div>
365+
<button type="submit" class="login-btn">Login</button>
366+
</form>
367+
<p class="note">This is a basic login interface for future authentication features.</p>
368+
</div>
369+
</div>
370+
</body>
371+
</html>`
372+
373+
// loginPageCSS contains the CSS styling for the login page
374+
const loginPageCSS = `
375+
* {
376+
margin: 0;
377+
padding: 0;
378+
box-sizing: border-box;
379+
}
380+
381+
body {
382+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
383+
background-color: #f6f8fa;
384+
color: #24292f;
385+
line-height: 1.5;
386+
}
387+
388+
.container {
389+
display: flex;
390+
align-items: center;
391+
justify-content: center;
392+
min-height: 100vh;
393+
padding: 20px;
394+
}
395+
396+
.login-form {
397+
background: white;
398+
border: 1px solid #d0d7de;
399+
border-radius: 6px;
400+
padding: 32px;
401+
box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
402+
max-width: 400px;
403+
width: 100%;
404+
}
405+
406+
h1 {
407+
text-align: center;
408+
margin-bottom: 8px;
409+
color: #0969da;
410+
font-size: 24px;
411+
}
412+
413+
h2 {
414+
text-align: center;
415+
margin-bottom: 24px;
416+
color: #656d76;
417+
font-size: 20px;
418+
font-weight: normal;
419+
}
420+
421+
.form-group {
422+
margin-bottom: 16px;
423+
}
424+
425+
label {
426+
display: block;
427+
margin-bottom: 8px;
428+
font-weight: 600;
429+
font-size: 14px;
430+
}
431+
432+
input[type="text"],
433+
input[type="password"] {
434+
width: 100%;
435+
padding: 12px;
436+
border: 1px solid #d0d7de;
437+
border-radius: 6px;
438+
font-size: 14px;
439+
transition: border-color 0.2s;
440+
}
441+
442+
input[type="text"]:focus,
443+
input[type="password"]:focus {
444+
outline: none;
445+
border-color: #0969da;
446+
box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.1);
447+
}
448+
449+
.login-btn {
450+
width: 100%;
451+
background-color: #238636;
452+
color: white;
453+
border: none;
454+
border-radius: 6px;
455+
padding: 12px;
456+
font-size: 14px;
457+
font-weight: 600;
458+
cursor: pointer;
459+
transition: background-color 0.2s;
460+
}
461+
462+
.login-btn:hover {
463+
background-color: #2ea043;
464+
}
465+
466+
.login-btn:active {
467+
background-color: #1a7f37;
468+
}
469+
470+
.note {
471+
text-align: center;
472+
margin-top: 24px;
473+
font-size: 12px;
474+
color: #656d76;
475+
font-style: italic;
476+
}
477+
478+
a {
479+
color: #0969da;
480+
text-decoration: none;
481+
}
482+
483+
a:hover {
484+
text-decoration: underline;
485+
}
486+
`
487+
254488
type apiHost struct {
255489
baseRESTURL *url.URL
256490
graphqlURL *url.URL

0 commit comments

Comments
 (0)