Skip to content

Conversation

@Agent-Hellboy
Copy link
Owner

@Agent-Hellboy Agent-Hellboy commented Jan 22, 2026

PR Summary: Authentication Enhancements with Auto-Detection and Fallback Logic

Overview

This PR introduces automatic authentication provider selection and environment variable auto-detection, allowing authentication to work with minimal configuration. Changes span authentication initialization, manager fallback logic, transport resolution, and enhanced logging.

Design Changes and Patterns

1. Implicit Auto-Detection Pattern
The PR adds implicit auto-detection across multiple layers:

  • resolve_auth_port(): Auto-detects auth from environment variables without explicit --auth-env flag
  • setup_auth_from_env(): Auto-selects default provider based on available providers
  • get_default_auth_headers(): Provides runtime fallback to api_key provider or sole provider

This follows a Convention-over-Configuration pattern, reducing explicit configuration burden but introducing implicit behavior.

2. Fallback/Cascade Strategy Pattern
Multiple fallback chains now exist:

  • resolve_auth_port(): auth_config → auth_env flag → auto-detect env vars → None
  • setup_auth_from_env(): explicit MCP_DEFAULT_AUTH_PROVIDER → single provider → api_key provider
  • get_default_auth_headers(): explicit default → single provider → api_key provider → empty dict

This implements a Priority-Based Resolver pattern for graceful degradation.

Design and Code Smells

SOLID Violations:

  1. Single Responsibility Principle (SRP) - Medium Violation

    • setup_auth_from_env() now handles: reading env vars, creating providers, and selecting default provider (3 responsibilities)
    • resolve_auth_port() handles: reading CLI args, checking env vars, and dispatching to appropriate loader
    • Impact: Changes to default selection logic require touching loader function
  2. Open/Closed Principle (OCP) - Medium Violation

    • Hard-coded provider selection priority: single provider → api_key preference
    • Hard-coded auth variable list in resolve_auth_port(): ["MCP_API_KEY", "MCP_USERNAME", ...]
    • Adding new auth types or changing priority requires code modifications
    • Impact: Inflexible to future auth providers or priority changes
  3. Dependency Inversion Principle (DIP) - Minor Violation

    • resolve_auth_port() directly depends on concrete function names (load_auth_config, setup_auth_from_env)
    • Could benefit from abstraction layer for auth resolution strategies

Code Smells:

  1. Duplicate Logic (DRY Violation) - High Severity

    • Default provider selection duplicated in two locations:
      • setup_auth_from_env() (lines 68-77 of loaders.py)
      • get_default_auth_headers() (lines 46-70 of manager.py)
    • Both implement same logic: single provider → api_key preference
    • Risk: Changes to selection logic must be synchronized across files, leading to maintenance burden and potential bugs
  2. Magic String Literals - Medium Severity

    • Hard-coded provider names: "api_key", "basic", "oauth", "custom"
    • Appear in multiple functions without centralized definition
    • Environmental variable names scattered across files
    • Recommendation: Extract to constants module (AUTH_PROVIDER_NAMES, AUTH_ENV_VARIABLES)
  3. Feature Envy - Minor

    • get_default_auth_headers() directly accesses auth_providers dict internals
    • Could use more descriptive AuthManager accessor methods
  4. Implicit Behavior - Low to Medium Severity

    • Auto-detection happens silently without user awareness
    • Different behavior based on which env vars are set
    • Could surprise users expecting explicit opt-in for auth
    • Behavior changes from initialization to runtime without clear signaling

Implicit Coupling Issues:

  1. Temporal Coupling: get_default_auth_headers() fallback logic depends on whether default_provider was previously set
  2. Environmental Coupling: Behavior changes based on which environment variables exist at runtime
  3. Configuration Order Dependency: Priority order is implicit and embedded in multiple functions
  4. Cross-Module Coupling: Changes to env var detection in transport layer affect auth manager behavior

Positive Aspects

Improved UX: Single provider or api_key scenarios require no explicit configuration
Dependency Injection: AuthManager properly injected throughout
Strategy Pattern: AuthProvider interface well-designed with concrete implementations
Enhanced Observability: Added debug/info logging for troubleshooting (lines 139-143 in main.py, lines 236-242 in tool_client.py)
Documentation: Updated docstrings reflect new behavior
Backward Compatibility: Explicit configuration still works as before
Defensive Coding: Null checks and fallbacks prevent AttributeErrors

