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)
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