{"record":{"id":"a9db271f46cabd31","repo":"DayuanJiang/next-ai-draw-io","slug":"addpagetodoc-opts-xml-must-be-a-bare-mxgraphmode","errorCode":null,"errorMessage":"addPageToDoc: opts.xml must be a bare <mxGraphModel>; received a full <mxfile>. Extract the target diagram's <mxGraphModel> first.","messagePattern":"addPageToDoc: opts\\.xml must be a bare <mxGraphModel>; received a full <mxfile>\\. Extract the target diagram's <mxGraphModel> first\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mcp-server/src/pages.ts","lineNumber":246,"sourceCode":" * Returns the new PageInfo. Throws if the requested id collides or the xml\n * shape is wrong.\n */\nexport function addPageToDoc(\n    doc: Document,\n    opts: { id?: string; name?: string; xml?: string } = {},\n): PageInfo {\n    const existing = listPagesFromDoc(doc)\n    const id = opts.id || generatePageId()\n    if (existing.some((p) => p.id === id)) {\n        throw new Error(`Page id \"${id}\" already exists`)\n    }\n    const name = opts.name || `Page-${existing.length + 1}`\n\n    let inner: string\n    if (opts.xml?.trim()) {\n        const trimmed = stripXmlDeclaration(opts.xml.trim())\n        if (isMxFile(trimmed)) {\n            throw new Error(\n                \"addPageToDoc: opts.xml must be a bare <mxGraphModel>; received a full <mxfile>. Extract the target diagram's <mxGraphModel> first.\",\n            )\n        }\n        if (!isMxGraphModel(trimmed)) {\n            throw new Error(\n                \"addPageToDoc: opts.xml must be a bare <mxGraphModel>.\",\n            )\n        }\n        inner = trimmed\n    } else {\n        inner = `<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/></root></mxGraphModel>`\n    }\n\n    const snippet = `<wrapper><diagram id=\"${escapeAttr(id)}\" name=\"${escapeAttr(name)}\">${inner}</diagram></wrapper>`\n    const tempDoc = new DOMParser().parseFromString(snippet, \"text/xml\")\n    if (tempDoc.querySelector(\"parsererror\")) {\n        throw new Error(\n            \"Failed to parse new page xml — make sure it is a valid <mxGraphModel>\",","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/DayuanJiang/next-ai-draw-io/blob/155ef4f7acd29c9d46fb6fc35c92e6e6955a6ce1/packages/mcp-server/src/pages.ts#L228-L264","documentation":"addPageToDoc requires opts.xml to be a single bare <mxGraphModel> element — the body of one page — because it wraps that markup inside a new <diagram> element. Passing a full multi-page <mxfile> would nest files illegally. This guard (isMxFile) fires before the generic shape check at line 251, which throws the same message for the same condition.","triggerScenarios":"Calling addPageToDoc(doc, { xml: fullDrawioFileString }) where the string's root element is <mxfile>, e.g. reusing the output of another file or of serializeToString on the whole document.","commonSituations":"Copy-pasting a .drawio file's contents as the xml option; piping output of one export directly into addPageToDoc; assuming the API accepts whole files rather than page fragments.","solutions":["Extract the target page's <mxGraphModel> first (see fix below) and pass that as opts.xml","Or use an API designed for whole-file imports if one exists instead of addPageToDoc","If you only need an empty page, omit opts.xml entirely"],"exampleFix":"// before\naddPageToDoc(doc, { xml: fs.readFileSync('other.drawio', 'utf8') })\n\n// after\nconst src = new DOMParser().parseFromString(fs.readFileSync('other.drawio', 'utf8'), 'text/xml')\nconst model = src.querySelector('mxfile > diagram > mxGraphModel')\naddPageToDoc(doc, { xml: new XMLSerializer().serializeToString(model) })","handlingStrategy":"type-guard","validationCode":"function extractFirstMxGraphModel(mxFileXml: string): string | null {\n  const doc = new DOMParser().parseFromString(mxFileXml, 'text/xml')\n  const m = doc.querySelector('mxfile > diagram > mxGraphModel') ?? doc.querySelector('mxGraphModel')\n  return m ? new XMLSerializer().serializeToString(m) : null\n}","typeGuard":"function isBareMxGraphModel(s: string): boolean {\n  return /^\\s*(<\\?xml[^>]*\\?>\\s*)?<mxGraphModel[\\s>]/i.test(s)\n}","tryCatchPattern":"try { addPageToDoc(doc, { xml }) } catch (e) { if ((e as Error).message.includes('bare <mxGraphModel>')) { const m = extractFirstMxGraphModel(xml); if (m) addPageToDoc(doc, { xml: m }) else throw e } else throw e }","preventionTips":["Always unwrap to the page-level mxGraphModel before passing xml","Prefer DOM extraction over regex/string slicing","Write a small import helper so every call site is correct"],"tags":["drawio","input-format","mxgraphmodel","validation"],"backgroundTag":"wrong-input-format","analyzedSha":"155ef4f7acd29c9d46fb6fc35c92e6e6955a6ce1","analyzedAt":"2026-08-27T11:40:38.297Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}