|
36 | 36 | Short: "Start stdio server", |
37 | 37 | Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`, |
38 | 38 | RunE: func(_ *cobra.Command, _ []string) error { |
39 | | - token := viper.GetString("personal_access_token") |
40 | | - var oauthMgr *oauth.Manager |
41 | | - |
42 | | - // If no token provided, setup OAuth manager if configured |
43 | | - if token == "" { |
44 | | - oauthClientID := viper.GetString("oauth_client_id") |
45 | | - if oauthClientID != "" { |
46 | | - // Create OAuth manager for lazy authentication |
47 | | - oauthCfg := oauth.GetGitHubOAuthConfig( |
48 | | - oauthClientID, |
49 | | - viper.GetString("oauth_client_secret"), |
50 | | - getOAuthScopes(), |
51 | | - viper.GetString("host"), |
52 | | - viper.GetInt("oauth_callback_port"), |
53 | | - ) |
54 | | - oauthMgr = oauth.NewManager(oauthCfg) |
55 | | - fmt.Fprintf(os.Stderr, "OAuth configured - will prompt for authentication when needed\n") |
56 | | - } else { |
57 | | - fmt.Fprintf(os.Stderr, "Warning: No authentication configured\n") |
58 | | - fmt.Fprintf(os.Stderr, " - Set GITHUB_PERSONAL_ACCESS_TOKEN, or\n") |
59 | | - fmt.Fprintf(os.Stderr, " - Configure OAuth with --oauth-client-id\n") |
60 | | - fmt.Fprintf(os.Stderr, "Tools will prompt for authentication when called\n") |
61 | | - } |
62 | | - } |
63 | | - |
64 | | - // Extract token from OAuth manager if available |
65 | | - if oauthMgr != nil && token == "" { |
66 | | - token = oauthMgr.GetAccessToken() |
67 | | - } |
68 | | - |
69 | 39 | // If you're wondering why we're not using viper.GetStringSlice("toolsets"), |
70 | 40 | // it's because viper doesn't handle comma-separated values correctly for env |
71 | 41 | // vars when using GetStringSlice. |
@@ -97,12 +67,54 @@ var ( |
97 | 67 | } |
98 | 68 | } |
99 | 69 |
|
| 70 | + token := viper.GetString("personal_access_token") |
| 71 | + var oauthMgr *oauth.Manager |
| 72 | + var oauthScopes []string |
| 73 | + var prebuiltInventory *inventory.Inventory |
| 74 | + |
| 75 | + // If no token provided, setup OAuth manager if configured |
| 76 | + if token == "" { |
| 77 | + oauthClientID := viper.GetString("oauth_client_id") |
| 78 | + if oauthClientID != "" { |
| 79 | + // Get translation helper for inventory building |
| 80 | + t, _ := translations.TranslationHelper() |
| 81 | + |
| 82 | + // Compute OAuth scopes and get inventory (avoids double building) |
| 83 | + scopesResult := getOAuthScopes(enabledToolsets, enabledTools, enabledFeatures, t) |
| 84 | + oauthScopes = scopesResult.scopes |
| 85 | + prebuiltInventory = scopesResult.inventory |
| 86 | + |
| 87 | + // Create OAuth manager for lazy authentication |
| 88 | + oauthCfg := oauth.GetGitHubOAuthConfig( |
| 89 | + oauthClientID, |
| 90 | + viper.GetString("oauth_client_secret"), |
| 91 | + oauthScopes, |
| 92 | + viper.GetString("host"), |
| 93 | + viper.GetInt("oauth_callback_port"), |
| 94 | + ) |
| 95 | + oauthMgr = oauth.NewManager(oauthCfg) |
| 96 | + fmt.Fprintf(os.Stderr, "OAuth configured - will prompt for authentication when needed\n") |
| 97 | + } else { |
| 98 | + fmt.Fprintf(os.Stderr, "Warning: No authentication configured\n") |
| 99 | + fmt.Fprintf(os.Stderr, " - Set GITHUB_PERSONAL_ACCESS_TOKEN, or\n") |
| 100 | + fmt.Fprintf(os.Stderr, " - Configure OAuth with --oauth-client-id\n") |
| 101 | + fmt.Fprintf(os.Stderr, "Tools will prompt for authentication when called\n") |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + // Extract token from OAuth manager if available |
| 106 | + if oauthMgr != nil && token == "" { |
| 107 | + token = oauthMgr.GetAccessToken() |
| 108 | + } |
| 109 | + |
100 | 110 | ttl := viper.GetDuration("repo-access-cache-ttl") |
101 | 111 | stdioServerConfig := ghmcp.StdioServerConfig{ |
102 | 112 | Version: version, |
103 | 113 | Host: viper.GetString("host"), |
104 | 114 | Token: token, |
105 | 115 | OAuthManager: oauthMgr, |
| 116 | + OAuthScopes: oauthScopes, |
| 117 | + PrebuiltInventory: prebuiltInventory, |
106 | 118 | EnabledToolsets: enabledToolsets, |
107 | 119 | EnabledTools: enabledTools, |
108 | 120 | EnabledFeatures: enabledFeatures, |
@@ -195,65 +207,49 @@ func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { |
195 | 207 | return pflag.NormalizedName(name) |
196 | 208 | } |
197 | 209 |
|
| 210 | +// oauthScopesResult holds the result of OAuth scope computation |
| 211 | +type oauthScopesResult struct { |
| 212 | + scopes []string |
| 213 | + inventory *inventory.Inventory // reused inventory to avoid double building |
| 214 | +} |
| 215 | + |
198 | 216 | // getOAuthScopes returns the OAuth scopes to request based on enabled tools |
| 217 | +// Also returns the built inventory to avoid building it twice |
199 | 218 | // Uses custom scopes if explicitly provided, otherwise computes required scopes |
200 | 219 | // from the tools that will be enabled based on user configuration |
201 | | -func getOAuthScopes() []string { |
| 220 | +func getOAuthScopes(enabledToolsets, enabledTools, enabledFeatures []string, t translations.TranslationHelperFunc) oauthScopesResult { |
202 | 221 | // Allow explicit override via --oauth-scopes flag |
203 | 222 | var scopes []string |
204 | 223 | if viper.IsSet("oauth_scopes") { |
205 | 224 | if err := viper.UnmarshalKey("oauth_scopes", &scopes); err == nil && len(scopes) > 0 { |
206 | | - return scopes |
207 | | - } |
208 | | - } |
209 | | - |
210 | | - // Compute required scopes based on enabled tools |
211 | | - // This ensures we only request scopes for tools the user will actually use |
212 | | - var enabledToolsets []string |
213 | | - if viper.IsSet("toolsets") { |
214 | | - if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil { |
215 | | - // If unmarshaling fails, fall back to defaults |
216 | | - enabledToolsets = nil |
217 | | - } |
218 | | - } |
219 | | - |
220 | | - var enabledTools []string |
221 | | - if viper.IsSet("tools") { |
222 | | - if err := viper.UnmarshalKey("tools", &enabledTools); err != nil { |
223 | | - enabledTools = nil |
224 | | - } |
225 | | - } |
226 | | - |
227 | | - var enabledFeatures []string |
228 | | - if viper.IsSet("features") { |
229 | | - if err := viper.UnmarshalKey("features", &enabledFeatures); err != nil { |
230 | | - enabledFeatures = nil |
| 225 | + // When scopes are explicit, don't build inventory (will be built in server) |
| 226 | + return oauthScopesResult{scopes: scopes} |
231 | 227 | } |
232 | 228 | } |
233 | 229 |
|
234 | 230 | // Build inventory with the same configuration that will be used at runtime |
235 | 231 | // This allows us to determine which tools will actually be available |
236 | | - t, _ := translations.TranslationHelper() |
| 232 | + // and avoids building the inventory twice |
237 | 233 | inventoryBuilder := github.NewInventory(t). |
238 | 234 | WithReadOnly(viper.GetBool("read-only")). |
239 | 235 | WithToolsets(enabledToolsets). |
240 | 236 | WithTools(enabledTools). |
241 | 237 | WithFeatureChecker(createFeatureChecker(enabledFeatures)) |
242 | 238 |
|
243 | | - inventory, err := inventoryBuilder.Build() |
| 239 | + inv, err := inventoryBuilder.Build() |
244 | 240 | if err != nil { |
245 | | - // If inventory build fails, fall back to default scopes |
246 | | - return getDefaultOAuthScopes() |
| 241 | + // If inventory build fails, fall back to default scopes without inventory |
| 242 | + return oauthScopesResult{scopes: getDefaultOAuthScopes()} |
247 | 243 | } |
248 | 244 |
|
249 | 245 | // Collect all required scopes from available tools |
250 | | - requiredScopes := collectRequiredScopes(inventory) |
| 246 | + requiredScopes := collectRequiredScopes(inv) |
251 | 247 | if len(requiredScopes) == 0 { |
252 | 248 | // If no tools require scopes, use defaults |
253 | | - return getDefaultOAuthScopes() |
| 249 | + return oauthScopesResult{scopes: getDefaultOAuthScopes(), inventory: inv} |
254 | 250 | } |
255 | 251 |
|
256 | | - return requiredScopes |
| 252 | + return oauthScopesResult{scopes: requiredScopes, inventory: inv} |
257 | 253 | } |
258 | 254 |
|
259 | 255 | // getDefaultOAuthScopes returns the default scopes for GitHub MCP Server |
|
0 commit comments