Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
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>
  • Loading branch information
Copilot and SamMorrowDrums committed Dec 21, 2025
commit 6d303967768534f9af2858dae91a460e9f05a6cf
235 changes: 73 additions & 162 deletions internal/ghmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
// Add middlewares
ghServer.AddReceivingMiddleware(addGitHubAPIErrorToContext)
ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.rest, clients.gqlHTTP))
ghServer.AddReceivingMiddleware(addSessionInfoMiddleware(cfg, clients.rest, enabledToolsets, instructionToolsets))

// Create dependencies for tool handlers
deps := github.NewBaseDeps(
Expand Down Expand Up @@ -347,14 +346,6 @@ func NewUnauthenticatedMCPServer(cfg MCPServerConfig) (*UnauthenticatedServerRes
// Add error context middleware
ghServer.AddReceivingMiddleware(addGitHubAPIErrorToContext)

// Add session info middleware for unauthenticated mode
// This will show configuration but no user info until authenticated
instructionToolsets := enabledToolsets
if instructionToolsets == nil {
instructionToolsets = github.GetDefaultToolsetIDs()
}
ghServer.AddReceivingMiddleware(addUnauthenticatedSessionInfoMiddleware(cfg, enabledToolsets, instructionToolsets))

// Create auth tool dependencies with a callback for when auth completes
authDeps := github.AuthToolDependencies{
AuthManager: authManager,
Expand Down Expand Up @@ -425,6 +416,79 @@ func NewUnauthenticatedMCPServer(cfg MCPServerConfig) (*UnauthenticatedServerRes
cfg.Logger.Info("auth tools removed after successful authentication")
}
},
GetSessionInfo: func(ctx context.Context, token string) string {
// Create a temporary client to fetch user info
apiHost, err := parseAPIHost(cfg.Host)
if err != nil {
return ""
}

tempClient := gogithub.NewClient(nil).WithAuthToken(token)
tempClient.BaseURL = apiHost.baseRESTURL

// Build session information text
var sessionInfo strings.Builder

// Fetch and include user information
if user, _, err := tempClient.Users.Get(ctx, ""); err == nil && user != nil {
sessionInfo.WriteString("## Your GitHub Account\n\n")
sessionInfo.WriteString(fmt.Sprintf("**Username:** @%s\n", user.GetLogin()))
if name := user.GetName(); name != "" {
sessionInfo.WriteString(fmt.Sprintf("**Name:** %s\n", name))
}
if email := user.GetEmail(); email != "" {
sessionInfo.WriteString(fmt.Sprintf("**Email:** %s\n", email))
}
if company := user.GetCompany(); company != "" {
sessionInfo.WriteString(fmt.Sprintf("**Company:** %s\n", company))
}
if location := user.GetLocation(); location != "" {
sessionInfo.WriteString(fmt.Sprintf("**Location:** %s\n", location))
}
sessionInfo.WriteString(fmt.Sprintf("**Profile:** %s\n", user.GetHTMLURL()))
}

// Add server configuration
sessionInfo.WriteString("\n## Server Configuration\n\n")

// Determine effective toolsets
var effectiveToolsets []string
if enabledToolsets == nil {
// nil means defaults - expand them here
effectiveToolsets = github.GetDefaultToolsetIDs()
} else {
effectiveToolsets = enabledToolsets
}

if len(effectiveToolsets) > 0 {
sessionInfo.WriteString(fmt.Sprintf("**Enabled Toolsets:** %s\n", strings.Join(effectiveToolsets, ", ")))
}

if len(cfg.EnabledTools) > 0 {
sessionInfo.WriteString(fmt.Sprintf("**Enabled Tools:** %s\n", strings.Join(cfg.EnabledTools, ", ")))
}

// Configuration flags
var configFlags []string
if cfg.ReadOnly {
configFlags = append(configFlags, "Read-only mode (write operations disabled)")
}
if cfg.LockdownMode {
configFlags = append(configFlags, "Lockdown mode (repository access restricted)")
}
if cfg.DynamicToolsets {
configFlags = append(configFlags, "Dynamic toolsets (can be enabled at runtime)")
}

if len(configFlags) > 0 {
sessionInfo.WriteString("\n**Configuration:**\n")
for _, flag := range configFlags {
sessionInfo.WriteString(fmt.Sprintf("- %s\n", flag))
}
}

return sessionInfo.String()
},
}

// Register only auth tools
Expand Down Expand Up @@ -850,156 +914,3 @@ func addUserAgentsMiddleware(cfg MCPServerConfig, restClient *gogithub.Client, g
}
}
}

