Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func generateReadmeDocs(readmePath string) error {
t, _ := translations.TranslationHelper()

// Create toolset group with mock clients
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000)
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{})

// Generate toolsets documentation
toolsetsDoc := generateToolsetsDoc(tsg)
Expand Down Expand Up @@ -302,7 +302,7 @@ func generateRemoteToolsetsDoc() string {
t, _ := translations.TranslationHelper()

// Create toolset group with mock clients
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000)
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{})

// Generate table header
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
Expand Down
3 changes: 3 additions & 0 deletions cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ var (
EnableCommandLogging: viper.GetBool("enable-command-logging"),
LogFilePath: viper.GetString("log-file"),
ContentWindowSize: viper.GetInt("content-window-size"),
LockdownEnabled: viper.GetBool("lockdown-enabled"),
}
return ghmcp.RunStdioServer(stdioServerConfig)
},
Expand All @@ -82,6 +83,7 @@ func init() {
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
rootCmd.PersistentFlags().Bool("lockdown-enabled", false, "Enable lockdown mode")

// Bind flag to viper
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
Expand All @@ -92,6 +94,7 @@ func init() {
_ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
_ = viper.BindPFlag("lockdown-enabled", rootCmd.PersistentFlags().Lookup("lockdown-enabled"))

// Add subcommands
rootCmd.AddCommand(stdioCmd)
Expand Down
19 changes: 17 additions & 2 deletions internal/ghmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type MCPServerConfig struct {

// Content window size
ContentWindowSize int

// LockdownEnabled indicates if we should enable lockdown mode
LockdownEnabled bool
}

const stdioServerLogPrefix = "stdioserver"
Expand Down Expand Up @@ -154,7 +157,15 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
}

// Create default toolsets
tsg := github.DefaultToolsetGroup(cfg.ReadOnly, getClient, getGQLClient, getRawClient, cfg.Translator, cfg.ContentWindowSize)
tsg := github.DefaultToolsetGroup(
cfg.ReadOnly,
getClient,
getGQLClient,
getRawClient,
cfg.Translator,
cfg.ContentWindowSize,
github.FeatureFlags{LockdownEnabled: cfg.LockdownEnabled},
)
err = tsg.EnableToolsets(enabledToolsets, nil)

if err != nil {
Expand Down Expand Up @@ -205,6 +216,9 @@ type StdioServerConfig struct {

// Content window size
ContentWindowSize int

// LockdownEnabled indicates if we should enable lockdown mode
LockdownEnabled bool
}

// RunStdioServer is not concurrent safe.
Expand All @@ -224,6 +238,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
ReadOnly: cfg.ReadOnly,
Translator: t,
ContentWindowSize: cfg.ContentWindowSize,
LockdownEnabled: cfg.LockdownEnabled,
})
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
Expand All @@ -245,7 +260,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo})
}
logger := slog.New(slogHandler)
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly)
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownEnabled)
stdLogger := log.New(logOutput, stdioServerLogPrefix, 0)
stdioServer.SetErrorLogger(stdLogger)

Expand Down
6 changes: 6 additions & 0 deletions pkg/github/feature_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package github

// FeatureFlags defines runtime feature toggles that adjust tool behavior.
type FeatureFlags struct {
LockdownEnabled bool
}
14 changes: 11 additions & 3 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/lockdown"
"github.com/github/github-mcp-server/pkg/sanitize"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/go-viper/mapstructure/v2"
Expand Down Expand Up @@ -227,7 +228,7 @@ func fragmentToIssue(fragment IssueFragment) *github.Issue {
}

// GetIssue creates a tool to get details of a specific issue in a GitHub repository.
func IssueRead(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
func IssueRead(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc, flags FeatureFlags) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("issue_read",
mcp.WithDescription(t("TOOL_ISSUE_READ_DESCRIPTION", "Get information about a specific issue in a GitHub repository.")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Expand Down Expand Up @@ -296,7 +297,7 @@ Options are:

switch method {
case "get":
return GetIssue(ctx, client, owner, repo, issueNumber)
return GetIssue(ctx, client, gqlClient, owner, repo, issueNumber, flags)
case "get_comments":
return GetIssueComments(ctx, client, owner, repo, issueNumber, pagination)
case "get_sub_issues":
Expand All @@ -309,7 +310,7 @@ Options are:
}
}

func GetIssue(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int) (*mcp.CallToolResult, error) {
func GetIssue(ctx context.Context, client *github.Client, gqlClient *githubv4.Client, owner string, repo string, issueNumber int, flags FeatureFlags) (*mcp.CallToolResult, error) {
issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
if err != nil {
return nil, fmt.Errorf("failed to get issue: %w", err)
Expand All @@ -324,6 +325,13 @@ func GetIssue(ctx context.Context, client *github.Client, owner string, repo str
return mcp.NewToolResultError(fmt.Sprintf("failed to get issue: %s", string(body))), nil
}

if flags.LockdownEnabled {
shouldFilter := lockdown.ShouldRemoveContent(ctx, gqlClient, *issue.User.Login, *issue.Repository.Owner.Login, *issue.Repository.Name)
if shouldFilter {
return mcp.NewToolResultError("access to issue details is restricted by lockdown mode"), nil
}
}

// Sanitize title/body on response
if issue != nil {
if issue.Title != nil {
Expand Down
Loading
Loading