{"record":{"id":"fd04d0bf422fce72","repo":"Panniantong/Agent-Reach","slug":"mcporter-mcpservers","errorCode":null,"errorMessage":"mcporter 配置缺少 mcpServers 对象","messagePattern":"mcporter 配置缺少 mcpServers 对象","errorType":"exception","errorClass":"McporterConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/mcporter.py","lineNumber":56,"sourceCode":"    0.7.3 loads the first home config\n    (``~/.mcporter/mcporter.json`` / ``mcporter.jsonc``) and then\n    ``<cwd>/config/mcporter.json``; project entries override duplicate home\n    names. Only exact ``mcpServers`` keys are returned. Editor imports are\n    deliberately not opened because Doctor must not expand its\n    credential-read boundary.\n    \"\"\"\n    selected_layers = _select_config_layers(root_dir)\n    if not selected_layers:\n        return McporterConfigInspection(frozenset(), None)\n\n    names = set()\n    imports_unchecked = False\n    sources = []\n    for config_path, source in selected_layers:\n        payload = _read_config_object(config_path)\n        servers = payload.get(\"mcpServers\")\n        if not isinstance(servers, dict):\n            raise McporterConfigError(\"mcporter 配置缺少 mcpServers 对象\")\n\n        for name, definition in servers.items():\n            if not isinstance(name, str) or not name.strip():\n                raise McporterConfigError(\"mcporter 配置包含无效的 server name\")\n            if not isinstance(definition, dict):\n                raise McporterConfigError(\"mcporter server 定义必须是对象\")\n            names.add(name.casefold())\n\n        imports = payload.get(\"imports\", _MISSING)\n        if imports is _MISSING:\n            # mcporter defaults to importing supported editor configs when the\n            # key is omitted. Doctor intentionally does not open those files.\n            imports_unchecked = True\n        elif not isinstance(imports, list) or not all(\n            isinstance(item, str) for item in imports\n        ):\n            raise McporterConfigError(\"mcporter imports 必须是字符串列表\")\n        elif imports:","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/mcporter.py#L38-L74","documentation":"Raised by the mcporter doctor when a selected config layer's parsed JSON lacks an 'mcpServers' key of dict type. Agent Reach inspects layered mcporter configs (user + project) and requires each present file to carry an mcpServers object, since it is the signal for which MCP servers mcporter can route to.","triggerScenarios":"Calling the mcporter inspection (doctor / mcporter channel check) when mcporter.json exists (e.g. repo config/mcporter.json or ~/.config/mcporter/...) but has no 'mcpServers' key, or its value is a list/string/number. A file with 'mcpServers': {} (empty dict) is fine.","commonSituations":"A mcporter.json written only with 'imports' or other tool settings; renaming the key ('servers', 'mcp-servers') from older examples; JSON5-style comments stripped leaving the key out; hand-authored minimal file copied from a README that uses a different schema.","solutions":["Add an mcpServers object to the offending layer: '\"mcpServers\": {}' at minimum, or your server definitions","Identify which layer fails: doctor iterates selected layers (project file when present, plus user config) — check each existing file","Align the key name exactly 'mcpServers' (camelCase) per the MCP config convention","Remove the file if mcporter is not actually configured — a nonexistent layer is skipped, an existing malformed one raises"],"exampleFix":"// before\n { \"defaults\": { \"transport\": \"stdio\" } }\n// after\n {\n   \"mcpServers\": {},\n   \"defaults\": { \"transport\": \"stdio\" }\n }","handlingStrategy":"validation","validationCode":"import json, os\n\ndef layer_has_servers(path) -> bool:\n    if not os.path.isfile(path):\n        return True  # missing layer is skipped by the tool\n    try:\n        payload = json.load(open(path, encoding=\"utf-8\"))\n    except Exception:\n        return False\n    return isinstance(payload, dict) and isinstance(payload.get(\"mcpServers\"), dict)","typeGuard":"def mcporter_layer_ok(payload) -> bool:\n    return isinstance(payload, dict) and isinstance(payload.get(\"mcpServers\"), dict)","tryCatchPattern":"from agent_reach.channels.mcporter import McporterConfigError\ntry:\n    inspection = inspect_mcporter_config(root)\nexcept McporterConfigError as exc:\n    show_doctor_warning(str(exc))  # doctor-level degradation, not crash","preventionTips":["Every existing mcporter config layer must carry an mcpServers object (empty {} is fine)","Delete stale config files instead of leaving key-less husks","Run `jq -e '.mcpServers|type==\"object\"' file` in CI for config repos"],"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"}