{"record":{"id":"f72cb454ae9dd438","repo":"gethomepage/homepage","slug":"content-must-be-a-string","errorCode":null,"errorMessage":"content must be a string","messagePattern":"content must be a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/utils/mcp/homepage-mcp.js","lineNumber":409,"sourceCode":"  switch (name) {\n    case \"list_config_files\":\n      return textContent(JSON.stringify({ configDir: CONF_DIR, files: listConfigFiles() }, null, 2));\n    case \"read_config_file\": {\n      assertKnownConfigFile(args.file);\n      return textContent(readConfig(args.file));\n    }\n    case \"validate_config_file\": {\n      assertKnownConfigFile(args.file);\n      const content = Object.prototype.hasOwnProperty.call(args, \"content\") ? args.content : readConfig(args.file);\n      return textContent(JSON.stringify(validateYaml(args.file, content), null, 2));\n    }\n    case \"write_config_file\": {\n      const disabled = ensureWriteEnabled();\n      if (disabled) return disabled;\n\n      assertKnownConfigFile(args.file);\n      if (typeof args.content !== \"string\") {\n        throw new Error(\"content must be a string\");\n      }\n      const validation = validateYaml(args.file, args.content);\n      if (!validation.valid) {\n        return {\n          isError: true,\n          ...textContent(JSON.stringify(validation, null, 2)),\n        };\n      }\n      mkdirSync(CONF_DIR, { recursive: true });\n      writeFileSync(configPath(args.file), args.content, \"utf8\");\n      return textContent(\n        JSON.stringify({ written: args.file, bytes: Buffer.byteLength(args.content, \"utf8\") }, null, 2),\n      );\n    }\n    case \"add_service\":\n      return addService(args);\n    case \"add_info_widget\":\n      return addInfoWidget(args);","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/utils/mcp/homepage-mcp.js#L391-L427","documentation":"Thrown by write_config_file when the `content` argument is not a string. Homepage writes the value verbatim to the config file, so it must already be valid YAML/CSS/JS text — objects or other types are rejected before the write to avoid corrupting config.","triggerScenarios":"write_config_file tool called with args.file valid but `typeof args.content !== 'string'`. Typical: caller sends a JSON object expecting the server to serialize it, sends a number/boolean, or omits content (and the field isn't handled by the optional branch above).","commonSituations":"MCP client serializes a config object instead of YAML-stringifying it; caller assumes the server accepts structured data; client omits content thinking the tool reads from elsewhere; templating engine emits a non-string.","solutions":["Pass content as a string containing valid YAML (or CSS/JS for custom files).","If you have a JS object, stringify it with js-yaml's dump (or JSON.stringify for non-YAML) before calling.","To validate without writing, use validate_config_file with the same string content first.","Ensure HOMEPAGE_MCP_ALLOW_WRITE=true is set or the tool returns a 'writing disabled' result before this check."],"exampleFix":"// before\n{ \"file\": \"services.yaml\", \"content\": [{ Infra: [] }] }\n\n// after\n{ \"file\": \"services.yaml\", \"content\": \"- Infra: []\\n\" }","handlingStrategy":"type-guard","validationCode":"function ensureContentString(content) {\n  if (typeof content !== 'string') {\n    throw new Error('content must be a string; serialize objects with js-yaml.dump or JSON.stringify');\n  }\n  return content;\n}","typeGuard":"function isContentString(v) {\n  return typeof v === 'string';\n}","tryCatchPattern":null,"preventionTips":["Always stringify config payloads (YAML/CSS/JS text) before write_config_file.","Validate with validate_config_file using the same string first.","Confirm HOMEPAGE_MCP_ALLOW_WRITE=true before attempting writes.","Keep a backup of the target file before programmatic writes."],"tags":["mcp","configuration","filesystem","types","validation"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}