{"record":{"id":"f82a260ff63251d1","repo":"coleam00/Archon","slug":"mcp-server-servername-must-be-a-json-object","errorCode":null,"errorMessage":"MCP server \"${serverName}\" must be a JSON object (got ${describeJsonType(serverConfig)})","messagePattern":"MCP server \"(.+?)\" must be a JSON object \\(got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/providers/src/mcp/config.ts","lineNumber":61,"sourceCode":"        return envVal ?? '';\n      }\n    );\n  }\n  return result;\n}\n\nfunction expandEnvVars(\n  config: Record<string, unknown>,\n  envSource: EnvSource\n): {\n  expanded: Record<string, unknown>;\n  missingVars: string[];\n} {\n  const result: Record<string, unknown> = {};\n  const missingVars: string[] = [];\n  for (const [serverName, serverConfig] of Object.entries(config)) {\n    if (typeof serverConfig !== 'object' || serverConfig === null || Array.isArray(serverConfig)) {\n      throw new Error(\n        `MCP server \"${serverName}\" must be a JSON object (got ${describeJsonType(serverConfig)})`\n      );\n    }\n    const server = { ...(serverConfig as Record<string, unknown>) };\n    if (server.env !== undefined) {\n      if (typeof server.env !== 'object' || server.env === null || Array.isArray(server.env)) {\n        throw new Error(\n          `MCP config ${serverName}.env must be a JSON object of string values (got ${describeJsonType(server.env)})`\n        );\n      }\n      server.env = expandEnvVarsInRecord(\n        server.env as Record<string, unknown>,\n        missingVars,\n        envSource,\n        `${serverName}.env`\n      );\n    }\n    if (server.headers !== undefined) {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/providers/src/mcp/config.ts#L43-L79","documentation":"expandEnvVars iterates the top-level server map and requires each server entry to be a JSON object (not null, not an array, not a primitive). This fails fast because per-server handling (env/headers expansion) only makes sense for object-shaped server configs.","triggerScenarios":"loadMcpConfig with a config where a key maps to a string/number/array/null, e.g. {\"filesystem\": \"/usr/bin\"} or {\"servers\": [...]} left un-nested after a bad mcpServers migration.","commonSituations":"Copying a Claude Desktop config that nests differently; pasting a command string as the server value; an editor collapsing a nested object; tools like version-pins written at the same level as servers.","solutions":["Make each top-level key map to an object: \"filesystem\": {\"command\": \"npx\", \"args\": [...]}","If you intended the wrapper form, put servers under \"mcpServers\": { ... } as the only top-level key.","Remove non-server keys (comments, pins, metadata) from the file."],"exampleFix":"// before\n{\"filesystem\": \"npx -y @modelcontextprotocol/server-filesystem\"}\n// after\n{\"filesystem\": {\"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/tmp\"]}}","handlingStrategy":"type-guard","validationCode":"const cfg = JSON.parse(readFileSync(mcpPath, 'utf-8'));\nfor (const [name, val] of Object.entries(cfg)) {\n  if (typeof val !== 'object' || val === null || Array.isArray(val)) {\n    throw new Error(`server \"${name}\" must be an object`);\n  }\n}","typeGuard":"const isServerObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Model every server as {command|url, args?, env?, headers?} — never a bare string.","Validate with a JSON schema in your editor so invalid shapes are flagged as you type.","Commit a known-good example config and diff new edits against it."],"tags":["config","validation","mcp","json"],"backgroundTag":"mcp-config-schema-invalid","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}