Skip to content

Commit 2eda841

Browse files
committed
Add http server with github app auth (#2)
* Add go-githubauth * Add http server with github app auth
1 parent 60a5391 commit 2eda841

File tree

5 files changed

+406
-30
lines changed

5 files changed

+406
-30
lines changed

cmd/github-mcp-server/main.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,53 @@ var (
5959
return ghmcp.RunStdioServer(stdioServerConfig)
6060
},
6161
}
62+
63+
httpCmd = &cobra.Command{
64+
Use: "http",
65+
Short: "Start HTTP server",
66+
Long: `Start a server that communicates via HTTP using the Streamable-HTTP transport.`,
67+
RunE: func(_ *cobra.Command, _ []string) error {
68+
// Check if we have either a personal access token or GitHub App credentials
69+
token := viper.GetString("personal_access_token")
70+
appID := viper.GetString("app_id")
71+
appPrivateKey := viper.GetString("app_private_key")
72+
enableGitHubAppAuth := viper.GetBool("enable_github_app_auth")
73+
74+
if token == "" && (!enableGitHubAppAuth || appID == "" || appPrivateKey == "") {
75+
return errors.New("either GITHUB_PERSONAL_ACCESS_TOKEN or GitHub App credentials (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY) must be set")
76+
}
77+
78+
// If you're wondering why we're not using viper.GetStringSlice("toolsets"),
79+
// it's because viper doesn't handle comma-separated values correctly for env
80+
// vars when using GetStringSlice.
81+
// https://github.com/spf13/viper/issues/380
82+
var enabledToolsets []string
83+
if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil {
84+
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
85+
}
86+
87+
httpServerConfig := ghmcp.HttpServerConfig{
88+
Version: version,
89+
Host: viper.GetString("host"),
90+
Token: token,
91+
EnabledToolsets: enabledToolsets,
92+
DynamicToolsets: viper.GetBool("dynamic_toolsets"),
93+
ReadOnly: viper.GetBool("read-only"),
94+
ExportTranslations: viper.GetBool("export-translations"),
95+
EnableCommandLogging: viper.GetBool("enable-command-logging"),
96+
LogFilePath: viper.GetString("log-file"),
97+
Address: viper.GetString("http_address"),
98+
MCPPath: viper.GetString("http_mcp_path"),
99+
EnableCORS: viper.GetBool("http_enable_cors"),
100+
AppID: appID,
101+
AppPrivateKey: appPrivateKey,
102+
EnableGitHubAppAuth: enableGitHubAppAuth,
103+
InstallationIDHeader: viper.GetString("installation_id_header"),
104+
}
105+
106+
return ghmcp.RunHTTPServer(httpServerConfig)
107+
},
108+
}
62109
)
63110