Recommendations for Improvement

  1. Extract Shared Logic (Critical - DRY): Create _select_default_provider(providers: dict) -> str | None utility function to DRY default selection and ensure consistency
  2. Externalize Configuration (Important - OCP): Move auth provider preferences and env var lists to a configuration class:
    class AuthResolutionConfig:
        env_vars: list[str] = ["MCP_API_KEY", "MCP_USERNAME", ...]
        provider_priority: list[str] = ["api_key"]
  3. Reduce Implicit Behavior: Make auto-detection explicit via --auto-detect-auth flag or configuration option with clear logging when auto-detection triggers
  4. Add Constants Module: Centralize all auth-related string literals (mcp_fuzzer/auth/constants.py)
  5. Consider Factory Pattern: Implement AuthResolutionFactory or AuthResolutionStrategy to encapsulate resolution logic and eliminate conditional logic in resolve_auth_port()
  6. Add Comprehensive Tests: Create tests for auto-detection behavior, fallback scenarios, and provider priority ordering to prevent regression
  7. Document Priority Order: Add clear documentation explaining the fallback priority chain and when auto-detection activates

Summary

The PR successfully reduces configuration friction through intelligent defaults but introduces implicit behavior and duplicated logic that violates DRY and increases maintenance burden. The changes prioritize developer UX over code clarity, which is acceptable for a fuzzer tool but should be addressed through refactoring and test coverage before adding more features. The most critical issue is the duplicated default provider selection logic that exists in two places—this should be extracted to a shared utility immediately.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Walkthrough

This PR adds intelligent fallback logic for authentication provider selection. When no explicit default provider is set, the system automatically selects from available providers (preferring a single registered provider or the "api_key" provider if multiple exist). Additionally, authentication configuration is auto-detected from environment variables when not explicitly configured. Minor logging enhancements support diagnostic purposes.

Changes

Cohort / File(s) Summary
Auth Provider Fallback Logic
mcp_fuzzer/auth/loaders.py, mcp_fuzzer/auth/manager.py
Added automatic default auth provider selection: selects sole provider if only one exists, or defaults to "api_key" if multiple providers present. Updated return documentation in manager.py to reflect fallback behavior.
Auth Environment Auto-Detection
mcp_fuzzer/client/transport/auth_port.py
Enhanced resolve_auth_port to auto-detect authentication from environment variables (MCP_API_KEY, MCP_USERNAME, MCP_PASSWORD, MCP_OAUTH_TOKEN, MCP_CUSTOM_HEADERS) with prioritized resolution: explicit config → explicit flag → auto-detect → none.
Logging Enhancements
mcp_fuzzer/client/main.py, mcp_fuzzer/client/tool_client.py
Added debug and info logging around fuzz_all_tools execution: logs input parameters, tool count, and warnings when no tools available. No control flow changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AuthPort
    participant Environment
    participant AuthManager
    participant Loaders

    Client->>AuthPort: resolve_auth_port(no explicit config)
    AuthPort->>Environment: Check for explicit auth_config
    Environment-->>AuthPort: None
    AuthPort->>Environment: Check auth_env flag
    Environment-->>AuthPort: None
    AuthPort->>Environment: Auto-detect MCP_* variables
    Environment-->>AuthPort: Found (e.g., MCP_API_KEY)
    AuthPort->>Loaders: setup_auth_from_env()
    Loaders->>AuthManager: Check registered providers
    AuthManager-->>Loaders: List of providers
    Loaders->>Loaders: Select default (sole/api_key)
    Loaders-->>AuthPort: Auth configuration
    AuthPort-->>Client: Resolved auth config
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • Add auth flags to fuzz tools with auth #23: Implements related auth default selection and environment-based auth detection that modify the same authentication setup components.
  • Fix issue #108 #111: Modifies the same auth defaulting logic in loaders.py and manager.py for default provider selection and fallbacks.

Poem

🐰 With whiskers twitching, auth we detect,
No config needed, defaults we select!
From env we read, smart fallbacks appear,
One provider or api_key, crystal clear! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix auth' is vague and overly broad. While authentication-related changes are present, the title lacks specificity about what auth issues are being fixed or which components are affected. Provide a more specific title that describes the actual auth changes, such as 'Add automatic default auth provider selection' or 'Implement auth provider fallback logic'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Jan 22, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.77%. Comparing base (8ec518b) to head (4c47d5c).

Files with missing lines Patch % Lines
mcp_fuzzer/auth/manager.py 40.00% 3 Missing ⚠️
mcp_fuzzer/client/transport/auth_port.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #154      +/-   ##
==========================================
- Coverage   95.80%   95.77%   -0.04%     
==========================================
  Files         150      150              
  Lines        8923     8943      +20     
