Jira ADF round-trip via Atlassian MCP
editJiraIssue atomically overwrites the whole description. Partial payloads delete content. Every edit is a round-trip: fetch full ADF → edit in files → validate → send full doc via MCP.
Details: round-trip-edit.md · node builders: adf-patterns.md
Markdown vs ADF
| Description shape | Use |
|---|---|
| Plain text, headings, bullets outside tables | contentFormat: "markdown" |
| Story table with bulleted Requirements / AC cells | contentFormat: "adf" |
Markdown cannot create bulletList inside tableCell.
Round-trip edit (required for updates)
Fetch —
getJiraIssuewithresponseContentFormat: "adf",fields: ["description"]. Save raw doc toagent-tools/<key>-desc-backup.json. Done when: file exists andvalidate-adf.jspasses.Edit — Copy backup to
<key>-desc-edited.json. Change only targetedtextnodes (typo fixes). Never rebuild from markdown or memory. Done when: diff is limited to intended strings.Preflight — Run both validators:
node scripts/validate-adf.js agent-tools/<key>-desc-edited.json node scripts/preflight-adf-edit.js agent-tools/<key>-desc-backup.json agent-tools/<key>-desc-edited.jsonDone when: both exit 0 and listItem counts per section are unchanged (unless intentionally adding bullets).
Payload file — Never inline ADF in
CallMcpTool:node scripts/build-edit-payload.js <KEY> agent-tools/<key>-desc-edited.jsonDone when:
agent-tools/<key>-edit-payload.jsonexists;node -e "require('./agent-tools/<key>-edit-payload.json')"parses.MCP update —
editJiraIssueonplugin-atlassian-atlassian. Passargumentsas the parsed payload object from step 4. If inline passing fails, delegate to a subagent that loads the file and calls MCP. Done when: MCP returns success.Verify —
getJiraIssuewithresponseContentFormat: "markdown". Done when: all table rows present (Story, Requirements, AC, etc.) and AC bullet count matches backup.
On verification failure: restore from backup via steps 4–5 immediately. Do not send another partial payload.
Create (new issue)
Build ADF from adf-patterns.md → validate-adf.js → createJiraIssue with contentFormat: "adf". Same file-backed rule if the doc is large.
Story table layout
Left: section label. Right: paragraph (Story) or bulletList → one listItem per bullet (Requirements, AC, Dependencies). Empty cells: empty paragraph, not lone -.
Markdown traps
| Pattern | Result |
|---|---|
- item in table cell |
Flat text or single item |
<br>- item |
One list item with literal <br>- |
Lone - in cell |
Empty list → * * * in UI |
__ in field names |
Italic parse breaks |
MCP tools
Read schemas in the MCP descriptor folder before calling.
| Action | Tool | Key params |
|---|---|---|
| Fetch | getJiraIssue |
responseContentFormat: "adf" |
| Update | editJiraIssue |
contentFormat: "adf", full fields.description |
| Create | createJiraIssue |
contentFormat: "adf" |
Cloud ID: getAccessibleAtlassianResources or known project UUID.
Error recovery
| Symptom | Fix |
|---|---|
| Ticket missing rows after edit | Partial payload sent — restore from *-desc-backup.json via full round-trip |
Failed to parse arguments |
Inline JSON truncated — use payload file + parsed require |
Bullets as * * * |
Empty bulletList or lone - — see adf-patterns |
| All requirements in one bullet | <br>- pattern — separate listItem per bullet |