D4Vinci/Scrapling · error · ValueError
Credentials dictionary must contain both 'username' and 'pas
Error message
Credentials dictionary must contain both 'username' and 'password' keys
What it means
The MCP server's proxy/credentials parameter normalizes a dict into a (username, password) tuple for the fetchers via _normalize_credentials. If the dict is non-empty but lacks either 'username' or 'password', it raises ValueError rather than sending an unauthenticated or half-configured request. This is a guard against silent auth misconfiguration.
Source
Thrown at scrapling/core/ai.py:126
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
)
]
return ResponseModel(status=page.status, content=content, url=page.url)
def _normalize_credentials(credentials: Optional[Dict[str, str]]) -> Optional[Tuple[str, str]]:
"""Convert a credentials dictionary to a tuple accepted by fetchers."""
if not credentials:
return None
username = credentials.get("username")
password = credentials.get("password")
if username is None or password is None:
raise ValueError("Credentials dictionary must contain both 'username' and 'password' keys")
return username, password
class _StaticTokenVerifier(TokenVerifier):
"""Verifies requests against a single shared bearer token."""
def __init__(self, token: str):
self._token = token.encode()
async def verify_token(self, token: str) -> Optional[AccessToken]:
if compare_digest(token.encode(), self._token):
return AccessToken(token=token, client_id="scrapling-mcp", scopes=[])
return None
class ScraplingMCPServer:
def __init__(self, executable_path: Optional[str] = None, auth_token: Optional[str] = None):View on GitHub (pinned to 5d213a2d47)
Solutions
- Provide both keys: credentials={'username': '...', 'password': '...'}
- Check exact key spelling and lowercase (no 'user', 'login', 'pass')
- Pass credentials=None (or omit) when the target needs no auth
Example fix
# before
await tool.fetch(url, credentials={'user': 'bob', 'password': 'hunter2'})
# after
await tool.fetch(url, credentials={'username': 'bob', 'password': 'hunter2'}) Defensive patterns
Strategy: validation
Validate before calling
creds = {'username': 'bob', 'password': 'hunter2'}
assert set(creds) == {'username', 'password'} or not creds, 'credentials need username+password or None' Type guard
from typing import Any, Dict, Optional
def is_valid_credentials(value: Any) -> bool:
if value is None:
return True
return (
isinstance(value, dict)
and (not value or ('username' in value and 'password' in value))
) Prevention
- Define a strict schema for tool arguments the LLM fills (required keys username, password)
- Reject partial credential dicts at your boundary instead of forwarding them
When it happens
Trigger: Calling an MCP fetch/tool call with credentials={'user': 'x'} or credentials={'username': 'x'} (missing password), or keys with different casing ('UserName'). Both keys must be present; empty/None credentials skip auth entirely.
Common situations: LLM-generated tool arguments using guessed key names, JSON payloads where one key was dropped, or copying credentials config from another tool that uses 'user'/'pass' naming.
Related errors
- Session '{session_id}' already exists. Use a different ID or
- Session '{session_id}' not found. Use list_sessions to see a
- Session '{session_id}' is no longer alive. Open a new sessio
- Session '{session_id}' is a '{entry.session_type}' session,
- 'quality' is only valid when 'image_type' is 'jpeg'.
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/d23f06cc037c79b8.
Report an issue: GitHub.