DayuanJiang/next-ai-draw-io · error · Error

addPageToDoc: opts.xml must be a bare

Error message

addPageToDoc: opts.xml must be a bare <mxGraphModel>; received a full <mxfile>. Extract the target diagram's <mxGraphModel> first.

What it means

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.

Solutions

  1. Extract the target page's <mxGraphModel> first (see fix below) and pass that as opts.xml
  2. Or use an API designed for whole-file imports if one exists instead of addPageToDoc
  3. If you only need an empty page, omit opts.xml entirely

Example fix

// before
addPageToDoc(doc, { xml: fs.readFileSync('other.drawio', 'utf8') })

// after
const src = new DOMParser().parseFromString(fs.readFileSync('other.drawio', 'utf8'), 'text/xml')
const model = src.querySelector('mxfile > diagram > mxGraphModel')
addPageToDoc(doc, { xml: new XMLSerializer().serializeToString(model) })
Defensive patterns

Strategy: type-guard

Validate before calling

function extractFirstMxGraphModel(mxFileXml: string): string | null {
  const doc = new DOMParser().parseFromString(mxFileXml, 'text/xml')
  const m = doc.querySelector('mxfile > diagram > mxGraphModel') ?? doc.querySelector('mxGraphModel')
  return m ? new XMLSerializer().serializeToString(m) : null
}

Type guard

function isBareMxGraphModel(s: string): boolean {
  return /^\s*(<\?xml[^>]*\?>\s*)?<mxGraphModel[\s>]/i.test(s)
}

Try / catch

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 }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of DayuanJiang/next-ai-draw-io@155ef4f7ac (2026-08-27). Data as JSON: /api/errors/a9db271f46cabd31. Report an issue: GitHub.

Appendix: source

Thrown at packages/mcp-server/src/pages.ts:246

 * Returns the new PageInfo. Throws if the requested id collides or the xml
 * shape is wrong.
 */
export function addPageToDoc(
    doc: Document,
    opts: { id?: string; name?: string; xml?: string } = {},
): PageInfo {
    const existing = listPagesFromDoc(doc)
    const id = opts.id || generatePageId()
    if (existing.some((p) => p.id === id)) {
        throw new Error(`Page id "${id}" already exists`)
    }
    const name = opts.name || `Page-${existing.length + 1}`

    let inner: string
    if (opts.xml?.trim()) {
        const trimmed = stripXmlDeclaration(opts.xml.trim())
        if (isMxFile(trimmed)) {
            throw new Error(
                "addPageToDoc: opts.xml must be a bare <mxGraphModel>; received a full <mxfile>. Extract the target diagram's <mxGraphModel> first.",
            )
        }
        if (!isMxGraphModel(trimmed)) {
            throw new Error(
                "addPageToDoc: opts.xml must be a bare <mxGraphModel>.",
            )
        }
        inner = trimmed
    } else {
        inner = `<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>`
    }

    const snippet = `<wrapper><diagram id="${escapeAttr(id)}" name="${escapeAttr(name)}">${inner}</diagram></wrapper>`
    const tempDoc = new DOMParser().parseFromString(snippet, "text/xml")
    if (tempDoc.querySelector("parsererror")) {
        throw new Error(
            "Failed to parse new page xml — make sure it is a valid <mxGraphModel>",

View on GitHub (pinned to 155ef4f7ac)