@crypto512/jicon-mcp 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +73 -67
  2. package/TOOL_LIST.md +785 -133
  3. package/dist/config/constants.d.ts +18 -7
  4. package/dist/config/constants.d.ts.map +1 -1
  5. package/dist/config/constants.js +21 -8
  6. package/dist/config/constants.js.map +1 -1
  7. package/dist/config/loader.d.ts +11 -11
  8. package/dist/config/loader.d.ts.map +1 -1
  9. package/dist/config/loader.js +53 -93
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/types.d.ts +3 -6
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/config/types.js +2 -4
  14. package/dist/config/types.js.map +1 -1
  15. package/dist/confluence/formatters.js +1 -1
  16. package/dist/confluence/formatters.js.map +1 -1
  17. package/dist/confluence/tools.d.ts +8 -12
  18. package/dist/confluence/tools.d.ts.map +1 -1
  19. package/dist/confluence/tools.js +285 -233
  20. package/dist/confluence/tools.js.map +1 -1
  21. package/dist/index.js +17 -26
  22. package/dist/index.js.map +1 -1
  23. package/dist/jira/formatters.d.ts +1 -0
  24. package/dist/jira/formatters.d.ts.map +1 -1
  25. package/dist/jira/formatters.js +13 -12
  26. package/dist/jira/formatters.js.map +1 -1
  27. package/dist/jira/tools.d.ts +4 -0
  28. package/dist/jira/tools.d.ts.map +1 -1
  29. package/dist/jira/tools.js +234 -44
  30. package/dist/jira/tools.js.map +1 -1
  31. package/dist/permissions/tool-registry.d.ts +2 -2
  32. package/dist/permissions/tool-registry.d.ts.map +1 -1
  33. package/dist/permissions/tool-registry.js +4 -2
  34. package/dist/permissions/tool-registry.js.map +1 -1
  35. package/dist/permissions/write-home-validator.d.ts.map +1 -1
  36. package/dist/permissions/write-home-validator.js +13 -3
  37. package/dist/permissions/write-home-validator.js.map +1 -1
  38. package/dist/tempo/defaults.d.ts +17 -0
  39. package/dist/tempo/defaults.d.ts.map +1 -0
  40. package/dist/tempo/defaults.js +26 -0
  41. package/dist/tempo/defaults.js.map +1 -0
  42. package/dist/tempo/tools.d.ts +5 -0
  43. package/dist/tempo/tools.d.ts.map +1 -1
  44. package/dist/tempo/tools.js +161 -35
  45. package/dist/tempo/tools.js.map +1 -1
  46. package/dist/utils/buffer-pipeline/index.d.ts +30 -0
  47. package/dist/utils/buffer-pipeline/index.d.ts.map +1 -0
  48. package/dist/utils/buffer-pipeline/index.js +317 -0
  49. package/dist/utils/buffer-pipeline/index.js.map +1 -0
  50. package/dist/utils/buffer-pipeline/output/csv.d.ts +20 -0
  51. package/dist/utils/buffer-pipeline/output/csv.d.ts.map +1 -0
  52. package/dist/utils/buffer-pipeline/output/csv.js +117 -0
  53. package/dist/utils/buffer-pipeline/output/csv.js.map +1 -0
  54. package/dist/utils/buffer-pipeline/output/json.d.ts +16 -0
  55. package/dist/utils/buffer-pipeline/output/json.d.ts.map +1 -0
  56. package/dist/utils/buffer-pipeline/output/json.js +48 -0
  57. package/dist/utils/buffer-pipeline/output/json.js.map +1 -0
  58. package/dist/utils/buffer-pipeline/output/markdown.d.ts +15 -0
  59. package/dist/utils/buffer-pipeline/output/markdown.d.ts.map +1 -0
  60. package/dist/utils/buffer-pipeline/output/markdown.js +105 -0
  61. package/dist/utils/buffer-pipeline/output/markdown.js.map +1 -0
  62. package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts +16 -0
  63. package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts.map +1 -0
  64. package/dist/utils/buffer-pipeline/output/xhtml-list.js +81 -0
  65. package/dist/utils/buffer-pipeline/output/xhtml-list.js.map +1 -0
  66. package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts +15 -0
  67. package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts.map +1 -0
  68. package/dist/utils/buffer-pipeline/output/xhtml-table.js +176 -0
  69. package/dist/utils/buffer-pipeline/output/xhtml-table.js.map +1 -0
  70. package/dist/utils/buffer-pipeline/schema.d.ts +1878 -0
  71. package/dist/utils/buffer-pipeline/schema.d.ts.map +1 -0
  72. package/dist/utils/buffer-pipeline/schema.js +168 -0
  73. package/dist/utils/buffer-pipeline/schema.js.map +1 -0
  74. package/dist/utils/buffer-pipeline/stages/filter.d.ts +32 -0
  75. package/dist/utils/buffer-pipeline/stages/filter.d.ts.map +1 -0
  76. package/dist/utils/buffer-pipeline/stages/filter.js +208 -0
  77. package/dist/utils/buffer-pipeline/stages/filter.js.map +1 -0
  78. package/dist/utils/buffer-pipeline/stages/format.d.ts +45 -0
  79. package/dist/utils/buffer-pipeline/stages/format.d.ts.map +1 -0
  80. package/dist/utils/buffer-pipeline/stages/format.js +160 -0
  81. package/dist/utils/buffer-pipeline/stages/format.js.map +1 -0
  82. package/dist/utils/buffer-pipeline/stages/group-by.d.ts +25 -0
  83. package/dist/utils/buffer-pipeline/stages/group-by.d.ts.map +1 -0
  84. package/dist/utils/buffer-pipeline/stages/group-by.js +190 -0
  85. package/dist/utils/buffer-pipeline/stages/group-by.js.map +1 -0
  86. package/dist/utils/buffer-pipeline/stages/select.d.ts +54 -0
  87. package/dist/utils/buffer-pipeline/stages/select.d.ts.map +1 -0
  88. package/dist/utils/buffer-pipeline/stages/select.js +228 -0
  89. package/dist/utils/buffer-pipeline/stages/select.js.map +1 -0
  90. package/dist/utils/buffer-pipeline/stages/sort.d.ts +20 -0
  91. package/dist/utils/buffer-pipeline/stages/sort.d.ts.map +1 -0
  92. package/dist/utils/buffer-pipeline/stages/sort.js +96 -0
  93. package/dist/utils/buffer-pipeline/stages/sort.js.map +1 -0
  94. package/dist/utils/buffer-pipeline/types.d.ts +277 -0
  95. package/dist/utils/buffer-pipeline/types.d.ts.map +1 -0
  96. package/dist/utils/buffer-pipeline/types.js +8 -0
  97. package/dist/utils/buffer-pipeline/types.js.map +1 -0
  98. package/dist/utils/buffer-tools.d.ts +749 -19
  99. package/dist/utils/buffer-tools.d.ts.map +1 -1
  100. package/dist/utils/buffer-tools.js +738 -491
  101. package/dist/utils/buffer-tools.js.map +1 -1
  102. package/dist/utils/content-buffer.d.ts +55 -4
  103. package/dist/utils/content-buffer.d.ts.map +1 -1
  104. package/dist/utils/content-buffer.js +107 -9
  105. package/dist/utils/content-buffer.js.map +1 -1
  106. package/dist/utils/jicon-help.d.ts +1 -1
  107. package/dist/utils/jicon-help.d.ts.map +1 -1
  108. package/dist/utils/jicon-help.js +345 -99
  109. package/dist/utils/jicon-help.js.map +1 -1
  110. package/dist/utils/json-structure.d.ts +121 -0
  111. package/dist/utils/json-structure.d.ts.map +1 -0
  112. package/dist/utils/json-structure.js +637 -0
  113. package/dist/utils/json-structure.js.map +1 -0
  114. package/dist/utils/plantuml/include-expander.d.ts +31 -30
  115. package/dist/utils/plantuml/include-expander.d.ts.map +1 -1
  116. package/dist/utils/plantuml/include-expander.js +167 -133
  117. package/dist/utils/plantuml/include-expander.js.map +1 -1
  118. package/dist/utils/plantuml/index.d.ts +3 -3
  119. package/dist/utils/plantuml/index.d.ts.map +1 -1
  120. package/dist/utils/plantuml/index.js +4 -4
  121. package/dist/utils/plantuml/index.js.map +1 -1
  122. package/dist/utils/plantuml/service.d.ts +13 -24
  123. package/dist/utils/plantuml/service.d.ts.map +1 -1
  124. package/dist/utils/plantuml/service.js +49 -99
  125. package/dist/utils/plantuml/service.js.map +1 -1
  126. package/dist/utils/plantuml/tools.d.ts.map +1 -1
  127. package/dist/utils/plantuml/tools.js +33 -72
  128. package/dist/utils/plantuml/tools.js.map +1 -1
  129. package/dist/utils/plantuml/types.d.ts +1 -35
  130. package/dist/utils/plantuml/types.d.ts.map +1 -1
  131. package/dist/utils/plantuml/types.js +1 -11
  132. package/dist/utils/plantuml/types.js.map +1 -1
  133. package/dist/utils/plantuml/validation-helper.d.ts +1 -1
  134. package/dist/utils/plantuml/validation-helper.js +12 -12
  135. package/dist/utils/plantuml/validation-helper.js.map +1 -1
  136. package/dist/utils/response-formatter.d.ts +68 -0
  137. package/dist/utils/response-formatter.d.ts.map +1 -1
  138. package/dist/utils/response-formatter.js +186 -78
  139. package/dist/utils/response-formatter.js.map +1 -1
  140. package/dist/utils/url-tools.d.ts.map +1 -1
  141. package/dist/utils/url-tools.js +22 -0
  142. package/dist/utils/url-tools.js.map +1 -1
  143. package/dist/utils/xhtml/error-locator.js +2 -2
  144. package/dist/utils/xhtml/error-locator.js.map +1 -1
  145. package/dist/utils/xhtml/index.d.ts +1 -1
  146. package/dist/utils/xhtml/index.d.ts.map +1 -1
  147. package/dist/utils/xhtml/index.js +1 -1
  148. package/dist/utils/xhtml/index.js.map +1 -1
  149. package/dist/utils/xhtml/parser.d.ts +34 -5
  150. package/dist/utils/xhtml/parser.d.ts.map +1 -1
  151. package/dist/utils/xhtml/parser.js +66 -11
  152. package/dist/utils/xhtml/parser.js.map +1 -1
  153. package/dist/utils/xhtml/plantuml.d.ts.map +1 -1
  154. package/dist/utils/xhtml/plantuml.js +5 -3
  155. package/dist/utils/xhtml/plantuml.js.map +1 -1
  156. package/dist/utils/xhtml/serializer.d.ts.map +1 -1
  157. package/dist/utils/xhtml/serializer.js +12 -15
  158. package/dist/utils/xhtml/serializer.js.map +1 -1
  159. package/dist/utils/xhtml/types.d.ts +1 -0
  160. package/dist/utils/xhtml/types.d.ts.map +1 -1
  161. package/package.json +12 -4
@@ -2,12 +2,12 @@
2
2
  * Confluence MCP Tools
3
3
  */
4
4
  import { z } from "zod";
5
- import { formatSuccess, formatError, isApiError } from "../utils/response-formatter.js";
5
+ import { formatSuccess, formatSuccessJson, formatSuccessXhtml, formatError, isApiError } from "../utils/response-formatter.js";
6
6
  import { contentBuffer } from "../utils/content-buffer.js";
7
7
  import { formatPageMetadata } from "./formatters.js";
8
- import { validateXhtmlAsync, parseXhtml, parseStructure, serializeXhtml, enhanceXhtmlError } from "../utils/xhtml/index.js";
8
+ import { validateXhtmlAsync, parseXhtml, enhanceXhtmlError } from "../utils/xhtml/index.js";
9
9
  import { detectRawPlantUml, detectDiagramType } from "../utils/xhtml/plantuml.js";
10
- import { expandPlantUmlInXhtml, collapseExpandedIncludesInXhtml } from "../utils/plantuml/index.js";
10
+ import { convertPlantUmlIncludesInXhtml, IncludeConversionError } from "../utils/plantuml/index.js";
11
11
  import { parseUrl } from "../utils/url-tools.js";
12
12
  import { DEFAULT_PAGE_EXPAND } from "./defaults.js";
