Skip to content

Health calculator incorrectly suggests 'login' for offline non-OAuth servers #242

@Dumbris

Description

@Dumbris

Bug Description

When an HTTP server is offline (not running), the health calculator incorrectly shows action: login instead of action: restart. This is misleading because the server doesn't require OAuth - it's simply unreachable.

Steps to Reproduce

  1. Configure an HTTP server without OAuth (e.g., local development server)
  2. Start mcpproxy with the server running - it connects successfully
  3. Stop the local server (turn it off)
  4. Check server health via API or CLI

Expected Behavior

{
  "name": "local-chatgpt-app",
  "health": {
    "level": "unhealthy",
    "summary": "Connection refused",
    "action": "restart"
  }
}

Actual Behavior

{
  "name": "local-chatgpt-app", 
  "health": {
    "level": "unhealthy",
    "summary": "Authentication required",
    "action": "login"
  },
  "last_error": "failed to connect: all authentication strategies failed, last error: OAuth authentication required for local-chatgpt-app..."
}

Root Cause Analysis

The issue is in two places:

1. mcp-go client layer

When connecting to an HTTP server, mcp-go tries authentication strategies. If the server is offline, it still generates an error message containing "authentication strategies failed" and "OAuth authentication required".

2. Health calculator (internal/health/calculator.go:336-357)

The isOAuthRelatedError() function matches on patterns like:

  • "authentication required"
  • "authentication strategies failed"
  • "oauth"

This triggers even when the underlying error is a connection failure, not an auth issue.

Comparison: Correct vs Incorrect

Server Protocol Actual Error Health Action Correct?
idea-ide-local SSE "connection refused" restart
local-chatgpt-app HTTP "all authentication strategies failed..." login

The SSE server correctly gets restart because its error message contains "connection refused" which is detected first. The HTTP server gets misclassified because the mcp-go error wraps the connection error inside an "authentication strategies failed" message.

Proposed Fix

In isOAuthRelatedError(), check for connection errors FIRST before checking for auth errors:

func isOAuthRelatedError(err string) bool {
    if err == "" {
        return false
    }
    
    // Connection errors take precedence - these are NOT OAuth issues
    connectionErrors := []string{
        "connection refused",
        "connection reset", 
        "no such host",
        "network is unreachable",
        "dial tcp",
        "i/o timeout",
    }
    for _, pattern := range connectionErrors {
        if containsIgnoreCase(err, pattern) {
            return false  // This is a connection error, not OAuth
        }
    }
    
    // Only then check for OAuth patterns
    oauthPatterns := []string{...}
    // ...
}

Alternatively, the fix could be in the mcp-go client to generate clearer error messages that distinguish connection failures from auth failures.

Environment

  • mcpproxy version: latest (branch 013-structured-server-state)
  • OS: macOS
  • Related to: Spec 013 (Structured Server State)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions