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

  1. Provide both keys: credentials={'username': '...', 'password': '...'}
  2. Check exact key spelling and lowercase (no 'user', 'login', 'pass')
  3. 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

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


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/d23f06cc037c79b8. Report an issue: GitHub.