13
13
  /**
@@ -46,72 +46,17 @@ function getContentSummary(content) {
46
46
  result.headingCount = doc.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
47
47
  return result;
48
48
  }
49
- /**
50
- * Resolve content from either direct content string or bufferId.
51
- * Returns { content, error } - if error is set, return it as the tool result.
52
- */
53
- function resolveContentFromBuffer(contentArg, bufferIdArg) {
54
- // Exactly one of content or bufferId must be provided
55
- if (contentArg && bufferIdArg) {
56
- return {
57
- error: formatError({
58
- error: true,
59
- message: "Provide either 'content' or 'bufferId', not both",
60
- statusCode: 400,
61
- }),
62
- };
63
- }
64
- if (bufferIdArg) {
65
- const bufferChunk = contentBuffer.getChunk(bufferIdArg);
66
- if (!bufferChunk) {
67
- return {
68
- error: formatError({
69
- error: true,
70
- message: `Buffer ${bufferIdArg} not found or expired`,
71
- statusCode: 404,
72
- }),
73
- };
74
- }
75
- // Validate that buffer contains XHTML content for Confluence
76
- const bufferInfo = contentBuffer.getInfo(bufferIdArg);
77
- if (bufferInfo?.metadata?.contentType && bufferInfo.metadata.contentType !== "xhtml") {
78
- return {
79
- error: formatError({
80
- error: true,
81
- message: `Buffer ${bufferIdArg} contains ${bufferInfo.metadata.contentType} content, but Confluence requires XHTML.`,
82
- statusCode: 400,
83
- details: {
84
- hint: "Create Confluence content with buffer_create, then edit with buffer_edit.",
85
- foundContentType: bufferInfo.metadata.contentType,
86
- expectedContentType: "xhtml",
87
- },
88
- }),
89
- };
90
- }
91
- // Get full content from buffer
92
- const fullContent = contentBuffer.getChunk(bufferIdArg, 0, bufferChunk.totalSize);
93
- if (!fullContent) {
94
- return {
95
- error: formatError({
96
- error: true,
97
- message: "Failed to retrieve full buffer content",
98
- statusCode: 500,
99
- }),
100
- };
101
- }
102
- return { content: fullContent.chunk };
103
- }
104
- // Direct content provided
105
- return { content: contentArg };
106
- }
107
49
  /**
108
50
  * Validate XHTML content before Confluence write operations.
109
51
  * Returns error result if validation fails, null if valid.
52
+ * When bufferId is provided, includes it in error response for recovery.
110
53
  */
111
- async function validateContentForWrite(content) {
54
+ async function validateContentForWrite(content, bufferId) {
112
55
  const validation = await validateXhtmlAsync(content, { validatePlantUml: true });
113
56
  if (!validation.valid) {
114
57
  const errorMessages = [];
58
+ let errorElementId;
59
+ let errorContext;
115
60
  // XHTML structure errors
116
61
  if (validation.errors && validation.errors.length > 0) {
117
62
  errorMessages.push("XHTML validation errors:");
@@ -124,6 +69,14 @@ async function validateContentForWrite(content) {
124
69
  ? err.location.context.substring(0, 60) + "..."
125
70
  : err.location.context;
126
71
  errorMessages.push(` Near: "${contextPreview}"`);
72
+ // Capture first error context for recovery
73
+ if (!errorContext) {
74
+ errorContext = err.location.context;
75
+ }
76
+ }
77
+ // Capture first error elementId for recovery
78
+ if (!errorElementId && err.location?.elementId) {
79
+ errorElementId = err.location.elementId;
127
80
  }
128
81
  });
129
82
  }
@@ -151,10 +104,18 @@ async function validateContentForWrite(content) {
151
104
  errorMessages.push(" </ac:structured-macro>");
152
105
  }
153
106
  }
154
- // Add help hints
107
+ // Add recovery instructions
155
108
  errorMessages.push("");
156
- errorMessages.push('TIP: Call help(topic="storage") for Confluence XHTML format guide.');
157
- errorMessages.push('TIP: Call help(topic="plantuml") for PlantUML macro examples.');
109
+ if (bufferId) {
110
+ errorMessages.push(`RECOVERY: Use buffer_edit(bufferId="${bufferId}", ...) to fix errors.`);
111
+ if (errorElementId) {
112
+ errorMessages.push(` buffer_edit(bufferId="${bufferId}", replace=${errorElementId}, content="<fixed>...</fixed>")`);
113
+ }
114
+ else if (errorContext) {
115
+ errorMessages.push(` Use buffer_grep(bufferId="${bufferId}", pattern="...") to find the error location.`);
116
+ }
117
+ }
118
+ errorMessages.push('TIP: Call help(topic="storage") for XHTML syntax (HTML vs XHTML differences).');
158
119
  errorMessages.push("");
159
120
  errorMessages.push("ACTION REQUIRED: Fix content errors before calling this tool again.");
160
121
  errorMessages.push("DO NOT claim success - the draft was NOT created.");
@@ -164,34 +125,31 @@ async function validateContentForWrite(content) {
164
125
  statusCode: 400,
165
126
  details: {
166
127
  validationErrors: errorMessages,
167
- hint: "Use buffer_validate_xhtml to check content, or plantuml_validate for PlantUML syntax",
128
+ ...(bufferId && { bufferId }),
129
+ ...(errorElementId && { errorElementId }),
130
+ ...(errorContext && { errorContext }),
131
+ hint: errorElementId
132
+ ? `Use buffer_edit(bufferId="${bufferId}", replace=${errorElementId}, content="...") to fix`
133
+ : `Use buffer_grep to find the error, then buffer_edit to fix`,
168
134
  },
169
135
  });
170
136
  }
171
137
  return null; // Valid content
172
138
  }
