langchain-ai/deepagents · error · ValueError

{self.scope} MCP config {need} a project root: {self.path}

Error message

{self.scope} MCP config {need} a project root: {self.path}

What it means

MCP config references carry a scope (PROJECT or user/global). A PROJECT-scoped config must have a `project_root`, and a non-PROJECT config must NOT have one — exactly one of the two conditions must hold. `MCPConfigRef.__post_init__` raises this ValueError when scope and project_root disagree, templating the message with the scope, the required word ('requires'/'must not carry') and the path.

Source

Thrown at libs/code/deepagents_code/mcp_tools.py:1377

    project_root: Path | None = None

    def __post_init__(self) -> None:
        """Tie `project_root` to the scope that requires it.

        `project_root` is the key that project-trust approvals are recorded
        against, so a `PROJECT` config without one would be checked against a
        re-derived fallback root instead of failing. Rejecting the combination
        here keeps that from degrading into "trusted against the wrong root".

        Raises:
            ValueError: If the scope and `project_root` disagree.
        """
        if (self.scope is MCPConfigScope.PROJECT) != (self.project_root is not None):
            need = (
                "requires" if self.scope is MCPConfigScope.PROJECT else "must not carry"
            )
            msg = f"{self.scope} MCP config {need} a project root: {self.path}"
            raise ValueError(msg)


class MCPConfigIdentity(StrEnum):
    """Whether two discovered config paths are the same underlying file."""

    SAME = "same"
    """The paths are lexically equal or resolve to one file."""

    DIFFERENT = "different"
    """The paths resolve to distinct files."""

    UNKNOWN = "unknown"
    """Resolution failed, so identity could not be determined."""


def _same_config_location(first: Path, second: Path) -> MCPConfigIdentity:
    """Return whether two discovered paths identify the same config.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. For a project-scoped ref, set project_root to the directory owning the config: MCPConfigRef(scope=MCPConfigScope.PROJECT, project_root=Path.cwd(), ...).
  2. For a user/global-scoped ref, set project_root=None.
  3. Derive scope and project_root together from one code path so they can't diverge.

Example fix

// before
MCPConfigRef(scope=MCPConfigScope.PROJECT, path=cfg_path, project_root=None)
// after
MCPConfigRef(scope=MCPConfigScope.PROJECT, path=cfg_path, project_root=Path("/home/me/project"))
Defensive patterns

Strategy: validation

Validate before calling

def make_ref(scope, path, project_root=None):
    if (scope is MCPConfigScope.PROJECT) != (project_root is not None):
        raise ValueError("scope and project_root are inconsistent")
    return MCPConfigRef(scope=scope, path=path, project_root=project_root)

Type guard

def ref_is_consistent(ref) -> bool:
    return (ref.scope is MCPConfigScope.PROJECT) == (ref.project_root is not None)

Try / catch

try:
    ref = MCPConfigRef(scope=scope, path=p, project_root=root)
except ValueError as e:
    if "project root" in str(e):
        ref = MCPConfigRef(scope=scope, path=p, project_root=Path.cwd() if scope is MCPConfigScope.PROJECT else None)
    else:
        raise

Prevention

When it happens

Trigger: Constructing MCPConfigRef(scope=MCPConfigScope.PROJECT, project_root=None, ...) or MCPConfigRef(scope=MCPConfigScope.USER/<global>, project_root="/some/dir", ...) — the invariant (scope is PROJECT) == (project_root is not None) is violated in a dataclass post-init.

Common situations: Programmatically building config refs where the scope enum was changed but project_root wasn't updated; copying a user-scope ref and adding a project_root to disambiguate paths; deserializing refs from storage with mismatched fields.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/a00e597610d6f2a7. Report an issue: GitHub.