Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add HTTP server mode with OAuth token support#1216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
talalryz wants to merge3 commits intogithub:main
base:main
Choose a base branch
Loading
fromtalalryz:add-http-oauth-support
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletionsDockerfile
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,9 @@ LABEL io.modelcontextprotocol.server.name="io.github.github/github-mcp-server"
WORKDIR /server
# Copy the binary from the build stage
COPY --from=build /bin/github-mcp-server .

EXPOSE 8080

# Set the entrypoint to the server binary
ENTRYPOINT ["/server/github-mcp-server"]
# Default arguments for ENTRYPOINT
Expand Down
37 changes: 37 additions & 0 deletionsREADME.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -116,6 +116,43 @@ GitHub Enterprise Server does not support remote server hosting. Please refer to

---

##HTTP Server Mode

The GitHub MCP Server can run in HTTP mode, allowing it to serve multiple clients concurrently. This is useful for enterprise scenarios where you want to run a single MCP server instance that handles multiple external clients.

###Starting the HTTP Server

To run the server in HTTP mode, use the`http` command:

```bash
github-mcp-server http --port 8080
```

Or with Docker:

```bash
docker run -p 8080:8080 \
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
ghcr.io/github/github-mcp-server http --port 8080
```

###HTTP Server with "Bring Your Own Token"

When running the server in HTTP mode, clients can provide their own GitHub token with each request using the`Authorization` header:

```http
Authorization: Bearer <github-token>
```

This allows each client to authenticate with their own credentials, enabling:
- Multi-tenant deployments where each user has their own access level
- Enterprise use cases with centralized MCP server infrastructure
- OAuth-based authentication flows

If no`Authorization` header is provided, the server will fall back to using the token specified via the`GITHUB_PERSONAL_ACCESS_TOKEN` environment variable (if configured).

---

##Local GitHub MCP Server

[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&inputs=%5B%7B%22id%22%3A%22github_token%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22GitHub%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22GITHUB_PERSONAL_ACCESS_TOKEN%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agithub_token%7D%22%7D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&inputs=%5B%7B%22id%22%3A%22github_token%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22GitHub%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22GITHUB_PERSONAL_ACCESS_TOKEN%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agithub_token%7D%22%7D%7D&quality=insiders)
Expand Down
37 changes: 37 additions & 0 deletionscmd/github-mcp-server/main.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ var (
Version:fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s",version,commit,date),
}

httpCmd=&cobra.Command{
Use:"http",
Short:"Start HTTP server",
Long:`Start a server that communicates via HTTP using the MCP protocol.`,
RunE:func(_*cobra.Command,_ []string)error {
token:=viper.GetString("personal_access_token")

varenabledToolsets []string
iferr:=viper.UnmarshalKey("toolsets",&enabledToolsets);err!=nil {
returnfmt.Errorf("failed to unmarshal toolsets: %w",err)
}

iflen(enabledToolsets)==0 {
enabledToolsets=github.GetDefaultToolsetIDs()
}

httpServerConfig:= ghmcp.HTTPServerConfig{
Version:version,
Host:viper.GetString("host"),
Token:token,
EnabledToolsets:enabledToolsets,
DynamicToolsets:viper.GetBool("dynamic_toolsets"),
ReadOnly:viper.GetBool("read-only"),
ExportTranslations:viper.GetBool("export-translations"),
EnableCommandLogging:viper.GetBool("enable-command-logging"),
LogFilePath:viper.GetString("log-file"),
ContentWindowSize:viper.GetInt("content-window-size"),
Port:viper.GetInt("port"),
}
returnghmcp.RunHTTPServer(httpServerConfig)
},
}