173
- /**
174
- * Store XHTML content in buffer with element IDs for structured editing.
175
- * Parses XHTML, assigns data-jicon-id attributes, and stores with structure.
176
- * Content is validated before Confluence writes, so parsing always succeeds.
177
- */
178
- function storeXhtmlWithStructure(content, metadata) {
179
- const parseResult = parseXhtml(content);
180
- const structureResult = parseStructure(parseResult.document);
181
- const contentWithIds = serializeXhtml(parseResult.document);
182
- const bufferId = contentBuffer.storeWithStructure(contentWithIds, structureResult.structure, structureResult.nextId, metadata);
183
- return { bufferId, structure: structureResult.structure };
184
- }
185
139
  export function createConfluenceTools(client) {
186
140
  return {
187
141
  confluence_search_content: {
188
142
  description: `Search Confluence content using CQL. Auto-fetches all results (up to 5000).
189
143
 
190
- TIP: See help(topic="cql") for CQL syntax guide.
144
+ Entry point for finding Confluence pages by content, title, or metadata.
145
+
146
+ REQUIRES: Valid CQL query (see help(topic="cql") for syntax)
147
+ RETURNS: bufferId (JSON array), itemCount with page IDs and titles
148
+ NEXT: confluence_get_page (to load), buffer_pipeline (for reports), buffer_grep (to search results)
191
149
 
192
- Example: type=page AND space=DOCS AND text~"API" ORDER BY lastmodified DESC
150
+ Example: confluence_search_content(cql="type=page AND space=DOCS AND text~'API'")
193
151
 
194
- WARNING: Use text~ (not content~ or body~). Use space KEY (not name).`,
152
+ WARNING: Use text~ not content~. Use space KEY not name.`,
195
153
  inputSchema: z.object({
196
154
  cql: z.string().describe("CQL query string"),
197
155
  expand: z.array(z.string()).optional().describe("Additional data to expand"),
@@ -199,8 +157,10 @@ WARNING: Use text~ (not content~ or body~). Use space KEY (not name).`,
199
157
  handler: async (args) => {
200
158
  try {
201
159
  const result = await client.searchContentAll(args.cql, args.expand);
202
- // Use formatSuccess for automatic buffering of large responses
203
- return formatSuccess(result);
160
+ return formatSuccessJson(result, {
161
+ resourceType: "confluence_search",
162
+ title: `CQL: ${args.cql.substring(0, 100)}${args.cql.length > 100 ? "..." : ""}`,
163
+ });
204
164
  }
205
165
  catch (error) {
206
166
  // Enhanced error handling for common CQL errors
@@ -269,18 +229,19 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add conten
269
229
  handler: async (args) => {
270
230
  try {
271
231
  const result = await client.getPage(args.pageId, args.expand);
272
- const rawContent = result.body?.storage?.value || "";
273
- // Collapse expanded includes back to !include directives
274
- const content = collapseExpandedIncludesInXhtml(rawContent);
232
+ const content = result.body?.storage?.value || "";
275
233
  // Store content with element IDs for structured editing
276
- const { bufferId, structure } = storeXhtmlWithStructure(content, {
234
+ const xhtmlResult = formatSuccessXhtml(content, {
277
235
  resourceType: "confluence_page",
278
236
  resourceId: String(result.id),
279
- contentType: "xhtml",
280
237
  version: result.version?.number,
281
238
  spaceKey: result.space?.key,
282
- title: result.title,
239
+ title: result.title || args.pageId,
283
240
  });
241
+ if ("errorResult" in xhtmlResult) {
242
+ return xhtmlResult.errorResult;
243
+ }
244
+ const { bufferId, structure } = xhtmlResult;
284
245
  return formatSuccess({
285
246
  ...formatPageMetadata(result),
286
247
  pageId: result.id,
@@ -318,18 +279,19 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add conten
318
279
  statusCode: 404,
319
280
  });
320
281
  }
321
- const rawContent = result.body?.storage?.value || "";
322
- // Collapse expanded includes back to !include directives
323
- const content = collapseExpandedIncludesInXhtml(rawContent);
282
+ const content = result.body?.storage?.value || "";
324
283
  // Store content with element IDs for structured editing
325
- const { bufferId, structure } = storeXhtmlWithStructure(content, {
284
+ const xhtmlResult = formatSuccessXhtml(content, {
326
285
  resourceType: "confluence_page",
327
286
  resourceId: String(result.id),
328
- contentType: "xhtml",
329
287
  version: result.version?.number,
330
288
  spaceKey: result.space?.key,
331
- title: result.title,
289
+ title: result.title || args.title,
332
290
  });
291
+ if ("errorResult" in xhtmlResult) {
292
+ return xhtmlResult.errorResult;
293
+ }
294
+ const { bufferId, structure } = xhtmlResult;
333
295
  return formatSuccess({
334
296
  ...formatPageMetadata(result),
335
297
  pageId: result.id,
@@ -346,27 +308,11 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add conten
346
308
  },
347
309
  },
348
310
  confluence_edit: {
349
- description: `Smart page/draft loader - auto-resolves URLs, pageIds, draftIds, or SPACE/Title.
311
+ description: `Load page/draft for editing. Returns bufferId, structure (element IDs), pageId, version.
350
312
 
351
- ACCEPTS ANY OF:
352
- - Full URL: https://confluence.example.com/pages/viewpage.action?pageId=123
353
- - Full URL: https://confluence.example.com/pages/resumedraft.action?draftId=456
354
- - Full URL: https://confluence.example.com/display/SPACE/Page+Title
355
- - Page ID: "123456"
356
- - Draft ID: "draft:123456" (prefix with "draft:")
357
- - Space/Title: "DOCS/API Guide"
358
-
359
- SMART BEHAVIOR:
360
- - URLs are parsed automatically to extract pageId or draftId
361
- - Draft IDs: tries to load draft; if 404 (published), finds page by title
362
- - Returns bufferId + structure + pageId for editing
363
-
364
- WORKFLOW:
365
- 1. confluence_edit(input) → bufferId, structure, pageId
366
- 2. buffer_edit(bufferId, ...) → modify content
367
- 3. confluence_draft_create(pageId=..., bufferId=...) → draft linked to original page
368
- 4. User publishes via Confluence UI (updates original page)
369
- 5. For more edits: confluence_edit(same URL or "SPACE/Title") → auto-resolves`,
313
+ ACCEPTS: URL | pageId | "draft:ID" | "SPACE/Title"
314
+ RETURNS: bufferId, structure, pageId, version
315
+ NEXT: buffer_edit confluence_draft_create → confluence_review_publish`,
370
316
  inputSchema: z.object({
371
317
  input: z.string().describe('URL, pageId, "draft:ID", or "SPACE/Title"'),
372
318
  }),
@@ -381,16 +327,18 @@ WORKFLOW:
381
327
  }
382
328
  // Helper to load page content and return formatted result
383
329
  const loadPageContent = async (pageResult) => {
384
- const rawContent = pageResult.body?.storage?.value || "";
385
- const content = collapseExpandedIncludesInXhtml(rawContent);
386
- const { bufferId, structure } = storeXhtmlWithStructure(content, {
330
+ const content = pageResult.body?.storage?.value || "";
331
+ const xhtmlResult = formatSuccessXhtml(content, {
387
332
  resourceType: "confluence_page",
388
333
  resourceId: String(pageResult.id),
389
- contentType: "xhtml",
390
334
  version: pageResult.version?.number,
391
335
  spaceKey: pageResult.space?.key,
392
336
  title: pageResult.title,
393
337
  });
338
+ if ("errorResult" in xhtmlResult) {
339
+ throw new Error("Failed to parse XHTML content");
340
+ }
341
+ const { bufferId, structure } = xhtmlResult;
394
342
  return {
395
343
  pageId: pageResult.id,
396
344
  spaceKey: pageResult.space?.key,
@@ -405,16 +353,18 @@ WORKFLOW:
405
353
  // Helper to load draft content
406
354
  const loadDraftContent = async (draftId) => {
407
355
  const result = await client.getDraft(draftId);
408
- const rawContent = result.body?.storage?.value || "";
409
- const content = collapseExpandedIncludesInXhtml(rawContent);
410
- const { bufferId, structure } = storeXhtmlWithStructure(content, {
411
- resourceType: "confluence_page",
356
+ const content = result.body?.storage?.value || "";
357
+ const xhtmlResult = formatSuccessXhtml(content, {
358
+ resourceType: "confluence_draft",
412
359
  resourceId: String(result.id),
413
- contentType: "xhtml",
414
360
  spaceKey: result.space?.key,
415
361
  title: result.title,
416
362
  isDraft: true,
417
363
  });
364
+ if ("errorResult" in xhtmlResult) {
365
+ throw new Error("Failed to parse XHTML content");
366
+ }
367
+ const { bufferId, structure } = xhtmlResult;
418
368
  return {
419
369
  draftId: result.id,
420
370
  spaceKey: result.space?.key,
@@ -538,7 +488,15 @@ WORKFLOW:
538
488
  // - New pages: confluence_draft_create → user publishes
539
489
  // - Existing pages: confluence_get_page → buffer_edit → confluence_draft_create → user publishes
540
490
  confluence_delete_page: {
541
- description: "Delete a Confluence page",
491
+ description: `Delete a Confluence page permanently.
492
+
493
+ Page is moved to trash. Use with caution.
494
+
495
+ REQUIRES: pageId
496
+ RETURNS: Success confirmation
497
+ NEXT: None
498
+
499
+ Example: confluence_delete_page(pageId="123456")`,
542
500
  inputSchema: z.object({
543
501
  pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
544
502
  }),
@@ -561,7 +519,15 @@ WORKFLOW:
561
519
  },
562
520
  },
563
521
  confluence_list_spaces: {
564
- description: `List all accessible Confluence spaces.`,
522
+ description: `List all accessible Confluence spaces.
523
+
524
+ Discover available spaces and their keys for use in CQL queries.
525
+
526
+ REQUIRES: None (optional type filter)
527
+ RETURNS: bufferId with spaces array (key, name, type)
528
+ NEXT: confluence_get_space (for details), confluence_search_content (to query space)
529
+
530
+ Example: confluence_list_spaces(type="global")`,
565
531
  inputSchema: z.object({
566
532
  type: z
567
533
  .enum(["global", "personal"])
@@ -571,7 +537,10 @@ WORKFLOW:
571
537
  handler: async (args) => {
572
538
  try {
573
539
  const result = await client.listSpaces(args.type);
574
- return formatSuccess(result);
540
+ return formatSuccessJson(result, {
541
+ resourceType: "confluence_spaces",
542
+ title: args.type ? `${args.type} spaces` : "All Spaces",
543
+ });
575
544
  }
576
545
  catch (error) {
577
546
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -579,7 +548,15 @@ WORKFLOW:
579
548
  },
580
549
  },
581
550
  confluence_get_space: {
582
- description: "Get detailed information about a space",
551
+ description: `Get detailed information about a Confluence space.
552
+
553
+ View space configuration, homepage, and description.
554
+
555
+ REQUIRES: spaceKey (short code like 'DOCS')
556
+ RETURNS: bufferId with space details (name, homepage, description)
557
+ NEXT: confluence_search_content (to query space), confluence_get_page (homepage)
558
+
559
+ Example: confluence_get_space(spaceKey="DOCS")`,
583
560
  inputSchema: z.object({
584
561
  spaceKey: z.string().describe("Space key"),
585
562
  expand: z.array(z.string()).optional().describe("Additional data to expand"),
@@ -587,8 +564,11 @@ WORKFLOW:
587
564
  handler: async (args) => {
588
565
  try {
589
566
  const result = await client.getSpace(args.spaceKey, args.expand);
590
- // Use formatSuccess for automatic buffering of large responses
591
- return formatSuccess(result);
567
+ return formatSuccessJson(result, {
568
+ resourceType: "confluence_space",
569
+ title: result.name || args.spaceKey,
570
+ spaceKey: args.spaceKey,
571
+ });
592
572
  }
593
573
  catch (error) {
594
574
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -596,7 +576,9 @@ WORKFLOW:
596
576
  },
597
577
  },
598
578
  confluence_get_page_children: {
599
- description: `Get all child pages of a page.`,
579
+ description: `Get all child pages of a page.
580
+
581
+ Returns bufferId. Use buffer_get_chunk to read, buffer_grep to search.`,
600
582
  inputSchema: z.object({
601
583
  pageId: z.coerce.string().describe("Parent page ID (accepts string or number)"),
602
584
  expand: z.array(z.string()).optional().describe("Additional data to expand"),
@@ -604,7 +586,11 @@ WORKFLOW:
604
586
  handler: async (args) => {
605
587
  try {
606
588
  const result = await client.getPageChildren(args.pageId, args.expand);
607
- return formatSuccess(result);
589
+ return formatSuccessJson(result, {
590
+ resourceType: "confluence_page_children",
591
+ title: `Page ${args.pageId} children`,
592
+ pageId: args.pageId,
593
+ });
608
594
  }
609
595
  catch (error) {
610
596
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -628,14 +614,20 @@ WORKFLOW:
628
614
  },
629
615
  },
630
616
  confluence_get_comments: {
631
- description: `Get all comments on a Confluence page.`,
617
+ description: `Get all comments on a Confluence page.
618
+
619
+ Returns bufferId. Use buffer_get_chunk to read, buffer_grep to search.`,
632
620
  inputSchema: z.object({
633
621
  pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
634
622
  }),
635
623
  handler: async (args) => {
636
624
  try {
637
625
  const result = await client.getComments(args.pageId);
638
- return formatSuccess(result);
626
+ return formatSuccessJson(result, {
627
+ resourceType: "confluence_comments",
628
+ title: `Page ${args.pageId} comments`,
629
+ pageId: args.pageId,
630
+ });
639
631
  }
640
632
  catch (error) {
641
633
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -660,14 +652,20 @@ WORKFLOW:
660
652
  },
661
653
  },
662
654
  confluence_list_attachments: {
663
- description: `List all attachments on a Confluence page.`,
655
+ description: `List all attachments on a Confluence page.
656
+
657
+ Returns bufferId. Use buffer_get_chunk to read, buffer_grep to search.`,
664
658
  inputSchema: z.object({
665
659
  pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
666
660
  }),
667
661
  handler: async (args) => {
668
662
  try {
669
663
  const result = await client.listAttachments(args.pageId);
670
- return formatSuccess(result);
664
+ return formatSuccessJson(result, {
665
+ resourceType: "confluence_attachments",
666
+ title: `Page ${args.pageId} attachments`,
667
+ pageId: args.pageId,
668
+ });
671
669
  }
672
670
  catch (error) {
673
671
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -675,14 +673,8 @@ WORKFLOW:
675
673
  },
676
674
  },
677
675
  confluence_get_current_user_space: {
678
- description: `Get the current user's personal/home Confluence space.
679
-
680
- Use this tool when the user asks about:
681
- - "my space", "my home space", "my personal space"
682
- - "confluence home", "home space"
683
- - "where can I write", "my confluence area"
684
-
685
- Returns the user's personal space key and details. Use this to verify your personal space before write operations when write-home restriction is enabled.`,
676
+ description: `Get the current user's personal Confluence space. Returns spaceKey and homePageId.
677
+ Use to verify personal space before write operations when write-home restriction is enabled.`,
686
678
  inputSchema: z.object({}),
687
679
  handler: async () => {
688
680
  try {
@@ -714,39 +706,19 @@ Returns the user's personal space key and details. Use this to verify your perso
714
706
  // Note: Confluence drafts cannot be updated via API, only created, read, or deleted.
715
707
  // The workflow is: create draft → edit locally in buffer → save (delete+recreate) → publish
716
708
  confluence_draft_create: {
717
- description: `Create a Confluence draft for user review. Returns draftId, bufferId, structure (element IDs), and clickable URL.
709
+ description: `Create draft for user review. Requires bufferId with valid XHTML.
718
710
 
719
- IMPORTANT: Call help(topic="plantuml") BEFORE creating content with PlantUML diagrams.
720
- IMPORTANT: Call help(topic="storage") BEFORE creating XHTML content for proper syntax.
721
- IMPORTANT: User must validate the draft in Confluence UI before publishing.
722
- IMPORTANT: Raw @startuml outside macros is NOT supported. Use buffer_edit with plantuml parameter.
711
+ NEW page: confluence_draft_create(spaceKey, title, bufferId)
712
+ EDIT page: confluence_edit buffer_edit confluence_draft_create(pageId, bufferId)
723
713
 
724
- Two modes:
725
- 1. NEW PAGE: Provide spaceKey + title + content/bufferId creates standalone draft
726
- 2. EDIT EXISTING PAGE (Review Workflow): Provide pageId + bufferId
727
- - Creates "[jicon-mcp REVIEW] Title" draft linked to original via label
728
- - REQUIRED: bufferId must come from confluence_get_page(pageId) or confluence_edit(pageId)
729
- - Use confluence_review_publish(draftId) to apply changes to original
730
- - Use confluence_review_discard(draftId) to cancel without changes
731
- - Use confluence_review_list() to find all review drafts
732
-
733
- Workflow for editing existing page:
734
- 1. confluence_get_page(pageId) → bufferId, structure
735
- 2. buffer_edit(bufferId, ...) → modify content
736
- 3. confluence_draft_create(pageId=..., bufferId=...) → creates review draft
737
- 4. User reviews in Confluence UI
738
- 5. confluence_review_publish(draftId) → applies changes to original page
739
-
740
- Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit).`,
714
+ Draft title auto-prefixed with "[jicon-mcp REVIEW]" when editing existing pages.
715
+ RETURNS: draftId, bufferId, structure, url. On XHTML error: errorElementId for buffer_edit fix.
716
+ WARNING: pageId must be a PAGE ID, not a draft ID. Use confluence_draft_open for existing drafts.`,
741
717
  inputSchema: z.object({
742
- pageId: z.coerce.string().optional().describe("Existing page ID to create edit draft for. When provided, bufferId must come from that page."),
718
+ pageId: z.coerce.string().optional().describe("Existing PAGE ID to edit (NOT a draft ID). Get from confluence_get_page or confluence_edit. When provided, bufferId must come from that page."),
743
719
  spaceKey: z.string().optional().describe("Space key (required for new pages, auto-populated when pageId is provided)"),
744
720
  title: z.string().optional().describe("Page title (required for new pages, auto-populated when pageId is provided)"),
745
- content: z
746
- .string()
747
- .optional()
748
- .describe("Page content in Confluence storage format (XHTML-based)"),
749
- bufferId: z.string().optional().describe("Buffer ID containing content (alternative to content)"),
721
+ bufferId: z.string().describe("Buffer ID containing XHTML content (from buffer_create or confluence_get_page)"),
750
722
  parentId: z.string().optional().describe("Parent page ID"),
751
723
  labels: z.array(z.string()).optional().describe("Array of labels"),
752
724
  }),
@@ -757,27 +729,41 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
757
729
  let parentId = args.parentId;
758
730
  let originalPageId;
759
731
  let originalPageVersion;
732
+ // Validate buffer exists and get content
733
+ const bufferInfo = contentBuffer.getInfo(args.bufferId);
734
+ if (!bufferInfo) {
735
+ return formatError({
736
+ error: true,
737
+ message: `Buffer not found or expired: ${args.bufferId}`,
738
+ statusCode: 404,
739
+ details: {
740
+ hint: "Create a buffer first: buffer_create(content='<h1>...</h1>', contentType='xhtml')",
741
+ },
742
+ });
743
+ }
744
+ // Validate buffer contains XHTML content
745
+ if (bufferInfo.metadata?.contentType && bufferInfo.metadata.contentType !== "xhtml") {
746
+ return formatError({
747
+ error: true,
748
+ message: `Buffer '${args.bufferId}' is not XHTML content (found: ${bufferInfo.metadata.contentType})`,
749
+ statusCode: 400,
750
+ details: {
751
+ hint: "Use buffer_create(content='...', contentType='xhtml') to create an XHTML buffer",
752
+ },
753
+ });
754
+ }
755
+ // Get full content from buffer
756
+ const fullContent = contentBuffer.getChunk(args.bufferId, 0, bufferInfo.bufferSizeBytes);
757
+ if (!fullContent) {
758
+ return formatError({
759
+ error: true,
760
+ message: "Failed to retrieve buffer content",
761
+ statusCode: 500,
762
+ });
763
+ }
764
+ const content = fullContent.chunk;
760
765
  if (args.pageId) {
761
766
  // MODE: Edit existing page - validate bufferId came from this page
762
- if (!args.bufferId) {
763
- return formatError({
764
- error: true,
765
- message: "When pageId is provided, bufferId is required (must come from confluence_get_page or confluence_edit of that page)",
766
- statusCode: 400,
767
- details: {
768
- hint: "First call confluence_get_page(pageId) or confluence_edit(pageId) to get a bufferId, then modify with buffer_edit",
769
- },
770
- });
771
- }
772
- // Validate buffer originated from the specified page
773
- const bufferInfo = contentBuffer.getInfo(args.bufferId);
774
- if (!bufferInfo) {
775
- return formatError({
776
- error: true,
777
- message: `Buffer not found or expired: ${args.bufferId}`,
778
- statusCode: 404,
779
- });
780
- }
781
767
  const bufferSourceId = bufferInfo.metadata?.resourceId;
782
768
  if (bufferSourceId !== args.pageId) {
783
769
  return formatError({
@@ -811,10 +797,18 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
811
797
  }
812
798
  }
813
799
  catch (error) {
800
+ // Provide helpful hint if they might have passed a draftId instead of pageId
801
+ const errorMessage = isApiError(error) ? error.message :
802
+ error instanceof Error ? error.message : String(error);
814
803
  return formatError({
815
804
  error: true,
816
- message: `Failed to fetch original page ${args.pageId}: ${error instanceof Error ? error.message : String(error)}`,
805
+ message: `Page '${args.pageId}' not found. If this is a draft ID (from confluence_draft_create), use confluence_draft_open to continue editing instead.`,
817
806
  statusCode: 404,
807
+ details: {
808
+ originalError: errorMessage,
809
+ hint: "For existing drafts: confluence_draft_open(draftId) → buffer_edit → confluence_draft_save. For new drafts from existing pages: confluence_edit(pageId) → buffer_edit → confluence_draft_create(pageId, bufferId)",
810
+ pageIdProvided: args.pageId,
811
+ },
818
812
  });
819
813
  }
820
814
  }
@@ -828,19 +822,6 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
828
822
  });
829
823
  }
830
824
  }
831
- // Resolve content from either content string or bufferId
832
- const resolved = resolveContentFromBuffer(args.content, args.bufferId);
833
- if (resolved.error) {
834
- return resolved.error;
835
- }
836
- if (!resolved.content) {
837
- return formatError({
838
- error: true,
839
- message: "Either 'content' or 'bufferId' must be provided",
840
- statusCode: 400,
841
- });
842
- }
843
- const content = resolved.content;
844
825
  // Check for raw PlantUML that should use buffer_edit with plantuml parameter
845
826
  const rawPlantUml = detectRawPlantUml(content);
846
827
  if (rawPlantUml) {
@@ -855,12 +836,30 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
855
836
  });
856
837
  }
857
838
  // Validate XHTML and PlantUML content before writing
858
- const validationError = await validateContentForWrite(content);
839
+ const validationError = await validateContentForWrite(content, args.bufferId);
859
840
  if (validationError) {
860
841
  return validationError;
861
842
  }
862
- // Expand PlantUML !include directives before sending to Confluence
863
- const expandedContent = await expandPlantUmlInXhtml(content);
843
+ // Convert PlantUML !include URLs to stdlib syntax before sending to Confluence
844
+ let expandedContent;
845
+ try {
846
+ expandedContent = convertPlantUmlIncludesInXhtml(content);
847
+ }
848
+ catch (error) {
849
+ if (error instanceof IncludeConversionError) {
850
+ return formatError({
851
+ error: true,
852
+ message: error.message,
853
+ statusCode: 400,
854
+ details: {
855
+ url: error.url,
856
+ suggestion: error.suggestion,
857
+ hint: "Replace the HTTP URL with the suggested stdlib syntax in your PlantUML diagram",
858
+ },
859
+ });
860
+ }
861
+ throw error;
862
+ }
864
863
  try {
865
864
  // Constants for review workflow
866
865
  const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
@@ -922,16 +921,19 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
922
921
  }
923
922
  // Store content with element IDs for structured editing
924
923
  // Track originalPageId in metadata so we can link the draft to its source
925
- const { bufferId: newBufferId } = storeXhtmlWithStructure(content, {
926
- resourceType: "confluence_page",
924
+ const xhtmlResult = formatSuccessXhtml(content, {
925
+ resourceType: "confluence_draft",
927
926
  resourceId: result.id,
928
- contentType: "xhtml",
929
927
  spaceKey: result.space?.key,
930
928
  title: result.title,
931
929
  isDraft: true,
932
930
  originalPageId: originalPageId,
933
931
  originalPageVersion: originalPageVersion,
934
932
  });
933
+ if ("errorResult" in xhtmlResult) {
934
+ return xhtmlResult.errorResult;
935
+ }
936
+ const newBufferId = xhtmlResult.bufferId;
935
937
  // Build the draft edit URL (drafts use resumedraft.action, not the webui link)
936
938
  const baseUrl = client.getBaseUrl();
937
939
  const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${result.id}`;
@@ -1007,18 +1009,19 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to modify, th
1007
1009
  handler: async (args) => {
1008
1010
  try {
1009
1011
  const result = await client.getDraft(args.draftId);
1010
- const rawContent = result.body?.storage?.value || "";
1011
- // Collapse expanded includes back to !include directives
1012
- const content = collapseExpandedIncludesInXhtml(rawContent);
1012
+ const content = result.body?.storage?.value || "";
1013
1013
  // Store content with element IDs for structured editing
1014
- const { bufferId, structure } = storeXhtmlWithStructure(content, {
1015
- resourceType: "confluence_page",
1014
+ const xhtmlResult = formatSuccessXhtml(content, {
1015
+ resourceType: "confluence_draft",
1016
1016
  resourceId: result.id,
1017
- contentType: "xhtml",
1018
1017
  spaceKey: result.space?.key,
1019
1018
  title: result.title,
1020
1019
  isDraft: true,
1021
1020
  });
1021
+ if ("errorResult" in xhtmlResult) {
1022
+ return xhtmlResult.errorResult;
1023
+ }
1024
+ const { bufferId, structure } = xhtmlResult;
1022
1025
  // Build the draft edit URL
1023
1026
  const baseUrl = client.getBaseUrl();
1024
1027
  const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${result.id}`;
@@ -1041,14 +1044,15 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to modify, th
1041
1044
  },
1042
1045
  },
1043
1046
  confluence_draft_list: {
1044
- description: `List your draft pages. Use confluence_draft_open to load a draft for editing.`,
1047
+ description: `List your draft pages. Use confluence_draft_open to load a draft for editing.
1048
+
1049
+ Returns bufferId. Use buffer_get_chunk to read, buffer_grep to search.`,
1045
1050
  inputSchema: z.object({
1046
1051
  spaceKey: z.string().optional().describe("Filter by space key"),
1047
- limit: z.number().optional().describe("Max results (default: 25)"),
1048
1052
  }),
1049
1053
  handler: async (args) => {
1050
1054
  try {
1051
- const result = await client.listUserDrafts(args.spaceKey, args.limit);
1055
+ const result = await client.listUserDrafts(args.spaceKey);
1052
1056
  // Build the base URL for constructing full URLs
1053
1057
  const baseUrl = client.getBaseUrl();
1054
1058
  const drafts = result.results.map((page) => ({
@@ -1059,9 +1063,12 @@ Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to modify, th
1059
1063
  created: page.version?.when || "",
1060
1064
  url: `${baseUrl}/pages/resumedraft.action?draftId=${page.id}`,
1061
1065
  }));
1062
- return formatSuccess({
1066
+ return formatSuccessJson({
1063
1067
  drafts,
1064
1068
  total: result.totalSize,
1069
+ }, {
1070
+ resourceType: "confluence_drafts",
1071
+ title: args.spaceKey ? `Drafts in ${args.spaceKey}` : "All Drafts",
1065
1072
  });
1066
1073
  }
1067
1074
  catch (error) {
@@ -1102,7 +1109,7 @@ Returns new draftId, bufferId, structure (element IDs), and URL. Always use the
1102
1109
  });
1103
1110
  }
1104
1111
  // Get full content from buffer
1105
- const fullContent = contentBuffer.getChunk(args.bufferId, 0, bufferChunk.totalSize);
1112
+ const fullContent = contentBuffer.getChunk(args.bufferId, 0, bufferChunk.bufferSizeBytes);
1106
1113
  if (!fullContent) {
1107
1114
  return formatError({
1108
1115
  error: true,
@@ -1125,12 +1132,30 @@ Returns new draftId, bufferId, structure (element IDs), and URL. Always use the
1125
1132
  });
1126
1133
  }
1127
1134
  // Validate XHTML and PlantUML content before writing
1128
- const validationError = await validateContentForWrite(savedContent);
1135
+ const validationError = await validateContentForWrite(savedContent, args.bufferId);
1129
1136
  if (validationError) {
1130
1137
  return validationError;
1131
1138
  }
1132
- // Expand PlantUML !include directives before sending to Confluence
1133
- const expandedContent = await expandPlantUmlInXhtml(savedContent);
1139
+ // Convert PlantUML !include URLs to stdlib syntax before sending to Confluence
1140
+ let expandedContent;
1141
+ try {
1142
+ expandedContent = convertPlantUmlIncludesInXhtml(savedContent);
1143
+ }
1144
+ catch (error) {
1145
+ if (error instanceof IncludeConversionError) {
1146
+ return formatError({
1147
+ error: true,
1148
+ message: error.message,
1149
+ statusCode: 400,
1150
+ details: {
1151
+ url: error.url,
1152
+ suggestion: error.suggestion,
1153
+ hint: "Replace the HTTP URL with the suggested stdlib syntax in your PlantUML diagram",
1154
+ },
1155
+ });
1156
+ }
1157
+ throw error;
1158
+ }
1134
1159
  const title = args.title || existingDraft.title;
1135
1160
  // Delete old draft before creating new one
1136
1161
  // NOTE: Confluence Data Center does NOT support updating drafts directly.
@@ -1161,18 +1186,21 @@ Returns new draftId, bufferId, structure (element IDs), and URL. Always use the
1161
1186
  });
1162
1187
  // Update buffer metadata with new draft ID
1163
1188
  contentBuffer.invalidateByMetadata({
1164
- resourceType: "confluence_page",
1189
+ resourceType: "confluence_draft",
1165
1190
  resourceId: args.draftId,
1166
1191
  });
1167
1192
  // Store content with element IDs for structured editing
1168
- const { bufferId: newBufferId, structure } = storeXhtmlWithStructure(savedContent, {
1169
- resourceType: "confluence_page",
1193
+ const xhtmlResult = formatSuccessXhtml(savedContent, {
1194
+ resourceType: "confluence_draft",
1170
1195
  resourceId: newDraft.id,
1171
- contentType: "xhtml",
1172
1196
  spaceKey: newDraft.space?.key,
1173
1197
  title: newDraft.title,
1174
1198
  isDraft: true,
1175
1199
  });
1200
+ if ("errorResult" in xhtmlResult) {
1201
+ return xhtmlResult.errorResult;
1202
+ }
1203
+ const { bufferId: newBufferId, structure } = xhtmlResult;
1176
1204
  // Build the draft edit URL
1177
1205
  const baseUrl = client.getBaseUrl();
1178
1206
  const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${newDraft.id}`;
@@ -1233,14 +1261,24 @@ Drafts are NOT sent to trash - they are permanently deleted.`,
1233
1261
  confluence_review_publish: {
1234
1262
  description: `Publish a review draft to apply changes to the original page.
1235
1263
 
1264
+ Apply changes from "[jicon-mcp REVIEW]" draft to the original page.
1265
+
1266
+ REQUIRES: reviewDraftId (from confluence_draft_create with pageId)
1267
+ RETURNS: Updated page info with new version number
1268
+ NEXT: confluence_get_page (to verify), confluence_edit (for more changes)
1269
+
1236
1270
  This tool:
1237
1271
  1. Validates the draft is a "[jicon-mcp REVIEW]" draft with proper label
1238
1272
  2. Copies the draft content to the original page (creates new version)
1239
1273
  3. Deletes the review draft
1240
1274
 
1241
- Use this after user has reviewed the draft in Confluence UI.`,
1275
+ Example: confluence_review_publish(reviewDraftId="123456")`,
1242
1276
  inputSchema: z.object({
1243
1277
  reviewDraftId: z.coerce.string().describe("ID of the [jicon-mcp REVIEW] draft to publish"),
1278
+ autoRetry: z
1279
+ .boolean()
1280
+ .optional()
1281
+ .describe("Auto-retry once on 409 conflict (refetches page version)"),
1244
1282
  }),
1245
1283
  handler: async (args) => {
1246
1284
  const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
@@ -1297,9 +1335,23 @@ Use this after user has reviewed the draft in Confluence UI.`,
1297
1335
  // 5. Get the review draft content
1298
1336
  const reviewContent = reviewDraft.body?.storage?.value || "";
1299
1337
  // 6. Update original page with review content
1300
- const updatedPage = await client.updatePage(originalPageId, originalPage.version?.number || 1, originalPage.title, // Keep original title (not the REVIEW prefix)
1301
- reviewContent, false // Not a minor edit
1302
- );
1338
+ let updatedPage;
1339
+ try {
1340
+ updatedPage = await client.updatePage(originalPageId, originalPage.version?.number || 1, originalPage.title, // Keep original title (not the REVIEW prefix)
1341
+ reviewContent, false // Not a minor edit
1342
+ );
1343
+ }
1344
+ catch (updateError) {
1345
+ // Handle version conflicts with optional auto-retry
1346
+ if (isApiError(updateError) && updateError.statusCode === 409 && args.autoRetry) {
1347
+ // Re-fetch page to get current version and retry
1348
+ const refreshedPage = await client.getPage(originalPageId, ["version"]);
1349
+ updatedPage = await client.updatePage(originalPageId, refreshedPage.version?.number || 1, originalPage.title, reviewContent, false);
1350
+ }
1351
+ else {
1352
+ throw updateError;
1353
+ }
1354
+ }
1303
1355
  // 7. Delete review draft
1304
1356
  await client.deleteDraft(args.reviewDraftId);
1305
1357
  // 8. Invalidate buffers