Files
William Martin 1a23af0961
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Unit Tests / build (macos-latest) (push) Has been cancelled
Unit Tests / build (ubuntu-latest) (push) Has been cancelled
Unit Tests / build (windows-latest) (push) Has been cancelled
License Check / license-check (push) Has been cancelled
Lint / lint (push) Has been cancelled
WIP: in process e2e
2025-05-06 12:58:39 +02:00

147 lines
3.4 KiB
Go

package github
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"
stdlog "log"
iolog "github.com/github/github-mcp-server/pkg/log"
"github.com/github/github-mcp-server/pkg/translations"
gogithub "github.com/google/go-github/v69/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
type RunConfig struct {
Stdin io.Reader
Stdout io.Writer
Version string
Token string
Logger *log.Logger
LogCommands bool
ReadOnly bool
ExportTranslations bool
EnabledToolsets []string
}
func RunStdioServer(cfg RunConfig) error {
// Create app context
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// Create GH client
ghClient := gogithub.NewClient(nil).WithAuthToken(cfg.Token)
ghClient.UserAgent = fmt.Sprintf("github-mcp-server/%s", cfg.Version)
host := viper.GetString("host")
if host != "" {
var err error
ghClient, err = ghClient.WithEnterpriseURLs(host, host)
if err != nil {
return fmt.Errorf("failed to create GitHub client with host: %w", err)
}
}
t, dumpTranslations := translations.TranslationHelper()
beforeInit := func(_ context.Context, _ any, message *mcp.InitializeRequest) {
ghClient.UserAgent = fmt.Sprintf(
"github-mcp-server/%s (%s/%s)",
cfg.Version,
message.Params.ClientInfo.Name,
message.Params.ClientInfo.Version,
)
}
getClient := func(_ context.Context) (*gogithub.Client, error) {
return ghClient, nil // closing over client
}
hooks := &server.Hooks{
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
}
// Create server
ghServer := NewServer(cfg.Version, server.WithHooks(hooks))
enabled := cfg.EnabledToolsets
// TODO: tear this out
dynamic := viper.GetBool("dynamic_toolsets")
if dynamic {
// filter "all" from the enabled toolsets
enabled = make([]string, 0, len(cfg.EnabledToolsets))
for _, toolset := range cfg.EnabledToolsets {
if toolset != "all" {
enabled = append(enabled, toolset)
}
}
}
// Create default toolsets
toolsets, err := InitToolsets(enabled, cfg.ReadOnly, getClient, t)
if err != nil {
cfg.Logger.Fatal("Failed to initialize toolsets:", err)
}
context := InitContextToolset(getClient, t)
// Register resources with the server
RegisterResources(ghServer, getClient, t)
// Register the tools with the server
toolsets.RegisterTools(ghServer)
context.RegisterTools(ghServer)
if dynamic {
dynamic := InitDynamicToolset(ghServer, toolsets, t)
dynamic.RegisterTools(ghServer)
}
stdioServer := server.NewStdioServer(ghServer)
stdLogger := stdlog.New(cfg.Logger.Writer(), "stdioserver", 0)
stdioServer.SetErrorLogger(stdLogger)
if cfg.ExportTranslations {
// Once server is initialized, all translations are loaded
dumpTranslations()
}
// Start listening for messages
errC := make(chan error, 1)
go func() {
in, out := cfg.Stdin, cfg.Stdout
if cfg.LogCommands {
loggedIO := iolog.NewIOLogger(in, out, cfg.Logger)
in, out = loggedIO, loggedIO
}
errC <- stdioServer.Listen(ctx, in, out)
}()
// Output github-mcp-server string
_, _ = fmt.Fprintf(os.Stderr, "GitHub MCP Server running on stdio\n")
// Wait for shutdown signal
select {
case <-ctx.Done():
cfg.Logger.Infof("shutting down server...")
case err := <-errC:
if err != nil {
return fmt.Errorf("error running server: %w", err)
}
}
return nil
}