Skip to content

Commit 1fad6c5

Browse files
authored
Add lockdown mode support for HTTP server (#1876)
* add readonly and toolset support * address feedback * forgotten files * remove redundant checks in WithRequestConfig * move middleware in RegisterRoutes * improve comment and add TestParseCommaSeparated * fix broken TestInventoryFiltersForRequest * parse X-MCP-Tools into ctx * clean up TestInventoryFiltersForRequest * review http args and add lockdown ctx helpers * parse lockdown header and update GetFlags to retrieve ff from ctx * clean up and fix tests * fix GetFlags check, move lockdown parsing in WithRequestConfig and fix broken tests
1 parent 8d25f46 commit 1fad6c5

File tree

10 files changed

+40
-16
lines changed

10 files changed

+40
-16
lines changed

cmd/github-mcp-server/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ var (
9696
Short: "Start HTTP server",
9797
Long: `Start an HTTP server that listens for MCP requests over HTTP.`,
9898
RunE: func(_ *cobra.Command, _ []string) error {
99+
ttl := viper.GetDuration("repo-access-cache-ttl")
99100
httpConfig := ghhttp.HTTPServerConfig{
100101
Version: version,
101102
Host: viper.GetString("host"),
@@ -104,6 +105,8 @@ var (
104105
EnableCommandLogging: viper.GetBool("enable-command-logging"),
105106
LogFilePath: viper.GetString("log-file"),
106107
ContentWindowSize: viper.GetInt("content-window-size"),
108+
LockdownMode: viper.GetBool("lockdown-mode"),
109+
RepoAccessCacheTTL: &ttl,
107110
}
108111

109112
return ghhttp.RunHTTPServer(httpConfig)

pkg/context/request.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,19 @@ func GetTools(ctx context.Context) []string {
4949
}
5050
return nil
5151
}
52+
53+
// lockdownCtxKey is a context key for lockdown mode
54+
type lockdownCtxKey struct{}
55+
56+
// WithLockdownMode adds lockdown mode state to the context
57+
func WithLockdownMode(ctx context.Context, enabled bool) context.Context {
58+
return context.WithValue(ctx, lockdownCtxKey{}, enabled)
59+
}
60+
61+
// IsLockdownMode retrieves the lockdown mode state from the context
62+
func IsLockdownMode(ctx context.Context) bool {
63+
if enabled, ok := ctx.Value(lockdownCtxKey{}).(bool); ok {
64+
return enabled
65+
}
66+
return false
67+
}

pkg/github/dependencies.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type ToolDependencies interface {
8787
GetT() translations.TranslationHelperFunc
8888

8989
// GetFlags returns feature flags
90-
GetFlags() FeatureFlags
90+
GetFlags(ctx context.Context) FeatureFlags
9191

9292
// GetContentWindowSize returns the content window size for log truncation
9393
GetContentWindowSize() int
@@ -165,7 +165,7 @@ func (d BaseDeps) GetRepoAccessCache(_ context.Context) (*lockdown.RepoAccessCac
165165
func (d BaseDeps) GetT() translations.TranslationHelperFunc { return d.T }
166166

167167
// GetFlags implements ToolDependencies.
168-
func (d BaseDeps) GetFlags() FeatureFlags { return d.Flags }
168+
func (d BaseDeps) GetFlags(_ context.Context) FeatureFlags { return d.Flags }
169169

170170
// GetContentWindowSize implements ToolDependencies.
171171
func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize }
@@ -262,7 +262,6 @@ func NewRequestDeps(
262262
lockdownMode bool,
263263
repoAccessOpts []lockdown.RepoAccessOption,
264264
t translations.TranslationHelperFunc,
265-
flags FeatureFlags,
266265
contentWindowSize int,
267266
featureChecker inventory.FeatureFlagChecker,
268267
) *RequestDeps {
@@ -272,7 +271,6 @@ func NewRequestDeps(
272271
lockdownMode: lockdownMode,
273272
RepoAccessOpts: repoAccessOpts,
274273
T: t,
275-
Flags: flags,
276274
ContentWindowSize: contentWindowSize,
277275
featureChecker: featureChecker,
278276
}
@@ -379,7 +377,11 @@ func (d *RequestDeps) GetRepoAccessCache(ctx context.Context) (*lockdown.RepoAcc
379377
func (d *RequestDeps) GetT() translations.TranslationHelperFunc { return d.T }
380378

381379
// GetFlags implements ToolDependencies.
382-
func (d *RequestDeps) GetFlags() FeatureFlags { return d.Flags }
380+
func (d *RequestDeps) GetFlags(ctx context.Context) FeatureFlags {
381+
return FeatureFlags{
382+
LockdownMode: d.lockdownMode && ghcontext.IsLockdownMode(ctx),
383+
}
384+
}
383385

384386
// GetContentWindowSize implements ToolDependencies.
385387
func (d *RequestDeps) GetContentWindowSize() int { return d.ContentWindowSize }

pkg/github/feature_flags_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func HelloWorldTool(t translations.TranslationHelperFunc) inventory.ServerTool {
4545
if deps.IsFeatureEnabled(ctx, RemoteMCPEnthusiasticGreeting) {
4646
greeting += " Welcome to the future of MCP! 🎉"
4747
}
48-
if deps.GetFlags().InsiderMode {
48+
if deps.GetFlags(ctx).InsiderMode {
4949
greeting += " Experimental features are enabled! 🚀"
5050
}
5151

pkg/github/issues.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ func GetIssue(ctx context.Context, client *github.Client, deps ToolDependencies,
334334
if err != nil {
335335
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
336336
}
337-
flags := deps.GetFlags()
337+
flags := deps.GetFlags(ctx)
338338

339339
issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
340340
if err != nil {
@@ -389,7 +389,7 @@ func GetIssueComments(ctx context.Context, client *github.Client, deps ToolDepen
389389
if err != nil {
390390
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
391391
}
392-
flags := deps.GetFlags()
392+
flags := deps.GetFlags(ctx)
393393

394394
opts := &github.IssueListCommentsOptions{
395395
ListOptions: github.ListOptions{
@@ -449,7 +449,7 @@ func GetSubIssues(ctx context.Context, client *github.Client, deps ToolDependenc
449449
if err != nil {
450450
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
451451
}
452-
featureFlags := deps.GetFlags()
452+
featureFlags := deps.GetFlags(ctx)
453453

454454
opts := &github.IssueListOptions{
455455
ListOptions: github.ListOptions{

pkg/github/pullrequests.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func GetPullRequest(ctx context.Context, client *github.Client, deps ToolDepende
139139
if err != nil {
140140
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
141141
}
142-
ff := deps.GetFlags()
142+
ff := deps.GetFlags(ctx)
143143

144144
pr, resp, err := client.PullRequests.Get(ctx, owner, repo, pullNumber)
145145
if err != nil {
@@ -350,7 +350,7 @@ func GetPullRequestReviewComments(ctx context.Context, gqlClient *githubv4.Clien
350350
if err != nil {
351351
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
352352
}
353-
ff := deps.GetFlags()
353+
ff := deps.GetFlags(ctx)
354354

355355
// Convert pagination parameters to GraphQL format
356356
gqlParams, err := pagination.ToGraphQLParams()
@@ -437,7 +437,7 @@ func GetPullRequestReviews(ctx context.Context, client *github.Client, deps Tool
437437
if err != nil {
438438
return nil, fmt.Errorf("failed to get repo access cache: %w", err)
439439
}
440-
ff := deps.GetFlags()
440+
ff := deps.GetFlags(ctx)
441441

442442
reviews, resp, err := client.PullRequests.ListReviews(ctx, owner, repo, pullNumber, nil)
443443
if err != nil {

pkg/github/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (s stubDeps) GetRepoAccessCache(ctx context.Context) (*lockdown.RepoAccessC
5656
return s.repoAccessCache, nil
5757
}
5858
func (s stubDeps) GetT() translations.TranslationHelperFunc { return s.t }
59-
func (s stubDeps) GetFlags() FeatureFlags { return s.flags }
59+
func (s stubDeps) GetFlags(ctx context.Context) FeatureFlags { return s.flags }
6060
func (s stubDeps) GetContentWindowSize() int { return s.contentWindowSize }
6161
func (s stubDeps) IsFeatureEnabled(_ context.Context, _ string) bool { return false }
6262

pkg/http/headers/headers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const (
3232
MCPToolsetsHeader = "X-MCP-Toolsets"
3333
// MCPToolsHeader is a comma-separated list of MCP tools that the request is for.
3434
MCPToolsHeader = "X-MCP-Tools"
35+
// MCPLockdownHeader indicates whether lockdown mode is enabled.
36+
MCPLockdownHeader = "X-MCP-Lockdown"
3537
// MCPFeaturesHeader is a comma-separated list of feature flags to enable.
3638
MCPFeaturesHeader = "X-MCP-Features"
3739
)

pkg/http/middleware/request_config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ func WithRequestConfig(next http.Handler) http.Handler {
2626
ctx = ghcontext.WithTools(ctx, tools)
2727
}
2828

29+
if relaxedParseBool(r.Header.Get(headers.MCPLockdownHeader)) {
30+
ctx = ghcontext.WithLockdownMode(ctx, true)
31+
}
32+
2933
next.ServeHTTP(w, r.WithContext(ctx))
3034
})
3135
}

pkg/http/server.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ func RunHTTPServer(cfg HTTPServerConfig) error {
8989
cfg.LockdownMode,
9090
repoAccessOpts,
9191
t,
92-
github.FeatureFlags{
93-
LockdownMode: cfg.LockdownMode,
94-
},
9592
cfg.ContentWindowSize,
9693
nil,
9794
)

0 commit comments

Comments
 (0)