BerriAI/litellm · error · ValueError

mcp_tools_config must be a list of dictionaries

Error message

mcp_tools_config must be a list of dictionaries

What it means

ValueError from MCPToolRegistry.load_tools_from_config (tool_registry.py:108): iteration hit an element of mcp_tools that is not a dict. The loader requires every entry to be a mapping with name/description/handler keys; a YAML formatting mistake (list of strings, scalars, or a nested list) produces this immediately on the first bad element.

Source

Thrown at litellm/proxy/_experimental/mcp_server/tool_registry.py:108

        config_file_path: str | None = None,
    ) -> None:
        """
        Load and register tools from the proxy config

        Args:
            mcp_tools_config: The mcp_tools config from the proxy config
            config_file_path: Path to the operator's config.yaml. Threaded
                through to ``get_instance_fn`` so an ``s3://``/``gcs://``
                ``handler`` declared in the YAML resolves; callers from a
                non-YAML path must leave this ``None`` so the runtime gate
                fires.
        """
        if mcp_tools_config is None:
            raise ValueError("mcp_tools_config is required, please set `mcp_tools` in your proxy config")

        for tool_config in mcp_tools_config:
            if not isinstance(tool_config, dict):
                raise ValueError("mcp_tools_config must be a list of dictionaries")

            name = tool_config.get("name")
            description = tool_config.get("description")
            input_schema = tool_config.get("input_schema", {})
            handler_name = tool_config.get("handler")

            if not all([name, description, handler_name]):
                continue

            # Try to resolve the handler
            # First check if it's a module path (e.g., "module.submodule.function")
            if handler_name is None:
                raise ValueError(f"handler is required for tool {name}")
            handler = get_instance_fn(handler_name, config_file_path)

            if handler is None:
                verbose_logger.warning("Warning: Could not find handler %s for tool %s", handler_name, name)
                continue

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Rewrite mcp_tools as a list of mappings, each with name, description, and handler keys, all indented under the dash
  2. Lint the config before startup: every entry must be a dict (see validation snippet)
  3. Check for tabs/spacing mistakes around the dash items in YAML
  4. If you want a path-shorthand, expand it to full dicts upstream of the loader

Example fix

# config.yaml - before
mcp_tools:
  - tools.weather.get_weather

# after
mcp_tools:
  - name: get_weather
    description: Get weather
    handler: tools.weather.get_weather
Defensive patterns

Strategy: validation

Validate before calling

def validate_mcp_tools_entries(cfg) -> list[str]:
    if cfg is None:
        return ["mcp_tools missing"]
    bad = [i for i, entry in enumerate(cfg) if not isinstance(entry, dict)]
    return [f"entry {i} is not a dict (check YAML indentation)" for i in bad]

Type guard

def is_valid_mcp_tools_config(cfg) -> bool:
    return isinstance(cfg, list) and all(
        isinstance(e, dict) and {"name", "description", "handler"} <= set(e) for e in cfg
    )

Try / catch

try:
    registry.load_tools_from_config(cfg["mcp_tools"])
except ValueError as e:
    if "list of dictionaries" in str(e):
        raise ConfigError("mcp_tools entries must be mappings: - name: ... description: ... handler: ...") from e
    raise

Prevention

When it happens

Trigger: config.yaml mcp_tools written as a list of strings (e.g. - tools.weather.get_weather); a YAML dash item that parses as a scalar because keys were not indented under it; mixing a handler-name shorthand into the list; programmatic callers passing a list of module paths instead of dicts.

Common situations: Hand-editing YAML and losing one level of indentation so `- name: x` collapses to a string; converting from another tool format that lists handler paths only; copy-pasting examples that use a shorthand the loader never supported.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/ecfa59d56f72f836. Report an issue: GitHub.