{"record":{"id":"abf14b29bf02e79a","repo":"affaan-m/ECC","slug":"mcp-config-must-include-an-mcpservers-object","errorCode":null,"errorMessage":"MCP config must include an mcpServers object","messagePattern":"MCP config must include an mcpServers object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/mcp-config.js","lineNumber":19,"sourceCode":"'use strict';\n\nfunction parseDisabledMcpServers(value) {\n  return [...new Set(\n    String(value || '')\n      .split(',')\n      .map((entry) => entry.trim())\n      .filter(Boolean)\n  )];\n}\n\nfunction filterMcpConfig(config, disabledServerNames = []) {\n  if (!config || typeof config !== 'object' || Array.isArray(config)) {\n    throw new Error('MCP config must be a JSON object');\n  }\n\n  const servers = config.mcpServers;\n  if (!servers || typeof servers !== 'object' || Array.isArray(servers)) {\n    throw new Error('MCP config must include an mcpServers object');\n  }\n\n  const disabled = new Set(parseDisabledMcpServers(disabledServerNames));\n  if (disabled.size === 0) {\n    return {\n      config: {\n        ...config,\n        mcpServers: { ...servers },\n      },\n      removed: [],\n    };\n  }\n\n  const nextServers = {};\n  const removed = [];\n\n  for (const [name, serverConfig] of Object.entries(servers)) {\n    if (disabled.has(name)) {","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/mcp-config.js#L1-L37","documentation":"Thrown by filterMcpConfig() when the config object has no mcpServers field, or that field is not a plain object (it is null, an array, or a scalar). The mcpServers object is the contract for every MCP config document ECC consumes.","triggerScenarios":"A config file with {\"servers\": {}} (wrong key), {\"mcpServers\": []} (array instead of object), or {\"mcpServers\": null}; a config produced by a tool that names the key differently.","commonSituations":"Typos in the key name; merging configs where mcpServers was deleted; output from a generator that uses a different schema.","solutions":["Ensure the document has a top-level mcpServers key whose value is a plain object mapping server names to server configs.","If migrating from another schema, rename the key before passing to filterMcpConfig.","Validate the shape with a JSON schema before invoking the function."],"exampleFix":"// before\n{ \"servers\": { \"foo\": {...} } }\n// after\n{ \"mcpServers\": { \"foo\": {...} } }","handlingStrategy":"validation","validationCode":"function hasMcpServers(config) {\n  return config !== null\n    && typeof config === 'object'\n    && !Array.isArray(config)\n    && config.mcpServers !== null\n    && typeof config.mcpServers === 'object'\n    && !Array.isArray(config.mcpServers);\n}\nif (!hasMcpServers(config)) {\n  throw new Error('Config must include an mcpServers object.');\n}","typeGuard":"function isMcpServersMap(config) {\n  return config != null\n    && typeof config === 'object'\n    && !Array.isArray(config)\n    && config.mcpServers != null\n    && typeof config.mcpServers === 'object'\n    && !Array.isArray(config.mcpServers);\n}","tryCatchPattern":"try {\n  result = filterMcpConfig(config, disabled);\n} catch (e) {\n  if (/mcpServers object/.test(e.message)) {\n    if (config.servers) config = { ...config, mcpServers: config.servers };\n    result = filterMcpConfig(config, disabled);\n  } else throw e;\n}","preventionTips":["Standardize on the mcpServers key across all config producers.","Validate config files with a JSON schema that requires mcpServers as an object.","When merging configs, ensure mcpServers survives the merge as an object."],"tags":["mcp","config","validation","json","schema"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}