MCPcopy
hub / github.com/github/github-mcp-server / RunStdioServer

Function RunStdioServer

internal/ghmcp/server.go:263–388  ·  view source on GitHub ↗

RunStdioServer is not concurrent safe.

(cfg StdioServerConfig)

Source from the content-addressed store, hash-verified

261
262// RunStdioServer is not concurrent safe.
263func RunStdioServer(cfg StdioServerConfig) error {
264 // OAuth login and a static token are mutually exclusive: they would
265 // disagree on how the token is sourced (lazy provider vs. static) and on
266 // scope filtering, so reject the ambiguous combination up front.
267 if cfg.OAuthManager != nil && cfg.Token != "" {
268 return fmt.Errorf("OAuthManager and a static Token are mutually exclusive: provide one or the other")
269 }
270
271 // Create app context
272 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
273 defer stop()
274
275 t, dumpTranslations := translations.TranslationHelper()
276
277 var slogHandler slog.Handler
278 var logOutput io.Writer
279 if cfg.LogFilePath != "" {
280 file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
281 if err != nil {
282 return fmt.Errorf("failed to open log file: %w", err)
283 }
284 logOutput = file
285 slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelDebug})
286 } else {
287 logOutput = os.Stderr
288 slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo})
289 }
290 logger := slog.New(slogHandler)
291 logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode)
292
293 // Determine the scope set used to filter tools. Classic PATs expose their
294 // granted scopes via the API; OAuth uses the requested scopes (the default
295 // set hides nothing, a narrower explicit set filters accordingly). Other
296 // token types don't advertise scopes, so filtering is skipped.
297 var tokenScopes []string
298 switch {
299 case strings.HasPrefix(cfg.Token, "ghp_"):
300 fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host)
301 if err != nil {
302 logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err)
303 } else {
304 tokenScopes = fetchedScopes
305 logger.Info("token scopes fetched for filtering", "scopes", tokenScopes)
306 }
307 case cfg.OAuthManager != nil:
308 tokenScopes = cfg.OAuthScopes
309 logger.Info("using requested OAuth scopes for tool filtering", "scopes", tokenScopes)
310 default:
311 logger.Debug("skipping scope filtering for non-PAT token")
312 }
313
314 // For OAuth, the token is resolved lazily: empty until the user authorizes
315 // on the first tool call, then refreshed for the rest of the session.
316 var tokenProvider func() string
317 if cfg.OAuthManager != nil {
318 tokenProvider = cfg.OAuthManager.AccessToken
319 }
320

Callers 2

main.goFile · 0.92

Calls 6

TranslationHelperFunction · 0.92
ContextWithGitHubErrorsFunction · 0.92
fetchTokenScopesForHostFunction · 0.85
NewStdioMCPServerFunction · 0.85
createOAuthMiddlewareFunction · 0.85
ErrorMethod · 0.45

Tested by 1