Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions mcp_fuzzer/auth/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ def setup_auth_from_env() -> AuthManager:
default_provider = os.getenv("MCP_DEFAULT_AUTH_PROVIDER")
if default_provider:
auth_manager.set_default_provider(default_provider)
elif len(auth_manager.auth_providers) == 1:
# If only one provider exists, set it as default for convenience
provider_name = next(iter(auth_manager.auth_providers.keys()))
auth_manager.set_default_provider(provider_name)
elif "api_key" in auth_manager.auth_providers:
# Prefer api_key as default if multiple providers exist
auth_manager.set_default_provider("api_key")

return auth_manager

Expand Down
18 changes: 17 additions & 1 deletion mcp_fuzzer/auth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,26 @@ def get_auth_params_for_tool(self, tool_name: str) -> dict[str, Any]:

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()

Comment on lines 46 to +69
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.

return {}
2 changes: 2 additions & 0 deletions mcp_fuzzer/client/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ def __init__(self, protocol, endpoint, timeout):
config["tool"], runs=config.get("runs", 10)
)
else:
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")
Comment on lines +139 to +143
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).

elif mode == "protocol":
await _run_spec_guard_if_enabled(client, config, reporter)
if config.get("protocol_type"):
Expand Down
4 changes: 4 additions & 0 deletions mcp_fuzzer/client/tool_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,12 @@ async def fuzz_all_tools(
tool_timeout: float | None = None,
) -> dict[str, dict[str, Any]]:
"""Fuzz all tools from the server."""
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")
Comment on lines +236 to +241
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.

return {}

all_results = {}
Expand Down
22 changes: 21 additions & 1 deletion mcp_fuzzer/client/transport/auth_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,36 @@
from __future__ import annotations

import argparse
import os

from ...auth import load_auth_config, setup_auth_from_env


def resolve_auth_port(args: argparse.Namespace):
"""Port for resolving authentication managers."""
"""Port for resolving authentication managers.

Priority order:
1. --auth-config file (if provided)
2. --auth-env flag (if explicitly set)
3. Environment variables (if any auth vars are set, auto-detect)
4. None (no auth)
"""
if getattr(args, "auth_config", None):
return load_auth_config(args.auth_config)
if getattr(args, "auth_env", False):
return setup_auth_from_env()

# Auto-detect: check if any auth environment variables are set
auth_env_vars = [
"MCP_API_KEY",
"MCP_USERNAME",
"MCP_PASSWORD",
"MCP_OAUTH_TOKEN",
"MCP_CUSTOM_HEADERS",
]
if any(os.getenv(var) for var in auth_env_vars):
return setup_auth_from_env()

return None


Expand Down
Loading