// addSessionInfoMiddleware enriches the InitializeResult with session information
// including user details, enabled toolsets, and configuration flags.
func addSessionInfoMiddleware(cfg MCPServerConfig, restClient *gogithub.Client, enabledToolsets []string, instructionToolsets []string) func(next mcp.MethodHandler) mcp.MethodHandler {
return func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, request mcp.Request) (result mcp.Result, err error) {
// Only intercept initialize method
if method != "initialize" {
return next(ctx, method, request)
}

// Call the next handler to get the InitializeResult
result, err = next(ctx, method, request)
if err != nil {
return result, err
}

// Cast to InitializeResult to add metadata
initResult, ok := result.(*mcp.InitializeResult)
if !ok {
// If we can't cast, just return the original result
return result, err
}

// Build session info metadata
sessionInfo := make(map[string]any)

// Add configuration information
sessionInfo["readOnlyMode"] = cfg.ReadOnly
sessionInfo["lockdownMode"] = cfg.LockdownMode
sessionInfo["dynamicToolsets"] = cfg.DynamicToolsets

// Add toolsets information
switch {
case enabledToolsets == nil:
// nil means "use defaults"
sessionInfo["enabledToolsets"] = instructionToolsets
sessionInfo["toolsetsMode"] = "default"
case len(enabledToolsets) == 0:
sessionInfo["enabledToolsets"] = []string{}
sessionInfo["toolsetsMode"] = "none"
default:
sessionInfo["enabledToolsets"] = enabledToolsets
sessionInfo["toolsetsMode"] = "explicit"
}

// Add enabled tools if specified
if len(cfg.EnabledTools) > 0 {
sessionInfo["enabledTools"] = cfg.EnabledTools
}

// Try to fetch user information (get_me equivalent)
// If it fails, we simply omit it from the session info
if user, _, err := restClient.Users.Get(ctx, ""); err == nil && user != nil {
userInfo := map[string]any{
"login": user.GetLogin(),
"id": user.GetID(),
"profileURL": user.GetHTMLURL(),
"avatarURL": user.GetAvatarURL(),
}

// Add optional fields if they exist
if name := user.GetName(); name != "" {
userInfo["name"] = name
}
if email := user.GetEmail(); email != "" {
userInfo["email"] = email
}
if bio := user.GetBio(); bio != "" {
userInfo["bio"] = bio
}
if company := user.GetCompany(); company != "" {
userInfo["company"] = company
}
if location := user.GetLocation(); location != "" {
userInfo["location"] = location
}

sessionInfo["user"] = userInfo
}

// Set the metadata on the InitializeResult
if initResult.Meta == nil {
initResult.Meta = make(mcp.Meta)
}
initResult.Meta["sessionInfo"] = sessionInfo

return initResult, nil
}
}
}

// addUnauthenticatedSessionInfoMiddleware enriches the InitializeResult with session information
// for unauthenticated servers. This shows configuration but no user info until authenticated.
func addUnauthenticatedSessionInfoMiddleware(cfg MCPServerConfig, enabledToolsets []string, instructionToolsets []string) func(next mcp.MethodHandler) mcp.MethodHandler {
return func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, request mcp.Request) (result mcp.Result, err error) {
// Only intercept initialize method
if method != "initialize" {
return next(ctx, method, request)
}

// Call the next handler to get the InitializeResult
result, err = next(ctx, method, request)
if err != nil {
return result, err
}

// Cast to InitializeResult to add metadata
initResult, ok := result.(*mcp.InitializeResult)
if !ok {
// If we can't cast, just return the original result
return result, err
}

// Build session info metadata (without user info for unauthenticated mode)
sessionInfo := make(map[string]any)

// Add configuration information
sessionInfo["readOnlyMode"] = cfg.ReadOnly
sessionInfo["lockdownMode"] = cfg.LockdownMode
sessionInfo["dynamicToolsets"] = cfg.DynamicToolsets
sessionInfo["authenticated"] = false

// Add toolsets information
switch {
case enabledToolsets == nil:
// nil means "use defaults"
sessionInfo["enabledToolsets"] = instructionToolsets
sessionInfo["toolsetsMode"] = "default"
case len(enabledToolsets) == 0:
sessionInfo["enabledToolsets"] = []string{}
sessionInfo["toolsetsMode"] = "none"
default:
sessionInfo["enabledToolsets"] = enabledToolsets
sessionInfo["toolsetsMode"] = "explicit"
}

// Add enabled tools if specified
if len(cfg.EnabledTools) > 0 {
sessionInfo["enabledTools"] = cfg.EnabledTools
}

// Set the metadata on the InitializeResult
if initResult.Meta == nil {
initResult.Meta = make(mcp.Meta)
}
initResult.Meta["sessionInfo"] = sessionInfo

return initResult, nil
}
}
}
Loading