Skip to content

Commit 2e54180

Browse files
JoannaaKLCopilot
andauthored
Add lockdown mode to filter issue (#1371)
* Add lockdown mode to filter issue * Update flag name * Update pkg/lockdown/lockdown.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Merge two graphql queries into one * Don't use Issue.Repository * Add function signature --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e26cf42 commit 2e54180

File tree

11 files changed

+290
-55
lines changed

11 files changed

+290
-55
lines changed

cmd/github-mcp-server/generate_docs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func generateReadmeDocs(readmePath string) error {
6464
t, _ := translations.TranslationHelper()
6565

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

6969
// Generate toolsets documentation
7070
toolsetsDoc := generateToolsetsDoc(tsg)
@@ -302,7 +302,7 @@ func generateRemoteToolsetsDoc() string {
302302
t, _ := translations.TranslationHelper()
303303

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

307307
// Generate table header
308308
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")

cmd/github-mcp-server/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var (
6161
EnableCommandLogging: viper.GetBool("enable-command-logging"),
6262
LogFilePath: viper.GetString("log-file"),
6363
ContentWindowSize: viper.GetInt("content-window-size"),
64+
LockdownMode: viper.GetBool("lockdown-mode"),
6465
}
6566
return ghmcp.RunStdioServer(stdioServerConfig)
6667
},
@@ -82,6 +83,7 @@ func init() {
8283
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
8384
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
8485
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
86+
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
8587

8688
// Bind flag to viper
8789
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
@@ -92,6 +94,7 @@ func init() {
9294
_ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
9395
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
9496
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
97+
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
9598

9699
// Add subcommands
97100
rootCmd.AddCommand(stdioCmd)

internal/ghmcp/server.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ type MCPServerConfig struct {
5151

5252
// Content window size
5353
ContentWindowSize int
54+
55+
// LockdownMode indicates if we should enable lockdown mode
56+
LockdownMode bool
5457
}
5558

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

156159
// Create default toolsets
157-
tsg := github.DefaultToolsetGroup(cfg.ReadOnly, getClient, getGQLClient, getRawClient, cfg.Translator, cfg.ContentWindowSize)
160+
tsg := github.DefaultToolsetGroup(
161+
cfg.ReadOnly,
162+
getClient,
163+
getGQLClient,
164+
getRawClient,
165+
cfg.Translator,
166+
cfg.ContentWindowSize,
167+
github.FeatureFlags{LockdownMode: cfg.LockdownMode},
168+
)
158169
err = tsg.EnableToolsets(enabledToolsets, nil)
159170

160171
if err != nil {
@@ -205,6 +216,9 @@ type StdioServerConfig struct {
205216

206217
// Content window size
207218
ContentWindowSize int
219+
220+
// LockdownMode indicates if we should enable lockdown mode
221+
LockdownMode bool
208222
}
209223

210224
// RunStdioServer is not concurrent safe.
@@ -224,6 +238,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
224238
ReadOnly: cfg.ReadOnly,
225239
Translator: t,
226240
ContentWindowSize: cfg.ContentWindowSize,
241+
LockdownMode: cfg.LockdownMode,
227242
})
228243
if err != nil {
229244
return fmt.Errorf("failed to create MCP server: %w", err)
@@ -245,7 +260,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
245260
slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo})
246261
}
247262
logger := slog.New(slogHandler)
248-
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly)
263+
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode)
249264
stdLogger := log.New(logOutput, stdioServerLogPrefix, 0)
250265
stdioServer.SetErrorLogger(stdLogger)
251266

pkg/github/feature_flags.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package github
2+
3+
// FeatureFlags defines runtime feature toggles that adjust tool behavior.
4+
type FeatureFlags struct {
5+
LockdownMode bool
6+
}

pkg/github/issues.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
ghErrors "github.com/github/github-mcp-server/pkg/errors"
13+
"github.com/github/github-mcp-server/pkg/lockdown"
1314
"github.com/github/github-mcp-server/pkg/sanitize"
1415
"github.com/github/github-mcp-server/pkg/translations"
1516
"github.com/go-viper/mapstructure/v2"
@@ -227,7 +228,7 @@ func fragmentToIssue(fragment IssueFragment) *github.Issue {
227228
}
228229

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

297298
switch method {
298299
case "get":
299-
return GetIssue(ctx, client, owner, repo, issueNumber)
300+
return GetIssue(ctx, client, gqlClient, owner, repo, issueNumber, flags)
300301
case "get_comments":
301-
return GetIssueComments(ctx, client, owner, repo, issueNumber, pagination)
302+
return GetIssueComments(ctx, client, owner, repo, issueNumber, pagination, flags)
302303
case "get_sub_issues":
303-
return GetSubIssues(ctx, client, owner, repo, issueNumber, pagination)
304+
return GetSubIssues(ctx, client, owner, repo, issueNumber, pagination, flags)
304305
case "get_labels":
305-
return GetIssueLabels(ctx, gqlClient, owner, repo, issueNumber)
306+
return GetIssueLabels(ctx, gqlClient, owner, repo, issueNumber, flags)
306307
default:
307308
return mcp.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil
308309
}
309310
}
310311
}
311312

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

328+
if flags.LockdownMode {
329+
if issue.User != nil {
330+
shouldRemoveContent, err := lockdown.ShouldRemoveContent(ctx, gqlClient, *issue.User.Login, owner, repo)
331+
if err != nil {
332+
return mcp.NewToolResultError(fmt.Sprintf("failed to check lockdown mode: %v", err)), nil
333+
}
334+
if shouldRemoveContent {
335+
return mcp.NewToolResultError("access to issue details is restricted by lockdown mode"), nil
336+
}
337+
}
338+
}
339+
327340
// Sanitize title/body on response
328341
if issue != nil {
329342
if issue.Title != nil {
@@ -342,7 +355,7 @@ func GetIssue(ctx context.Context, client *github.Client, owner string, repo str
342355
return mcp.NewToolResultText(string(r)), nil
343356
}
344357

345-
func GetIssueComments(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int, pagination PaginationParams) (*mcp.CallToolResult, error) {
358+
func GetIssueComments(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int, pagination PaginationParams, _ FeatureFlags) (*mcp.CallToolResult, error) {
346359
opts := &github.IssueListCommentsOptions{
347360
ListOptions: github.ListOptions{
348361
Page: pagination.Page,
@@ -372,7 +385,7 @@ func GetIssueComments(ctx context.Context, client *github.Client, owner string,
372385
return mcp.NewToolResultText(string(r)), nil
373386
}
374387

375-
func GetSubIssues(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int, pagination PaginationParams) (*mcp.CallToolResult, error) {
388+
func GetSubIssues(ctx context.Context, client *github.Client, owner string, repo string, issueNumber int, pagination PaginationParams, _ FeatureFlags) (*mcp.CallToolResult, error) {
376389
opts := &github.IssueListOptions{
377390
ListOptions: github.ListOptions{
378391
Page: pagination.Page,
@@ -407,7 +420,7 @@ func GetSubIssues(ctx context.Context, client *github.Client, owner string, repo
407420
return mcp.NewToolResultText(string(r)), nil
408421
}
409422

410-
func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string, repo string, issueNumber int) (*mcp.CallToolResult, error) {
423+
func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string, repo string, issueNumber int, _ FeatureFlags) (*mcp.CallToolResult, error) {
411424
// Get current labels on the issue using GraphQL
412425
var query struct {
413426
Repository struct {

0 commit comments

Comments
 (0)