{"record":{"id":"5a17c902cea69c11","repo":"Panniantong/Agent-Reach","slug":"mcporter-exc","errorCode":null,"errorMessage":"mcporter 配置文件无法安全读取：{exc}","messagePattern":"mcporter 配置文件无法安全读取：(.+?)","errorType":"exception","errorClass":"McporterConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/mcporter.py","lineNumber":117,"sourceCode":"        candidate = home_base / name\n        if os.path.lexists(candidate):\n            layers.append((candidate, \"home\"))\n            break\n\n    project_path = root / \"config\" / \"mcporter.json\"\n    if os.path.lexists(project_path):\n        layers.append((project_path, \"project\"))\n    return layers\n\n\ndef _read_config_object(config_path: Path) -> dict:\n    try:\n        raw = read_small_text_no_follow(\n            config_path,\n            max_bytes=_MAX_CONFIG_BYTES,\n        )\n    except PrivatePathError as exc:\n        raise McporterConfigError(\n            f\"mcporter 配置文件无法安全读取：{exc}\"\n        ) from exc\n    except (OSError, UnicodeError) as exc:\n        raise McporterConfigError(\"mcporter 配置文件无法安全读取\") from exc\n    if raw is None:\n        raise McporterConfigError(\"mcporter 配置文件不存在\")\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise McporterConfigError(\"mcporter 配置不是有效的 UTF-8 JSON\") from exc\n    if not isinstance(payload, dict):\n        raise McporterConfigError(\"mcporter 配置顶层必须是对象\")\n    return payload\n\n\ndef configured_server_names(output: str) -> set[str]:\n    \"\"\"Return exact configured server names from ``mcporter ... --json``.\n","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/mcporter.py#L99-L135","documentation":"Raised when reading a mcporter config layer fails the security preconditions in read_small_text_no_follow(): a symlink anywhere in the path, the target not being a regular file, or the file exceeding _MAX_CONFIG_BYTES. The message embeds the underlying PrivatePathError detail. This is a deliberate anti-symlink-attack guard for files that influence tool routing.","triggerScenarios":"mcporter.json (or its parent dir) is a symlink; the file is a FIFO/device; the file is larger than _MAX_CONFIG_BYTES. Distinct from error 11: this branch carries the PrivatePathError, so the cause is a policy refusal, not a plain I/O failure.","commonSituations":"Dotfiles managers symlinking ~/.config/mcporter; config kept in a mounted/virtual path resolving through links; a pathological huge config (bundled server definitions pasted repeatedly).","solutions":["Resolve symlinks: replace links with real files (cp -L) so every component of the path is a real directory/file","Verify with: python -c \"import os;p='<file>';print(os.path.realpath(p))\" and compare, and ls -la each parent","Shrink the config below the size cap — split extra servers into imported files","Exclude the path from symlink-based dotfile management"],"exampleFix":"# before: ~/.config/mcporter/mcporter.json -> ~/dotfiles/mcporter.json\n# after\n cp -L ~/dotfiles/mcporter.json ~/.config/mcporter/mcporter.json","handlingStrategy":"validation","validationCode":"import os\n\ndef path_is_symlink_free(path) -> bool:\n    p = os.path.abspath(path)\n    while True:\n        if os.path.islink(p):\n            return False\n        parent = os.path.dirname(p)\n        if parent == p:\n            return True\n        p = parent","typeGuard":null,"tryCatchPattern":"except McporterConfigError as exc:\n    if \"无法安全读取\" in str(exc) and \"symlink\" in str(exc.__cause__ or \"\"):\n        advise(\"materialize the config as a real file\")","preventionTips":["Keep mcporter configs as real files on real directories","Exclude config paths from symlink dotfile managers","Split oversized configs; respect the size cap as a hard invariant"],"tags":["mcporter","mcp","filesystem","security","config"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}