44 "context"
55 "log/slog"
66 "net/http"
7- "strings"
87
8+ ghcontext "github.com/github/github-mcp-server/pkg/context"
99 "github.com/github/github-mcp-server/pkg/github"
1010 "github.com/github/github-mcp-server/pkg/http/headers"
1111 "github.com/github/github-mcp-server/pkg/http/middleware"
@@ -16,9 +16,10 @@ import (
1616)
1717
1818type InventoryFactoryFunc func (r * http.Request ) (* inventory.Inventory , error )
19- type GitHubMCPServerFactoryFunc func (ctx context. Context , r * http.Request , deps github.ToolDependencies , inventory * inventory.Inventory , cfg * github.MCPServerConfig ) (* mcp.Server , error )
19+ type GitHubMCPServerFactoryFunc func (r * http.Request , deps github.ToolDependencies , inventory * inventory.Inventory , cfg * github.MCPServerConfig ) (* mcp.Server , error )
2020
2121type HTTPMcpHandler struct {
22+ ctx context.Context
2223 config * HTTPServerConfig
2324 deps github.ToolDependencies
2425 logger * slog.Logger
@@ -46,7 +47,9 @@ func WithInventoryFactory(f InventoryFactoryFunc) HTTPMcpHandlerOption {
4647 }
4748}
4849
49- func NewHTTPMcpHandler (cfg * HTTPServerConfig ,
50+ func NewHTTPMcpHandler (
51+ ctx context.Context ,
52+ cfg * HTTPServerConfig ,
5053 deps github.ToolDependencies ,
5154 t translations.TranslationHelperFunc ,
5255 logger * slog.Logger ,
@@ -67,6 +70,7 @@ func NewHTTPMcpHandler(cfg *HTTPServerConfig,
6770 }
6871
6972 return & HTTPMcpHandler {
73+ ctx : ctx ,
7074 config : cfg ,
7175 deps : deps ,
7276 logger : logger ,
@@ -76,8 +80,33 @@ func NewHTTPMcpHandler(cfg *HTTPServerConfig,
7680 }
7781}
7882
83+ // RegisterRoutes registers the routes for the MCP server
84+ // URL-based values take precedence over header-based values
7985func (h * HTTPMcpHandler ) RegisterRoutes (r chi.Router ) {
86+ r .Use (middleware .WithRequestConfig )
87+
8088 r .Mount ("/" , h )
89+ // Mount readonly and toolset routes
90+ r .With (withToolset ).Mount ("/x/{toolset}" , h )
91+ r .With (withReadonly , withToolset ).Mount ("/x/{toolset}/readonly" , h )
92+ r .With (withReadonly ).Mount ("/readonly" , h )
93+ }
94+
95+ // withReadonly is middleware that sets readonly mode in the request context
96+ func withReadonly (next http.Handler ) http.Handler {
97+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
98+ ctx := ghcontext .WithReadonly (r .Context (), true )
99+ next .ServeHTTP (w , r .WithContext (ctx ))
100+ })
101+ }
102+
103+ // withToolset is middleware that extracts the toolset from the URL and sets it in the request context
104+ func withToolset (next http.Handler ) http.Handler {
105+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
106+ toolset := chi .URLParam (r , "toolset" )
107+ ctx := ghcontext .WithToolsets (r .Context (), []string {toolset })
108+ next .ServeHTTP (w , r .WithContext (ctx ))
109+ })
81110}
82111
83112func (h * HTTPMcpHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
@@ -87,7 +116,7 @@ func (h *HTTPMcpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
87116 return
88117 }
89118
90- ghServer , err := h .githubMcpServerFactory (r . Context (), r , h .deps , inventory , & github.MCPServerConfig {
119+ ghServer , err := h .githubMcpServerFactory (r , h .deps , inventory , & github.MCPServerConfig {
91120 Version : h .config .Version ,
92121 Translator : h .t ,
93122 ContentWindowSize : h .config .ContentWindowSize ,
@@ -108,8 +137,8 @@ func (h *HTTPMcpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
108137 middleware .ExtractUserToken ()(mcpHandler ).ServeHTTP (w , r )
109138}
110139
111- func DefaultGitHubMCPServerFactory (ctx context. Context , _ * http.Request , deps github.ToolDependencies , inventory * inventory.Inventory , cfg * github.MCPServerConfig ) (* mcp.Server , error ) {
112- return github .NewMCPServer (& github.MCPServerConfig {
140+ func DefaultGitHubMCPServerFactory (r * http.Request , deps github.ToolDependencies , inventory * inventory.Inventory , cfg * github.MCPServerConfig ) (* mcp.Server , error ) {
141+ return github .NewMCPServer (r . Context (), & github.MCPServerConfig {
113142 Version : cfg .Version ,
114143 Translator : cfg .Translator ,
115144 ContentWindowSize : cfg .ContentWindowSize ,
@@ -123,52 +152,37 @@ func DefaultInventoryFactory(cfg *HTTPServerConfig, t translations.TranslationHe
123152 b := github .NewInventory (t ).WithDeprecatedAliases (github .DeprecatedToolAliases )
124153
125154 // Feature checker composition
126- headerFeatures := parseCommaSeparatedHeader (r .Header .Get (headers .MCPFeaturesHeader ))
155+ headerFeatures := headers . ParseCommaSeparated (r .Header .Get (headers .MCPFeaturesHeader ))
127156 if checker := ComposeFeatureChecker (headerFeatures , staticChecker ); checker != nil {
128157 b = b .WithFeatureChecker (checker )
129158 }
130159
131- b = InventoryFiltersForRequestHeaders (r , b )
160+ b = InventoryFiltersForRequest (r , b )
132161 b .WithServerInstructions ()
133162
134163 return b .Build ()
135164 }
136165}
137166
138- // InventoryFiltersForRequestHeaders applies inventory filters based on HTTP request headers.
139- // Whitespace is trimmed from comma-separated values; empty values are ignored.
140- func InventoryFiltersForRequestHeaders (r * http.Request , builder * inventory.Builder ) * inventory.Builder {
141- if r .Header .Get (headers .MCPReadOnlyHeader ) != "" {
167+ // InventoryFiltersForRequest applies filters to the inventory builder
168+ // based on the request context and headers
169+ func InventoryFiltersForRequest (r * http.Request , builder * inventory.Builder ) * inventory.Builder {
170+ ctx := r .Context ()
171+
172+ if ghcontext .IsReadonly (ctx ) {
142173 builder = builder .WithReadOnly (true )
143174 }
144175
145- if toolsetsStr := r .Header .Get (headers .MCPToolsetsHeader ); toolsetsStr != "" {
146- toolsets := parseCommaSeparatedHeader (toolsetsStr )
176+ if toolsets := ghcontext .GetToolsets (ctx ); len (toolsets ) > 0 {
147177 builder = builder .WithToolsets (toolsets )
148178 }
149179
150- if toolsStr := r .Header .Get (headers .MCPToolsHeader ); toolsStr != "" {
151- tools := parseCommaSeparatedHeader (toolsStr )
180+ if tools := ghcontext .GetTools (ctx ); len (tools ) > 0 {
181+ if len (ghcontext .GetToolsets (ctx )) == 0 {
182+ builder = builder .WithToolsets ([]string {})
183+ }
152184 builder = builder .WithTools (github .CleanTools (tools ))
153185 }
154186
155187 return builder
156188}
157-
158- // parseCommaSeparatedHeader splits a header value by comma, trims whitespace,
159- // and filters out empty values.
160- func parseCommaSeparatedHeader (value string ) []string {
161- if value == "" {
162- return []string {}
163- }
164-
165- parts := strings .Split (value , "," )
166- result := make ([]string , 0 , len (parts ))
167- for _ , p := range parts {
168- trimmed := strings .TrimSpace (p )
169- if trimmed != "" {
170- result = append (result , trimmed )
171- }
172- }
173- return result
174- }
0 commit comments