{"record":{"id":"05bdc91a77c75af2","repo":"Panniantong/Agent-Reach","slug":"mcporter-05bdc9","errorCode":null,"errorMessage":"mcporter 配置顶层必须是对象","messagePattern":"mcporter 配置顶层必须是对象","errorType":"exception","errorClass":"McporterConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/mcporter.py","lineNumber":129,"sourceCode":"    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\n    Paths, descriptions, endpoints, and other metadata are deliberately\n    ignored: only each server object's ``name`` field is a routing signal.\n    \"\"\"\n    try:\n        payload = json.loads(output)\n    except (json.JSONDecodeError, TypeError) as exc:\n        raise McporterConfigError(\"mcporter 返回的 JSON 无法解析\") from exc\n\n    if not isinstance(payload, dict) or not isinstance(payload.get(\"servers\"), list):\n        raise McporterConfigError(\"mcporter JSON 缺少 servers 列表\")\n\n    return {","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/mcporter.py#L111-L147","documentation":"Raised when the parsed mcporter config JSON is not a top-level object (dict). The whole layer schema is an object with mcpServers/imports/defaults keys; a top-level array, string, number, or bare null fails isinstance(payload, dict).","triggerScenarios":"mcporter.json contains '[{...}]', '\"just a string\"', or 'null' at the top level. Note json.loads('null') returns None which is not a dict → this error (distinct from the YAML channel where None returns 'not configured').","commonSituations":"A jq transform outputting an array; a file containing only a quoted string (e.g. from echo '\"$VALUE\"' with empty/odd value); an effectively empty file written as 'null'.","solutions":["Wrap the content in an object: '{ \"mcpServers\": { ...your entries... } }'","If the file is meaningless, delete it — missing layers are skipped without error","Check what wrote the file (script/template) and fix its output shape"],"exampleFix":"// before\n [ { \"command\": \"npx\" } ]\n// after\n { \"mcpServers\": { \"srv\": { \"command\": \"npx\" } } }","handlingStrategy":"validation","validationCode":"import json\n\ndef config_toplevel_is_object(path) -> bool:\n    try:\n        return isinstance(json.load(open(path, encoding=\"utf-8\")), dict)\n    except Exception:\n        return False","typeGuard":"def is_config_object(payload) -> bool:\n    return isinstance(payload, dict)","tryCatchPattern":"except McporterConfigError as exc:\n    if \"顶层必须是对象\" in str(exc):\n        show(\"wrap the file content in { \\\"mcpServers\\\": {...} }\")","preventionTips":["Top level of mcporter.json is always an object","Watch out for jq transforms that output arrays","An empty object {} satisfies the check; null does not"],"tags":["mcporter","mcp","json","config"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}