Files
github--github-mcp-server/pkg/github/auth_tools.go
copilot-swe-agent[bot] 6d30396776
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
License Check / license-check (push) Has been cancelled
Add session info to auth_login tool response
- Add GetSessionInfo callback to AuthToolDependencies
- Fetch user info and configuration after successful authentication
- Display comprehensive session context in auth_login success message
- Remove unused middleware functions that were adding to _meta
- Revert instructions.go to original (no longer modifying instructions)

The auth_login tool now provides the model with:
- User account details (username, name, email, company, location, profile URL)
- Enabled toolsets and tools
- Configuration flags (read-only, lockdown, dynamic toolsets)

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
2025-12-21 08:42:49 +00:00

183 lines
6.7 KiB
Go

package github
import (
"context"
"fmt"
"log/slog"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/github/github-mcp-server/pkg/utils"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// AuthToolset is the toolset for authentication tools.
// This is a special toolset that's only available when unauthenticated.
var ToolsetMetadataAuth = inventory.ToolsetMetadata{
ID: "auth",
Description: "Authentication tools for logging into GitHub",
Icon: "key",
}
// AuthToolDependencies contains dependencies for auth tools.
type AuthToolDependencies struct {
AuthManager *AuthManager
T translations.TranslationHelperFunc
// Server is the MCP server, used to access sessions for notifications
Server *mcp.Server
// Logger for debug logging
Logger *slog.Logger
// OnAuthenticated is called when authentication completes successfully.
// It should initialize GitHub clients and register tools.
OnAuthenticated func(ctx context.Context, token string) error
// OnAuthComplete is called after authentication flow completes (success or failure).
// It can be used to clean up auth tools after they're no longer needed.
OnAuthComplete func()
// GetSessionInfo is called after authentication to get session context information
// to include in the auth_login success response.
GetSessionInfo func(ctx context.Context, token string) string
}
// AuthTools returns the authentication tools.
// These are available when the server starts without a token.
func AuthTools(t translations.TranslationHelperFunc) []inventory.ServerTool {
return []inventory.ServerTool{
AuthLogin(t),
}
}
// AuthLogin creates a tool that initiates the OAuth device flow.
// It uses URL elicitation to show the user the authorization URL and code,
// then blocks while polling until the user completes authorization.
func AuthLogin(t translations.TranslationHelperFunc) inventory.ServerTool {
return inventory.ServerTool{
Tool: mcp.Tool{
Name: "auth_login",
Description: t("auth_login_description", "Initiate GitHub authentication using OAuth device flow. This will provide a URL and code that you can use to authenticate with GitHub. After visiting the URL and entering the code, authentication will complete automatically."),
Annotations: &mcp.ToolAnnotations{
Title: t("auth_login_title", "Login to GitHub"),
ReadOnlyHint: true,
},
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{},
},
},
Toolset: ToolsetMetadataAuth,
HandlerFunc: func(deps any) mcp.ToolHandler {
return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
authDeps := deps.(AuthToolDependencies)
authMgr := authDeps.AuthManager
if authMgr.IsAuthenticated() {
return utils.NewToolResultText("Already authenticated with GitHub."), nil
}
// Reset any pending flow before starting a new one
authMgr.Reset()
deviceResp, err := authMgr.StartDeviceFlow(ctx)
if err != nil {
return utils.NewToolResultError(fmt.Sprintf("Failed to start authentication: %v", err)), nil
}
if authDeps.Logger != nil {
authDeps.Logger.Info("starting auth flow", "expiresIn", deviceResp.ExpiresIn)
}
// Use URL elicitation to show the auth URL to the user
// This creates a nice UI in the client for the user to click
elicitResult, err := req.Session.Elicit(ctx, &mcp.ElicitParams{
Mode: "url",
Message: fmt.Sprintf("🔐 GitHub Authentication\n\nEnter code: %s", deviceResp.UserCode),
URL: deviceResp.VerificationURI,
})
if err != nil {
if authDeps.Logger != nil {
authDeps.Logger.Error("elicitation failed", "error", err)
}
// Elicitation not supported or failed - fall back to polling
return pollAndComplete(ctx, req.Session, authDeps, authMgr, deviceResp)
}
// Check if user cancelled
if elicitResult.Action == "cancel" || elicitResult.Action == "decline" {
authMgr.Reset()
return utils.NewToolResultText("Authentication cancelled."), nil
}
// User clicked the link - now poll for completion with progress
return pollAndComplete(ctx, req.Session, authDeps, authMgr, deviceResp)
}
},
}
}
// pollAndComplete polls for the auth token and completes the flow.
// It sends progress notifications during polling so the user knows it's working.
func pollAndComplete(ctx context.Context, session *mcp.ServerSession, authDeps AuthToolDependencies, authMgr *AuthManager, _ *DeviceCodeResponse) (*mcp.CallToolResult, error) {
// Poll for the token with progress updates
err := authMgr.CompleteDeviceFlowWithProgress(ctx, func(elapsed, total int, _ string) {
if authDeps.Logger != nil {
authDeps.Logger.Debug("auth polling", "elapsed", elapsed, "total", total)
}
// Send progress notification so user sees we're waiting
if session != nil {
_ = session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
ProgressToken: "auth-polling",
Progress: float64(elapsed),
Total: float64(total),
Message: "⏳ Waiting for GitHub authorization...",
})
}
})
if err != nil {
if authDeps.Logger != nil {
authDeps.Logger.Error("auth polling failed", "error", err)
}
return utils.NewToolResultError(fmt.Sprintf("Authentication failed: %v", err)), nil
}
if authDeps.Logger != nil {
authDeps.Logger.Info("auth polling succeeded, registering tools")
}
// Call the OnAuthenticated callback to initialize clients and register tools
if authDeps.OnAuthenticated != nil {
if err := authDeps.OnAuthenticated(ctx, authMgr.Token()); err != nil {
if authDeps.Logger != nil {
authDeps.Logger.Error("failed to initialize after auth", "error", err)
}
return nil, fmt.Errorf("authentication succeeded but failed to initialize: %w", err)
}
}
// Send a user-visible notification about successful authentication
if session != nil {
_ = session.Log(ctx, &mcp.LoggingMessageParams{
Level: "notice",
Logger: "github-mcp-server",
Data: "✅ Successfully authenticated with GitHub! All GitHub tools are now available.",
})
}
// Clean up auth tools now that we're authenticated
if authDeps.OnAuthComplete != nil {
authDeps.OnAuthComplete()
}
// Build the success response with session information
successMessage := "✅ Successfully authenticated with GitHub!\n\nAll GitHub tools are now available."
// Get session info if the callback is provided
if authDeps.GetSessionInfo != nil {
sessionInfo := authDeps.GetSessionInfo(ctx, authMgr.Token())
if sessionInfo != "" {
successMessage += "\n\n" + sessionInfo
}
}
return utils.NewToolResultText(successMessage), nil
}