{"record":{"id":"a00e597610d6f2a7","repo":"langchain-ai/deepagents","slug":"self-scope-mcp-config-need-a-project-root-se","errorCode":null,"errorMessage":"{self.scope} MCP config {need} a project root: {self.path}","messagePattern":"(.+?) MCP config (.+?) a project root: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/mcp_tools.py","lineNumber":1377,"sourceCode":"    project_root: Path | None = None\n\n    def __post_init__(self) -> None:\n        \"\"\"Tie `project_root` to the scope that requires it.\n\n        `project_root` is the key that project-trust approvals are recorded\n        against, so a `PROJECT` config without one would be checked against a\n        re-derived fallback root instead of failing. Rejecting the combination\n        here keeps that from degrading into \"trusted against the wrong root\".\n\n        Raises:\n            ValueError: If the scope and `project_root` disagree.\n        \"\"\"\n        if (self.scope is MCPConfigScope.PROJECT) != (self.project_root is not None):\n            need = (\n                \"requires\" if self.scope is MCPConfigScope.PROJECT else \"must not carry\"\n            )\n            msg = f\"{self.scope} MCP config {need} a project root: {self.path}\"\n            raise ValueError(msg)\n\n\nclass MCPConfigIdentity(StrEnum):\n    \"\"\"Whether two discovered config paths are the same underlying file.\"\"\"\n\n    SAME = \"same\"\n    \"\"\"The paths are lexically equal or resolve to one file.\"\"\"\n\n    DIFFERENT = \"different\"\n    \"\"\"The paths resolve to distinct files.\"\"\"\n\n    UNKNOWN = \"unknown\"\n    \"\"\"Resolution failed, so identity could not be determined.\"\"\"\n\n\ndef _same_config_location(first: Path, second: Path) -> MCPConfigIdentity:\n    \"\"\"Return whether two discovered paths identify the same config.\n","sourceCodeStart":1359,"sourceCodeEnd":1395,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/mcp_tools.py#L1359-L1395","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["For a project-scoped ref, set project_root to the directory owning the config: MCPConfigRef(scope=MCPConfigScope.PROJECT, project_root=Path.cwd(), ...).","For a user/global-scoped ref, set project_root=None.","Derive scope and project_root together from one code path so they can't diverge."],"exampleFix":"// before\nMCPConfigRef(scope=MCPConfigScope.PROJECT, path=cfg_path, project_root=None)\n// after\nMCPConfigRef(scope=MCPConfigScope.PROJECT, path=cfg_path, project_root=Path(\"/home/me/project\"))","handlingStrategy":"validation","validationCode":"def make_ref(scope, path, project_root=None):\n    if (scope is MCPConfigScope.PROJECT) != (project_root is not None):\n        raise ValueError(\"scope and project_root are inconsistent\")\n    return MCPConfigRef(scope=scope, path=path, project_root=project_root)","typeGuard":"def ref_is_consistent(ref) -> bool:\n    return (ref.scope is MCPConfigScope.PROJECT) == (ref.project_root is not None)","tryCatchPattern":"try:\n    ref = MCPConfigRef(scope=scope, path=p, project_root=root)\nexcept ValueError as e:\n    if \"project root\" in str(e):\n        ref = MCPConfigRef(scope=scope, path=p, project_root=Path.cwd() if scope is MCPConfigScope.PROJECT else None)\n    else:\n        raise","preventionTips":["Build refs through one factory that sets scope and project_root together.","Never attach project_root to user/global-scope refs to 'disambiguate' paths.","When deserializing refs, recompute scope from project_root instead of storing both independently."],"tags":["mcp","invariant","dataclass","config-scope"],"backgroundTag":"invariant-violation","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}