@@ -11,6 +11,7 @@ import (
11
11
"os/signal"
12
12
"strings"
13
13
"syscall"
14
+ "time"
14
15
15
16
"github.com/github/github-mcp-server/pkg/errors"
16
17
"github.com/github/github-mcp-server/pkg/github"
@@ -251,6 +252,239 @@ func RunStdioServer(cfg StdioServerConfig) error {
251
252
return nil
252
253
}
253
254
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
+
254
488
type apiHost struct {
255
489
baseRESTURL * url.URL
256
490
graphqlURL * url.URL
0 commit comments