Panniantong/Agent-Reach · error · McporterConfigError

mcporter 配置文件无法安全读取:{exc}

Error message

mcporter 配置文件无法安全读取:{exc}

What it means

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.

Source

Thrown at agent_reach/channels/mcporter.py:117

        candidate = home_base / name
        if os.path.lexists(candidate):
            layers.append((candidate, "home"))
            break

    project_path = root / "config" / "mcporter.json"
    if os.path.lexists(project_path):
        layers.append((project_path, "project"))
    return layers


def _read_config_object(config_path: Path) -> dict:
    try:
        raw = read_small_text_no_follow(
            config_path,
            max_bytes=_MAX_CONFIG_BYTES,
        )
    except PrivatePathError as exc:
        raise McporterConfigError(
            f"mcporter 配置文件无法安全读取:{exc}"
        ) from exc
    except (OSError, UnicodeError) as exc:
        raise McporterConfigError("mcporter 配置文件无法安全读取") from exc
    if raw is None:
        raise McporterConfigError("mcporter 配置文件不存在")
    try:
        payload = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise McporterConfigError("mcporter 配置不是有效的 UTF-8 JSON") from exc
    if not isinstance(payload, dict):
        raise McporterConfigError("mcporter 配置顶层必须是对象")
    return payload


def configured_server_names(output: str) -> set[str]:
    """Return exact configured server names from ``mcporter ... --json``.

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Resolve symlinks: replace links with real files (cp -L) so every component of the path is a real directory/file
  2. Verify with: python -c "import os;p='<file>';print(os.path.realpath(p))" and compare, and ls -la each parent
  3. Shrink the config below the size cap — split extra servers into imported files
  4. Exclude the path from symlink-based dotfile management

Example fix

# before: ~/.config/mcporter/mcporter.json -> ~/dotfiles/mcporter.json
# after
 cp -L ~/dotfiles/mcporter.json ~/.config/mcporter/mcporter.json
Defensive patterns

Strategy: validation

Validate before calling

import os

def path_is_symlink_free(path) -> bool:
    p = os.path.abspath(path)
    while True:
        if os.path.islink(p):
            return False
        parent = os.path.dirname(p)
        if parent == p:
            return True
        p = parent

Try / catch

except McporterConfigError as exc:
    if "无法安全读取" in str(exc) and "symlink" in str(exc.__cause__ or ""):
        advise("materialize the config as a real file")

Prevention

When it happens

Trigger: 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.

Common situations: Dotfiles managers symlinking ~/.config/mcporter; config kept in a mounted/virtual path resolving through links; a pathological huge config (bundled server definitions pasted repeatedly).

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/5a17c902cea69c11. Report an issue: GitHub.