Skip to content

Commit cf8c56c

Browse files
committed
add header support in http entry point
1 parent 0b5601b commit cf8c56c

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

pkg/context/request.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ func IsInsidersMode(ctx context.Context) bool {
8282
return false
8383
}
8484

85+
// disallowedToolsCtxKey is a context key for disallowed tools
86+
type disallowedToolsCtxKey struct{}
87+
88+
// WithDisallowedTools adds the disallowed tools to the context
89+
func WithDisallowedTools(ctx context.Context, tools []string) context.Context {
90+
return context.WithValue(ctx, disallowedToolsCtxKey{}, tools)
91+
}
92+
93+
// GetDisallowedTools retrieves the disallowed tools from the context
94+
func GetDisallowedTools(ctx context.Context) []string {
95+
if tools, ok := ctx.Value(disallowedToolsCtxKey{}).([]string); ok {
96+
return tools
97+
}
98+
return nil
99+
}
100+
85101
// headerFeaturesCtxKey is a context key for raw header feature flags
86102
type headerFeaturesCtxKey struct{}
87103

pkg/http/handler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ func InventoryFiltersForRequest(r *http.Request, builder *inventory.Builder) *in
272272
builder = builder.WithTools(github.CleanTools(tools))
273273
}
274274

275+
if disallowed := ghcontext.GetDisallowedTools(ctx); len(disallowed) > 0 {
276+
builder = builder.WithDisallowedTools(disallowed)
277+
}
278+
275279
return builder
276280
}
277281

pkg/http/handler_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,31 @@ func TestInventoryFiltersForRequest(t *testing.T) {
104104
},
105105
expectedTools: []string{"get_file_contents", "create_repository", "list_issues"},
106106
},
107+
{
108+
name: "disallowed tools removes specific tools",
109+
contextSetup: func(ctx context.Context) context.Context {
110+
return ghcontext.WithDisallowedTools(ctx, []string{"create_repository", "issue_write"})
111+
},
112+
expectedTools: []string{"get_file_contents", "list_issues"},
113+
},
114+
{
115+
name: "disallowed tools overrides explicit tools",
116+
contextSetup: func(ctx context.Context) context.Context {
117+
ctx = ghcontext.WithTools(ctx, []string{"list_issues", "create_repository"})
118+
ctx = ghcontext.WithDisallowedTools(ctx, []string{"create_repository"})
119+
return ctx
120+
},
121+
expectedTools: []string{"list_issues"},
122+
},
123+
{
124+
name: "disallowed tools combines with readonly",
125+
contextSetup: func(ctx context.Context) context.Context {
126+
ctx = ghcontext.WithReadonly(ctx, true)
127+
ctx = ghcontext.WithDisallowedTools(ctx, []string{"list_issues"})
128+
return ctx
129+
},
130+
expectedTools: []string{"get_file_contents"},
131+
},
107132
}
108133

109134
for _, tt := range tests {
@@ -267,6 +292,40 @@ func TestHTTPHandlerRoutes(t *testing.T) {
267292
},
268293
expectedTools: []string{"get_file_contents", "create_repository", "list_issues", "create_issue", "list_pull_requests", "create_pull_request", "hidden_by_holdback"},
269294
},
295+
{
296+
name: "X-MCP-Disallowed-Tools header removes specific tools",
297+
path: "/",
298+
headers: map[string]string{
299+
headers.MCPDisallowedToolsHeader: "create_issue,create_pull_request",
300+
},
301+
expectedTools: []string{"get_file_contents", "create_repository", "list_issues", "list_pull_requests", "hidden_by_holdback"},
302+
},
303+
{
304+
name: "X-MCP-Disallowed-Tools with toolset header",
305+
path: "/",
306+
headers: map[string]string{
307+
headers.MCPToolsetsHeader: "issues",
308+
headers.MCPDisallowedToolsHeader: "create_issue",
309+
},
310+
expectedTools: []string{"list_issues"},
311+
},
312+
{
313+
name: "X-MCP-Disallowed-Tools overrides X-MCP-Tools",
314+
path: "/",
315+
headers: map[string]string{
316+
headers.MCPToolsHeader: "list_issues,create_issue",
317+
headers.MCPDisallowedToolsHeader: "create_issue",
318+
},
319+
expectedTools: []string{"list_issues"},
320+
},
321+
{
322+
name: "X-MCP-Disallowed-Tools with readonly path",
323+
path: "/readonly",
324+
headers: map[string]string{
325+
headers.MCPDisallowedToolsHeader: "list_issues",
326+
},
327+
expectedTools: []string{"get_file_contents", "list_pull_requests", "hidden_by_holdback"},
328+
},
270329
}
271330

272331
for _, tt := range tests {

pkg/http/headers/headers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const (
4141
MCPLockdownHeader = "X-MCP-Lockdown"
4242
// MCPInsidersHeader indicates whether insiders mode is enabled for early access features.
4343
MCPInsidersHeader = "X-MCP-Insiders"
44+
// MCPDisallowedToolsHeader is a comma-separated list of MCP tools that should be
45+
// disabled regardless of other settings or header values.
46+
MCPDisallowedToolsHeader = "X-MCP-Disallowed-Tools"
4447
// MCPFeaturesHeader is a comma-separated list of feature flags to enable.
4548
MCPFeaturesHeader = "X-MCP-Features"
4649

pkg/http/middleware/request_config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ func WithRequestConfig(next http.Handler) http.Handler {
3535
ctx = ghcontext.WithLockdownMode(ctx, true)
3636
}
3737

38+
// Disallowed tools
39+
if disallowedTools := headers.ParseCommaSeparated(r.Header.Get(headers.MCPDisallowedToolsHeader)); len(disallowedTools) > 0 {
40+
ctx = ghcontext.WithDisallowedTools(ctx, disallowedTools)
41+
}
42+
3843
// Insiders mode
3944
if relaxedParseBool(r.Header.Get(headers.MCPInsidersHeader)) {
4045
ctx = ghcontext.WithInsidersMode(ctx, true)

0 commit comments

Comments
 (0)