11package main
22
33import (
4+ "context"
45 "errors"
56 "fmt"
67 "os"
78 "strings"
89 "time"
910
1011 "github.com/github/github-mcp-server/internal/ghmcp"
12+ "github.com/github/github-mcp-server/internal/oauth"
1113 "github.com/github/github-mcp-server/pkg/github"
1214 "github.com/spf13/cobra"
1315 "github.com/spf13/pflag"
3335 Long : `Start a server that communicates via standard input/output streams using JSON-RPC messages.` ,
3436 RunE : func (_ * cobra.Command , _ []string ) error {
3537 token := viper .GetString ("personal_access_token" )
38+
39+ // If no token provided, check if OAuth is configured
3640 if token == "" {
37- return errors .New ("GITHUB_PERSONAL_ACCESS_TOKEN not set" )
41+ oauthClientID := viper .GetString ("oauth_client_id" )
42+ if oauthClientID != "" {
43+ // Perform interactive OAuth flow
44+ ctx := context .Background ()
45+ oauthCfg := oauth .GetGitHubOAuthConfig (
46+ oauthClientID ,
47+ viper .GetString ("oauth_client_secret" ),
48+ getOAuthScopes (),
49+ viper .GetString ("host" ), // Pass the gh-host configuration
50+ )
51+
52+ result , err := oauth .StartInteractiveFlow (ctx , oauthCfg )
53+ if err != nil {
54+ return fmt .Errorf ("OAuth flow failed: %w" , err )
55+ }
56+
57+ token = result .AccessToken
58+ } else {
59+ return errors .New ("GITHUB_PERSONAL_ACCESS_TOKEN not set and OAuth not configured (set GITHUB_OAUTH_CLIENT_ID)" )
60+ }
3861 }
3962
4063 // If you're wondering why we're not using viper.GetStringSlice("toolsets"),
@@ -112,6 +135,11 @@ func init() {
112135 rootCmd .PersistentFlags ().Bool ("insider-mode" , false , "Enable insider features" )
113136 rootCmd .PersistentFlags ().Duration ("repo-access-cache-ttl" , 5 * time .Minute , "Override the repo access cache TTL (e.g. 1m, 0s to disable)" )
114137
138+ // OAuth flags (stdio mode only)
139+ rootCmd .PersistentFlags ().String ("oauth-client-id" , "" , "GitHub OAuth app client ID (enables interactive OAuth flow if token not set)" )
140+ rootCmd .PersistentFlags ().String ("oauth-client-secret" , "" , "GitHub OAuth app client secret (optional for public clients with PKCE)" )
141+ rootCmd .PersistentFlags ().StringSlice ("oauth-scopes" , nil , "OAuth scopes to request (comma-separated)" )
142+
115143 // Bind flag to viper
116144 _ = viper .BindPFlag ("toolsets" , rootCmd .PersistentFlags ().Lookup ("toolsets" ))
117145 _ = viper .BindPFlag ("tools" , rootCmd .PersistentFlags ().Lookup ("tools" ))
@@ -126,6 +154,9 @@ func init() {
126154 _ = viper .BindPFlag ("lockdown-mode" , rootCmd .PersistentFlags ().Lookup ("lockdown-mode" ))
127155 _ = viper .BindPFlag ("insider-mode" , rootCmd .PersistentFlags ().Lookup ("insider-mode" ))
128156 _ = viper .BindPFlag ("repo-access-cache-ttl" , rootCmd .PersistentFlags ().Lookup ("repo-access-cache-ttl" ))
157+ _ = viper .BindPFlag ("oauth_client_id" , rootCmd .PersistentFlags ().Lookup ("oauth-client-id" ))
158+ _ = viper .BindPFlag ("oauth_client_secret" , rootCmd .PersistentFlags ().Lookup ("oauth-client-secret" ))
159+ _ = viper .BindPFlag ("oauth_scopes" , rootCmd .PersistentFlags ().Lookup ("oauth-scopes" ))
129160
130161 // Add subcommands
131162 rootCmd .AddCommand (stdioCmd )
@@ -154,3 +185,25 @@ func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName {
154185 }
155186 return pflag .NormalizedName (name )
156187}
188+
189+ // getOAuthScopes returns the OAuth scopes to request
190+ // Uses custom scopes if provided, otherwise defaults to common scopes
191+ func getOAuthScopes () []string {
192+ var scopes []string
193+ if viper .IsSet ("oauth_scopes" ) {
194+ if err := viper .UnmarshalKey ("oauth_scopes" , & scopes ); err == nil && len (scopes ) > 0 {
195+ return scopes
196+ }
197+ }
198+
199+ // Default scopes for GitHub MCP Server
200+ // Based on the protected resource metadata at https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp
201+ return []string {
202+ "repo" ,
203+ "user" ,
204+ "gist" ,
205+ "notifications" ,
206+ "read:org" ,
207+ "project" ,
208+ }
209+ }
0 commit comments