==========================================
+ Hits         8549     8565      +16     
- Misses        374      378       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@mcp_fuzzer/auth/manager.py`:
- Around line 46-69: Add unit tests for get_default_auth_headers on the Auth
manager to lock the new fallback logic: 1) "single provider returns its headers"
— create a manager with one auth provider (mock or simple stub implementing
get_auth_headers) and assert returned dict equals that provider's headers; 2)
"multiple providers with api_key prefer api_key" — add at least two providers
including a key "api_key" and assert headers come from
auth_providers["api_key"]; 3) "multiple providers without api_key returns empty
dict" — add multiple providers none named "api_key" and assert the return is {}.
Use the same class/method names as in the diff (get_default_auth_headers and
auth_providers) and mock get_auth_headers to return distinct marker dicts for
clear assertions.

In `@mcp_fuzzer/client/main.py`:
- Around line 139-143: The two debug f-strings exceed line length; replace them
with logger-style formatting and break long calls across lines: change
logging.debug(f"Calling fuzz_all_tools with runs={config.get('runs', 10)}") to
logging.debug("Calling fuzz_all_tools with runs=%s", config.get("runs", 10)) and
change logging.debug(f"fuzz_all_tools completed, got {len(tool_results)} tool
results") to logging.debug("fuzz_all_tools completed, got %s tool results",
len(tool_results)); also ensure the client.fuzz_all_tools call (and its
runs_per_tool=...) is wrapped over multiple lines to keep each line under the
limit (refer to client.fuzz_all_tools, runs_per_tool, tool_results, config.get).

In `@mcp_fuzzer/client/tool_client.py`:
- Around line 236-241: The debug log f-strings in fuzz_all_tools exceed the
line-length limit; replace them with logger-style formatting and avoid eager
interpolation: in the fuzz_all_tools method (and the debug after calling
_get_tools_from_server) stop using f-strings and instead call self._logger.debug
with a format string and separate arguments (use %s/%d placeholders and pass
runs_per_tool and tool_timeout, and pass the computed tools count expression as
an argument), keeping the info/warning calls as-is and ensuring lines are
wrapped under the 88-char limit.

Comment on lines 46 to +69
def get_default_auth_headers(self) -> dict[str, str]:
"""Get auth headers from default provider for transport authentication.

If no default provider is set, tries to use:
1. Provider named "api_key" (if exists)
2. First available provider (if only one exists)
3. Empty dict (if multiple providers and no default)

Returns:
Dict of auth headers, or empty dict if no default provider is set
Dict of auth headers, or empty dict if no provider available
"""
# Use explicitly set default provider
if self.default_provider and self.default_provider in self.auth_providers:
return self.auth_providers[self.default_provider].get_auth_headers()

# Fallback: if only one provider exists, use it
if len(self.auth_providers) == 1:
provider = next(iter(self.auth_providers.values()))
return provider.get_auth_headers()

# Fallback: prefer "api_key" provider if it exists
if "api_key" in self.auth_providers:
return self.auth_providers["api_key"].get_auth_headers()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add unit coverage for the new fallback selection paths.

The new default-selection logic is subtle (single provider vs. api_key vs. empty). Please add tests to lock behavior and prevent regressions in future refactors. Suggested cases: single provider returns its headers; multiple providers with api_key prefer api_key; multiple providers without api_key returns empty dict.

🤖 Prompt for AI Agents
In `@mcp_fuzzer/auth/manager.py` around lines 46 - 69, Add unit tests for
get_default_auth_headers on the Auth manager to lock the new fallback logic: 1)
"single provider returns its headers" — create a manager with one auth provider
(mock or simple stub implementing get_auth_headers) and assert returned dict
equals that provider's headers; 2) "multiple providers with api_key prefer
api_key" — add at least two providers including a key "api_key" and assert
headers come from auth_providers["api_key"]; 3) "multiple providers without
api_key returns empty dict" — add multiple providers none named "api_key" and
assert the return is {}. Use the same class/method names as in the diff
(get_default_auth_headers and auth_providers) and mock get_auth_headers to
return distinct marker dicts for clear assertions.

Comment on lines +139 to +143
logging.debug(f"Calling fuzz_all_tools with runs={config.get('runs', 10)}")
tool_results = await client.fuzz_all_tools(
runs_per_tool=config.get("runs", 10)
)
logging.debug(f"fuzz_all_tools completed, got {len(tool_results)} tool results")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix Ruff E501 by wrapping debug logs.

These new f-strings exceed the line-length limit and fail CI. Use logger formatting and line wrapping.

✅ Proposed fix
-                    logging.debug(f"Calling fuzz_all_tools with runs={config.get('runs', 10)}")
+                    logging.debug(
+                        "Calling fuzz_all_tools with runs=%s",
+                        config.get("runs", 10),
+                    )
@@
-                    logging.debug(f"fuzz_all_tools completed, got {len(tool_results)} tool results")
+                    logging.debug(
+                        "fuzz_all_tools completed, got %d tool results",
+                        len(tool_results),
+                    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logging.debug(f"Calling fuzz_all_tools with runs={config.get('runs', 10)}")
tool_results = await client.fuzz_all_tools(
runs_per_tool=config.get("runs", 10)
)
logging.debug(f"fuzz_all_tools completed, got {len(tool_results)} tool results")
logging.debug(
"Calling fuzz_all_tools with runs=%s",
config.get("runs", 10),
)
tool_results = await client.fuzz_all_tools(
runs_per_tool=config.get("runs", 10)
)
logging.debug(
"fuzz_all_tools completed, got %d tool results",
len(tool_results),
)
🧰 Tools
🪛 GitHub Actions: Lint

[error] 139-139: Ruff check failed: E501 Line too long (95 > 88) in mcp_fuzzer/client/main.py:139. Command: 'ruff check mcp_fuzzer tests'.


[error] 143-143: Ruff check failed: E501 Line too long (100 > 88) in mcp_fuzzer/client/main.py:143. Command: 'ruff check mcp_fuzzer tests'.

🤖 Prompt for AI Agents
In `@mcp_fuzzer/client/main.py` around lines 139 - 143, The two debug f-strings
exceed line length; replace them with logger-style formatting and break long
calls across lines: change logging.debug(f"Calling fuzz_all_tools with
runs={config.get('runs', 10)}") to logging.debug("Calling fuzz_all_tools with
runs=%s", config.get("runs", 10)) and change logging.debug(f"fuzz_all_tools
completed, got {len(tool_results)} tool results") to
logging.debug("fuzz_all_tools completed, got %s tool results",
len(tool_results)); also ensure the client.fuzz_all_tools call (and its
runs_per_tool=...) is wrapped over multiple lines to keep each line under the
limit (refer to client.fuzz_all_tools, runs_per_tool, tool_results, config.get).

Comment on lines +236 to +241
self._logger.debug(f"fuzz_all_tools called with runs_per_tool={runs_per_tool}, tool_timeout={tool_timeout}")
self._logger.info("Fetching tools from server...")
tools = await self._get_tools_from_server()
self._logger.debug(f"_get_tools_from_server returned {len(tools) if tools else 0} tools")
if not tools:
self._logger.warning("No tools available for fuzzing")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix Ruff E501 by wrapping debug logs and using logger formatting.

Current f-strings exceed the 88-char limit and fail CI. Switching to logger formatting resolves lint and avoids eager formatting.

✅ Proposed fix
-        self._logger.debug(f"fuzz_all_tools called with runs_per_tool={runs_per_tool}, tool_timeout={tool_timeout}")
+        self._logger.debug(
+            "fuzz_all_tools called with runs_per_tool=%s, tool_timeout=%s",
+            runs_per_tool,
+            tool_timeout,
+        )
@@
-        self._logger.debug(f"_get_tools_from_server returned {len(tools) if tools else 0} tools")
+        self._logger.debug(
+            "_get_tools_from_server returned %d tools",
+            len(tools) if tools else 0,
+        )
🧰 Tools
🪛 GitHub Actions: Lint

[error] 236-236: Ruff check failed: E501 Line too long (116 > 88) in mcp_fuzzer/client/tool_client.py:236. Command: 'ruff check mcp_fuzzer tests'.


[error] 239-239: Ruff check failed: E501 Line too long (97 > 88) in mcp_fuzzer/client/tool_client.py:239. Command: 'ruff check mcp_fuzzer tests'.

🤖 Prompt for AI Agents
In `@mcp_fuzzer/client/tool_client.py` around lines 236 - 241, The debug log
f-strings in fuzz_all_tools exceed the line-length limit; replace them with
logger-style formatting and avoid eager interpolation: in the fuzz_all_tools
method (and the debug after calling _get_tools_from_server) stop using f-strings
and instead call self._logger.debug with a format string and separate arguments
(use %s/%d placeholders and pass runs_per_tool and tool_timeout, and pass the
computed tools count expression as an argument), keeping the info/warning calls
as-is and ensuring lines are wrapped under the 88-char limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants