Skip to content

Commit e91a875

Browse files
committed
address feedback
1 parent c35ab93 commit e91a875

File tree

4 files changed

+30
-66
lines changed

4 files changed

+30
-66
lines changed

pkg/context/request.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ func IsReadonly(ctx context.Context) bool {
1818
return false
1919
}
2020

21-
// toolsetCtxKey is a context key for the active toolset
22-
type toolsetCtxKey struct{}
21+
// toolsetsCtxKey is a context key for the active toolsets
22+
type toolsetsCtxKey struct{}
2323

24-
// WithToolset adds the active toolset to the context
25-
func WithToolset(ctx context.Context, toolset string) context.Context {
26-
return context.WithValue(ctx, toolsetCtxKey{}, toolset)
24+
// WithToolsets adds the active toolsets to the context
25+
func WithToolsets(ctx context.Context, toolsets []string) context.Context {
26+
return context.WithValue(ctx, toolsetsCtxKey{}, toolsets)
2727
}
2828

29-
// GetToolset retrieves the active toolset from the context
30-
func GetToolset(ctx context.Context) string {
31-
if toolset, ok := ctx.Value(toolsetCtxKey{}).(string); ok {
32-
return toolset
29+
// GetToolsets retrieves the active toolsets from the context
30+
func GetToolsets(ctx context.Context) []string {
31+
if toolsets, ok := ctx.Value(toolsetsCtxKey{}).([]string); ok {
32+
return toolsets
3333
}
34-
return ""
34+
return nil
3535
}

pkg/http/handler.go

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"context"
55
"log/slog"
66
"net/http"
7-
"slices"
8-
"strings"
97

108
ghcontext "github.com/github/github-mcp-server/pkg/context"
119
"github.com/github/github-mcp-server/pkg/github"
@@ -99,7 +97,7 @@ func withReadonly(next http.Handler) http.Handler {
9997
func withToolset(next http.Handler) http.Handler {
10098
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10199
toolset := chi.URLParam(r, "toolset")
102-
ctx := ghcontext.WithToolset(r.Context(), toolset)
100+
ctx := ghcontext.WithToolsets(r.Context(), []string{toolset})
103101
next.ServeHTTP(w, r.WithContext(ctx))
104102
})
105103
}
@@ -143,7 +141,7 @@ func DefaultInventoryFactory(cfg *HTTPServerConfig, t translations.TranslationHe
143141
b := github.NewInventory(t).WithDeprecatedAliases(github.DeprecatedToolAliases)
144142

145143
// Feature checker composition
146-
headerFeatures := parseCommaSeparatedHeader(r.Header.Get(headers.MCPFeaturesHeader))
144+
headerFeatures := headers.ParseCommaSeparated(r.Header.Get(headers.MCPFeaturesHeader))
147145
if checker := ComposeFeatureChecker(headerFeatures, staticChecker); checker != nil {
148146
b = b.WithFeatureChecker(checker)
149147
}
@@ -153,62 +151,25 @@ func DefaultInventoryFactory(cfg *HTTPServerConfig, t translations.TranslationHe
153151
}
154152
}
155153

156-
// InventoryFiltersForRequest applies inventory filters from request context and headers
157-
// Whitespace is trimmed from comma-separated values; empty values are ignored
158-
// Route configuration (context) takes precedence over headers for toolsets
154+
// InventoryFiltersForRequest applies filters to the inventory builder
155+
// based on the request context and headers
159156
func InventoryFiltersForRequest(r *http.Request, builder *inventory.Builder) *inventory.Builder {
160157
ctx := r.Context()
161158

162-
// Enable readonly mode if set in context or via header
163-
if ghcontext.IsReadonly(ctx) || relaxedParseBool(r.Header.Get(headers.MCPReadOnlyHeader)) {
159+
if ghcontext.IsReadonly(ctx) {
164160
builder = builder.WithReadOnly(true)
165161
}
166162

167-
// Parse request configuration
168-
contextToolset := ghcontext.GetToolset(ctx)
169-
headerToolsets := parseCommaSeparatedHeader(r.Header.Get(headers.MCPToolsetsHeader))
170-
tools := parseCommaSeparatedHeader(r.Header.Get(headers.MCPToolsHeader))
171-
172-
// Apply toolset filtering (route wins, then header, then tools-only mode, else defaults)
173-
switch {
174-
case contextToolset != "":
175-
builder = builder.WithToolsets([]string{contextToolset})
176-
case len(headerToolsets) > 0:
177-
builder = builder.WithToolsets(headerToolsets)
178-
case len(tools) > 0:
179-
builder = builder.WithToolsets([]string{})
163+
if toolsets := ghcontext.GetToolsets(ctx); len(toolsets) > 0 {
164+
builder = builder.WithToolsets(toolsets)
180165
}
181166

182-
if len(tools) > 0 {
167+
if tools := headers.ParseCommaSeparated(r.Header.Get(headers.MCPToolsHeader)); len(tools) > 0 {
168+
if len(ghcontext.GetToolsets(ctx)) == 0 {
169+
builder = builder.WithToolsets([]string{})
170+
}
183171
builder = builder.WithTools(github.CleanTools(tools))
184172
}
185173

186174
return builder
187175
}
188-
189-
// parseCommaSeparatedHeader splits a header value by comma, trims whitespace,
190-
// and filters out empty values.
191-
func parseCommaSeparatedHeader(value string) []string {
192-
if value == "" {
193-
return []string{}
194-
}
195-
196-
parts := strings.Split(value, ",")
197-
result := make([]string, 0, len(parts))
198-
for _, p := range parts {
199-
trimmed := strings.TrimSpace(p)
200-
if trimmed != "" {
201-
result = append(result, trimmed)
202-
}
203-
}
204-
return result
205-
}
206-
207-
// relaxedParseBool parses a string into a boolean value, treating various
208-
// common false values or empty strings as false, and everything else as true.
209-
// It is case-insensitive and trims whitespace.
210-
func relaxedParseBool(s string) bool {
211-
s = strings.TrimSpace(strings.ToLower(s))
212-
falseValues := []string{"", "false", "0", "no", "off", "n", "f"}
213-
return !slices.Contains(falseValues, s)
214-
}

pkg/http/handler_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,27 @@ func TestInventoryFiltersForRequest(t *testing.T) {
5555
{
5656
name: "toolset from context filters to toolset",
5757
contextSetup: func(ctx context.Context) context.Context {
58-
return ghcontext.WithToolset(ctx, "repos")
58+
return ghcontext.WithToolsets(ctx, []string{"repos"})
5959
},
6060
expectedTools: []string{"get_file_contents", "create_repository"},
6161
},
6262
{
6363
name: "context toolset takes precedence over header",
6464
contextSetup: func(ctx context.Context) context.Context {
65-
return ghcontext.WithToolset(ctx, "repos")
65+
return ghcontext.WithToolsets(ctx, []string{"repos"})
6666
},
6767
headers: map[string]string{
6868
headers.MCPToolsetsHeader: "issues",
6969
},
7070
expectedTools: []string{"get_file_contents", "create_repository"},
7171
},
7272
{
73-
name: "tools are additive with toolsets",
74-
contextSetup: func(ctx context.Context) context.Context { return ctx },
73+
name: "tools are additive with toolsets",
74+
contextSetup: func(ctx context.Context) context.Context {
75+
return ghcontext.WithToolsets(ctx, []string{"repos"})
76+
},
7577
headers: map[string]string{
76-
headers.MCPToolsetsHeader: "repos",
77-
headers.MCPToolsHeader: "list_issues",
78+
headers.MCPToolsHeader: "list_issues",
7879
},
7980
expectedTools: []string{"get_file_contents", "create_repository", "list_issues"},
8081
},

pkg/http/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/github/github-mcp-server/pkg/github"
15+
"github.com/github/github-mcp-server/pkg/http/middleware"
1516
"github.com/github/github-mcp-server/pkg/lockdown"
1617
"github.com/github/github-mcp-server/pkg/translations"
1718
"github.com/github/github-mcp-server/pkg/utils"
@@ -98,6 +99,7 @@ func RunHTTPServer(cfg HTTPServerConfig) error {
9899
r := chi.NewRouter()
99100

100101
handler := NewHTTPMcpHandler(&cfg, deps, t, logger)
102+
r.Use(middleware.WithRequestConfig)
101103
handler.RegisterRoutes(r)
102104

103105
addr := fmt.Sprintf(":%d", cfg.Port)

0 commit comments

Comments
 (0)