stdioCmd=&cobra.Command{
Use:"stdio",
Short:"Start stdio server",
Expand DownExpand Up@@ -95,6 +128,10 @@ func init() {

// Add subcommands
rootCmd.AddCommand(stdioCmd)
rootCmd.AddCommand(httpCmd)

httpCmd.Flags().Int("port",8080,"Port to listen on for HTTP server")
_=viper.BindPFlag("port",httpCmd.Flags().Lookup("port"))
}

funcinitConfig() {
Expand Down
1 change: 1 addition & 0 deletionsgo.mod
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ require (
github.com/invopop/jsonschemav0.13.0// indirect
github.com/josharian/internv1.0.0// indirect
github.com/mailru/easyjsonv0.7.7// indirect
github.com/sirupsen/logrusv1.9.3// indirect
github.com/wk8/go-ordered-map/v2v2.1.8// indirect
github.com/yudai/golcsv0.0.0-20170316035057-ecda9a501e82// indirect
golang.org/x/expv0.0.0-20240719175910-8a7402abbf56// indirect
Expand Down
4 changes: 4 additions & 0 deletionsgo.sum
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -72,6 +72,8 @@ github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkv
github.com/shurcooL/githubv4v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
github.com/shurcooL/graphqlv0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphqlv0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/sirupsen/logrusv1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrusv1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/concv0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/concv0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/aferov1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
Expand All@@ -87,6 +89,7 @@ github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqj
github.com/stretchr/objxv0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testifyv1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testifyv1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testifyv1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testifyv1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testifyv1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenvv1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
Expand All@@ -103,6 +106,7 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
golang.org/x/expv0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/oauth2v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sysv0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sysv0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sysv0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/textv0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
Expand Down
151 changes: 149 additions & 2 deletionsinternal/ghmcp/server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,7 @@ import (
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/shurcooL/githubv4"
"github.com/sirupsen/logrus"
)

type MCPServerConfig struct {
Expand DownExpand Up@@ -120,11 +121,39 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
server.WithHooks(hooks),
)

getClient := func(_ context.Context) (*gogithub.Client, error) {
getClient := func(ctx context.Context) (*gogithub.Client, error) {
if tokenVal := ctx.Value(githubTokenKey{}); tokenVal != nil {
if token, ok := tokenVal.(string); ok && token != "" {
client := gogithub.NewClient(nil).WithAuthToken(token)
client.UserAgent = restClient.UserAgent
client.BaseURL = apiHost.baseRESTURL
client.UploadURL = apiHost.uploadURL
return client, nil
}
}
return restClient, nil // closing over client
}

getGQLClient := func(_ context.Context) (*githubv4.Client, error) {
getGQLClient := func(ctx context.Context) (*githubv4.Client, error) {
if tokenVal := ctx.Value(githubTokenKey{}); tokenVal != nil {
if token, ok := tokenVal.(string); ok && token != "" {
httpClient := &http.Client{
Transport: &bearerAuthTransport{
transport: http.DefaultTransport,
token: token,
},
}
if gqlHTTPClient.Transport != nil {
if uaTransport, ok := gqlHTTPClient.Transport.(*userAgentTransport); ok {
httpClient.Transport = &userAgentTransport{
transport: httpClient.Transport,
agent: uaTransport.agent,
}
}
}
return githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), httpClient), nil
}
}
return gqlClient, nil // closing over client
}

Expand DownExpand Up@@ -155,6 +184,46 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
return ghServer, nil
}

type githubTokenKey struct{}

type HTTPServerConfig struct {
// Version of the server
Version string

// GitHub Host to target for API requests (e.g. github.com or github.enterprise.com)
Host string

// GitHub Token to authenticate with the GitHub API (optional for HTTP mode with OAuth)
Token string

// EnabledToolsets is a list of toolsets to enable
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration
EnabledToolsets []string

// Whether to enable dynamic toolsets
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#dynamic-tool-discovery
DynamicToolsets bool

// ReadOnly indicates if we should only register read-only tools
ReadOnly bool

// ExportTranslations indicates if we should export translations
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#i18n--overriding-descriptions
ExportTranslations bool

// EnableCommandLogging indicates if we should log commands
EnableCommandLogging bool

// Path to the log file if not stderr
LogFilePath string

// Content window size
ContentWindowSize int

// Port to listen on for HTTP server
Port int
}

type StdioServerConfig struct {
// Version of the server
Version string
Expand DownExpand Up@@ -190,6 +259,77 @@ type StdioServerConfig struct {
ContentWindowSize int
}

func RunHTTPServer(cfg HTTPServerConfig) error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

t, dumpTranslations := translations.TranslationHelper()

ghServer, err := NewMCPServer(MCPServerConfig{
Version: cfg.Version,
Host: cfg.Host,
Token: cfg.Token,
EnabledToolsets: cfg.EnabledToolsets,
DynamicToolsets: cfg.DynamicToolsets,
ReadOnly: cfg.ReadOnly,
Translator: t,
ContentWindowSize: cfg.ContentWindowSize,
})
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
}

logrusLogger := logrus.New()
if cfg.LogFilePath != "" {
file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[nitpick] The file permissions 0600 are appropriate for log files containing potentially sensitive information, but consider using 0640 if the log file needs to be readable by a logging service or monitoring system running under a different user in the same group.

Suggested change
file,err:=os.OpenFile(cfg.LogFilePath,os.O_CREATE|os.O_WRONLY|os.O_APPEND,0600)
file,err:=os.OpenFile(cfg.LogFilePath,os.O_CREATE|os.O_WRONLY|os.O_APPEND,0640)

Copilot uses AI. Check for mistakes.
if err != nil {
return fmt.Errorf("failed to open log file: %w", err)
}

logrusLogger.SetLevel(logrus.DebugLevel)
logrusLogger.SetOutput(file)
}

httpOptions := []server.StreamableHTTPOption{
server.WithLogger(logrusLogger),
server.WithHeartbeatInterval(30 * time.Second),
server.WithHTTPContextFunc(extractTokenFromAuthHeader),
}

httpServer := server.NewStreamableHTTPServer(ghServer, httpOptions...)

if cfg.ExportTranslations {
dumpTranslations()
}

addr := fmt.Sprintf(":%d", cfg.Port)
srv := &http.Server{
Addr: addr,
Handler: httpServer,
}

_, _ = fmt.Fprintf(os.Stderr, "GitHub MCP Server running on HTTP at %s\n", addr)

errC := make(chan error, 1)
go func() {
errC <- srv.ListenAndServe()
}()

select {
case <-ctx.Done():
logrusLogger.Infof("Shutting down server...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return srv.Shutdown(shutdownCtx)
case err := <-errC:
if err != nil && err != http.ErrServerClosed {
return fmt.Errorf("error running server: %w", err)
}
}

return nil
}

// RunStdioServer is not concurrent safe.
func RunStdioServer(cfg StdioServerConfig) error {
// Create app context
Expand DownExpand Up@@ -466,6 +606,13 @@ func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro
return t.transport.RoundTrip(req)
}

func extractTokenFromAuthHeader(ctx context.Context, r *http.Request) context.Context {
authHeader := r.Header.Get("Authorization")
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
token := strings.TrimPrefix(authHeader, "Bearer ")
return context.WithValue(ctx, githubTokenKey{}, token)
}
return ctx
// cleanToolsets cleans and handles special toolset keywords:
// - Duplicates are removed from the result
// - Removes whitespaces
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp