BerriAI/litellm · error · ValueError

mcp_tools_config is required, please set `mcp_tools` in your

Error message

mcp_tools_config is required, please set `mcp_tools` in your proxy config

What it means

ValueError from MCPToolRegistry.load_tools_from_config (tool_registry.py:104): it was invoked with mcp_tools_config=None. The loader is meant to receive the mcp_tools list from the proxy config; None means the operator's config.yaml has no mcp_tools section (or the caller passed nothing), so the registry cannot be populated.

Source

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

    def load_tools_from_config(
        self,
        mcp_tools_config: dict[str, Any] | None = None,
        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)

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Add a top-level mcp_tools: section to config.yaml with at least one tool entry (list of dicts with name/description/handler)
  2. If MCP tools are intentionally unused, remove the custom code path that calls load_tools_from_config unconditionally
  3. Guard programmatic calls: skip loading when the config value is None instead of passing it through
  4. Validate config.yaml structure (mcp_tools at top level) before startup

Example fix

# config.yaml - before: section missing -> load_tools_from_config(None)
# after
top-level:
mcp_tools:
  - name: get_weather
    description: Get weather
    handler: tools.weather.get_weather
Defensive patterns

Strategy: validation

Validate before calling

def get_mcp_tools_or_none(config: dict):
    tools = config.get("mcp_tools")
    if tools is None:
        return None  # caller skips loading; do not call load_tools_from_config(None)
    return tools

Type guard

def has_mcp_tools_section(config: dict) -> bool:
    return isinstance(config, dict) and isinstance(config.get("mcp_tools"), list)

Try / catch

try:
    registry.load_tools_from_config(cfg.get("mcp_tools"))
except ValueError as e:
    if "mcp_tools_config is required" in str(e):
        skip_local_tools()  # section intentionally absent
    else:
        raise

Prevention

When it happens

Trigger: Starting/proxy code path or custom script calls load_tools_from_config() with no argument while config.yaml lacks an mcp_tools key; config loading returned None because mcp_tools was misindented or placed under the wrong top-level key; programmatic use of MCPToolRegistry without a config dict.

Common situations: YAML indentation puts mcp_tools under litellm_settings or general_settings instead of the top level; ops removes the mcp_tools block but a custom startup hook still unconditionally loads tools; tests calling the loader directly with None.

Related errors


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