64111
func init() {
@@ -76,17 +123,36 @@ func init() {
76123
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
77124
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
78125

79-
// Bind flag to viper
126+
// GitHub App authentication flags
127+
rootCmd.PersistentFlags().String("app-id", "", "GitHub App ID for authentication")
128+
rootCmd.PersistentFlags().String("app-private-key", "", "GitHub App private key for authentication")
129+
rootCmd.PersistentFlags().Bool("enable-github-app-auth", false, "Enable GitHub App authentication via custom headers")
130+
rootCmd.PersistentFlags().String("installation-id-header", "X-GitHub-Installation-ID", "Custom header name to read installation ID from")
131+
132+
// HTTP server specific flags
133+
httpCmd.Flags().String("http-address", ":8080", "HTTP server address to bind to")
134+
httpCmd.Flags().String("http-mcp-path", "/mcp", "HTTP path for MCP endpoint")
135+
httpCmd.Flags().Bool("http-enable-cors", false, "Enable CORS for cross-origin requests")
136+
137+
// Bind flags to viper
80138
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
81139
_ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets"))
82140
_ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only"))
83141
_ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
84142
_ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging"))
85143
_ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
86144
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
145+
_ = viper.BindPFlag("app_id", rootCmd.PersistentFlags().Lookup("app-id"))
146+
_ = viper.BindPFlag("app_private_key", rootCmd.PersistentFlags().Lookup("app-private-key"))
147+
_ = viper.BindPFlag("enable_github_app_auth", rootCmd.PersistentFlags().Lookup("enable-github-app-auth"))
148+
_ = viper.BindPFlag("installation_id_header", rootCmd.PersistentFlags().Lookup("installation-id-header"))
149+
_ = viper.BindPFlag("http_address", httpCmd.Flags().Lookup("http-address"))
150+
_ = viper.BindPFlag("http_mcp_path", httpCmd.Flags().Lookup("http-mcp-path"))
151+
_ = viper.BindPFlag("http_enable_cors", httpCmd.Flags().Lookup("http-enable-cors"))
87152

88153
// Add subcommands
89154
rootCmd.AddCommand(stdioCmd)
155+
rootCmd.AddCommand(httpCmd)
90156
}
91157

92158
func initConfig() {

go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ require (
1616
require (
1717
github.com/go-openapi/jsonpointer v0.19.5 // indirect
1818
github.com/go-openapi/swag v0.21.1 // indirect
19+
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
20+
github.com/google/go-github/v69 v69.2.0 // indirect
21+
github.com/jferrl/go-githubauth v1.2.0 // indirect
1922
github.com/josharian/intern v1.0.0 // indirect
2023
github.com/mailru/easyjson v0.7.7 // indirect
2124
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
@@ -30,7 +33,7 @@ require (
3033
github.com/google/go-github/v71 v71.0.0 // indirect
3134
github.com/google/go-querystring v1.1.0 // indirect
3235
github.com/google/uuid v1.6.0 // indirect
33-
github.com/gorilla/mux v1.8.0 // indirect
36+
github.com/gorilla/mux v1.8.1 // indirect
3437
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3538
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
3639
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -45,10 +48,10 @@ require (
4548
github.com/subosito/gotenv v1.6.0 // indirect
4649
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
4750
go.uber.org/multierr v1.11.0 // indirect
48-
golang.org/x/oauth2 v0.29.0 // indirect
51+
golang.org/x/oauth2 v0.30.0 // indirect
4952
golang.org/x/sys v0.31.0 // indirect
5053
golang.org/x/text v0.23.0 // indirect
51-
golang.org/x/time v0.5.0 // indirect
54+
golang.org/x/time v0.6.0 // indirect
5255
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
5356
gopkg.in/yaml.v3 v3.0.1 // indirect
5457
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK
1515
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
1616
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
1717
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
18+
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
19+
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
1820
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1921
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2022
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
23+
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
24+
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
2125
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
2226
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
2327
github.com/google/go-github/v73 v73.0.0 h1:aR+Utnh+Y4mMkS+2qLQwcQ/cF9mOTpdwnzlaw//rG24=
@@ -28,8 +32,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2832
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2933
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
3034
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
35+
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
36+
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
3137
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3238
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
39+
github.com/jferrl/go-githubauth v1.2.0 h1:K138gEpO2e/yBf6OI5Vb7+0xgZZa7N7/su/iAAG0ieU=
40+
github.com/jferrl/go-githubauth v1.2.0/go.mod h1:mglSJcfvt4HSvuzQKYx4vkvi1PtlMj88m2gz660QuC0=
3341
github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y=
3442
github.com/josephburnett/jd v1.9.2/go.mod h1:bImDr8QXpxMb3SD+w1cDRHp97xP6UwI88xUAuxwDQfM=
3543
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -98,13 +106,17 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
98106
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
99107
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
100108
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
109+
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
110+
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
101111
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
102112
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
103113
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
104114
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
105115
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
106116
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
107117
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
118+
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
119+
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
108120
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
109121
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
110122
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/ghmcp/http_server.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
package ghmcp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
"os/signal"
9+
"strconv"
10+
"syscall"
11+
"time"
12+
13+
"github.com/github/github-mcp-server/pkg/translations"
14+
"github.com/mark3labs/mcp-go/server"
15+
"github.com/sirupsen/logrus"
16+
)
17+
18+
type HttpServerConfig struct {
19+
// Version of the server
20+
Version string
21+
22+
// GitHub Host to target for API requests (e.g. github.com or github.enterprise.com)
23+
Host string
24+
25+
// GitHub Token to authenticate with the GitHub API
26+
Token string
27+
28+
// EnabledToolsets is a list of toolsets to enable
29+
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration
30+
EnabledToolsets []string
31+
32+
// Whether to enable dynamic toolsets
33+
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#dynamic-tool-discovery
34+
DynamicToolsets bool
35+
36+
// ReadOnly indicates if we should only register read-only tools
37+
ReadOnly bool
38+
39+
// ExportTranslations indicates if we should export translations
40+
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#i18n--overriding-descriptions
41+
ExportTranslations bool
42+
43+
// EnableCommandLogging indicates if we should log commands
44+
EnableCommandLogging bool
45+
46+
// Path to the log file if not stderr
47+
LogFilePath string
48+
49+
// HTTP server configuration
50+
Address string
51+
52+
// MCP endpoint path (defaults to "/mcp")
53+
MCPPath string
54+
55+
// Enable CORS for cross-origin requests
56+
EnableCORS bool
57+
58+
// GITHUB APP ID
59+
AppID string
60+
61+
// GITHUB APP PRIVATE KEY
62+
AppPrivateKey string
63+
64+
// Whether to enable GitHub App authentication via headers
65+
EnableGitHubAppAuth bool
66+
67+
// Custom header name to read installation ID from (defaults to "X-GitHub-Installation-ID")
68+
InstallationIDHeader string
69+
}
70+
71+
const installationContextKey = "installation_id"
72+
73+
// RunHTTPServer is not concurrent safe.
74+
func RunHTTPServer(cfg HttpServerConfig) error {
75+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
76+
defer stop()
77+
78+
t, dumpTranslations := translations.TranslationHelper()
79+
80+
mcpCfg := MCPServerConfig{
81+
Version: cfg.Version,
82+
Host: cfg.Host,
83+
Token: cfg.Token,
84+
EnabledToolsets: cfg.EnabledToolsets,
85+
DynamicToolsets: cfg.DynamicToolsets,
86+
ReadOnly: cfg.ReadOnly,
87+
Translator: t,
88+
AppID: cfg.AppID,
89+
AppPrivateKey: cfg.AppPrivateKey,
90+
EnableGitHubAppAuth: cfg.EnableGitHubAppAuth,
91+
InstallationIDHeader: cfg.InstallationIDHeader,
92+
}
93+
94+
ghServer, err := NewMCPServer(mcpCfg)
95+
if err != nil {
96+
return fmt.Errorf("failed to create MCP server: %w", err)
97+
}
98+
99+
httpServer := server.NewStreamableHTTPServer(ghServer)
100+
101+
logrusLogger := logrus.New()
102+
if cfg.LogFilePath != "" {
103+
file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
104+
if err != nil {
105+
return fmt.Errorf("failed to open log file: %w", err)
106+
}
107+
logrusLogger.SetLevel(logrus.DebugLevel)
108+
logrusLogger.SetOutput(file)
109+
} else {
110+
logrusLogger.SetLevel(logrus.InfoLevel)
111+
}
112+
113+
if cfg.Address == "" {
114+
cfg.Address = ":8080"
115+
}
116+
if cfg.MCPPath == "" {
117+
cfg.MCPPath = "/mcp"
118+
}
119+
if cfg.InstallationIDHeader == "" {
120+
cfg.InstallationIDHeader = "X-GitHub-Installation-ID"
121+
}
122+
123+
mux := http.NewServeMux()
124+
var handler http.Handler = httpServer
125+
126+
// Apply middlewares in the correct order: CORS first, then auth
127+
if cfg.EnableCORS {
128+
handler = corsMiddleware(handler)
129+
}
130+
if cfg.EnableGitHubAppAuth {
131+
handler = authMiddleware(handler, cfg.InstallationIDHeader, logrusLogger)
132+
}
133+
134+
mux.Handle(cfg.MCPPath, handler)
135+
136+
srv := &http.Server{
137+
Addr: cfg.Address,
138+
Handler: mux,
139+
}
140+
141+
if cfg.ExportTranslations {
142+
dumpTranslations()
143+
}
144+
145+
errC := make(chan error, 1)
146+
go func() {
147+
logrusLogger.Infof("Starting HTTP server on %s", cfg.Address)
148+
logrusLogger.Infof("MCP endpoint available at http://localhost%s%s", cfg.Address, cfg.MCPPath)
149+
if cfg.EnableGitHubAppAuth {
150+
logrusLogger.Infof("GitHub App authentication enabled with header: %s", cfg.InstallationIDHeader)
151+
}
152+
errC <- srv.ListenAndServe()
153+
}()
154+
155+
_, _ = fmt.Fprintf(os.Stderr, "GitHub MCP Server running on HTTP at %s\n", cfg.Address)
156+
_, _ = fmt.Fprintf(os.Stderr, "MCP endpoint: http://localhost%s%s\n", cfg.Address, cfg.MCPPath)
157+
if cfg.EnableGitHubAppAuth {
158+
_, _ = fmt.Fprintf(os.Stderr, "GitHub App authentication enabled with header: %s\n", cfg.InstallationIDHeader)
159+
}
160+
161+
select {
162+
case <-ctx.Done():
163+
logrusLogger.Infof("shutting down server...")
164+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
165+
defer cancel()
166+
if err := srv.Shutdown(shutdownCtx); err != nil {
167+
logrusLogger.Errorf("error during server shutdown: %v", err)
168+
}
169+
case err := <-errC:
170+
if err != nil && err != http.ErrServerClosed {
171+
return fmt.Errorf("error running server: %w", err)
172+
}
173+
}
174+
175+
return nil
176+
}
177+
178+
// corsMiddleware adds CORS headers to allow cross-origin requests
179+
func corsMiddleware(next http.Handler) http.Handler {
180+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181+
// Set CORS headers
182+
w.Header().Set("Access-Control-Allow-Origin", "*")
183+
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
184+
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Host, Origin, Referer, User-Agent")
185+
186+
// Handle preflight requests
187+
if r.Method == "OPTIONS" {
188+
w.WriteHeader(http.StatusOK)
189+
return
190+
}
191+
192+
next.ServeHTTP(w, r)
193+
})
194+
}
195+
196+
// authMiddleware extracts installation IDs from custom headers and adds them to the request context
197+
func authMiddleware(next http.Handler, headerName string, logger *logrus.Logger) http.Handler {
198+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
199+
installationIDStr := r.Header.Get(headerName)
200+
if installationIDStr == "" {
201+
next.ServeHTTP(w, r)
202+
return
203+
}
204+
205+
installationID, err := strconv.ParseInt(installationIDStr, 10, 64)
206+
if err != nil {
207+
logger.Warnf("Invalid installation ID format in header %s", headerName)
208+
http.Error(w, "Invalid installation ID format", http.StatusBadRequest)
209+
return
210+
}
211+
212+
if installationID <= 0 {
213+
logger.Warnf("Invalid installation ID value: %d", installationID)
214+
http.Error(w, "Invalid installation ID value", http.StatusBadRequest)
215+
return
216+
}
217+
218+
ctx := context.WithValue(r.Context(), installationContextKey, installationID)
219+
r = r.WithContext(ctx)
220+
221+
if logger.GetLevel() == logrus.DebugLevel {
222+
logger.Debugf("Authenticated request with installation ID %d", installationID)
223+
} else {
224+
logger.Debug("Request authenticated with GitHub App installation")
225+
}
226+
227+
next.ServeHTTP(w, r)
228+
})
229+
}

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