@crypto512/jicon-mcp 0.7.1 → 1.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 (129) hide show
  1. package/README.md +128 -395
  2. package/TOOL_LIST.md +810 -120
  3. package/dist/config/constants.d.ts +1 -0
  4. package/dist/config/constants.d.ts.map +1 -1
  5. package/dist/config/constants.js +1 -0
  6. package/dist/config/constants.js.map +1 -1
  7. package/dist/config/loader.d.ts +1 -0
  8. package/dist/config/loader.d.ts.map +1 -1
  9. package/dist/config/loader.js +27 -1
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/types.d.ts +8 -0
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/config/types.js +2 -0
  14. package/dist/config/types.js.map +1 -1
  15. package/dist/confluence/client.d.ts +38 -0
  16. package/dist/confluence/client.d.ts.map +1 -1
  17. package/dist/confluence/client.js +117 -0
  18. package/dist/confluence/client.js.map +1 -1
  19. package/dist/confluence/tools.d.ts +102 -75
  20. package/dist/confluence/tools.d.ts.map +1 -1
  21. package/dist/confluence/tools.js +510 -151
  22. package/dist/confluence/tools.js.map +1 -1
  23. package/dist/confluence/types.d.ts +55 -1
  24. package/dist/confluence/types.d.ts.map +1 -1
  25. package/dist/index.js +88 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/jira/tools.d.ts +0 -5
  28. package/dist/jira/tools.d.ts.map +1 -1
  29. package/dist/jira/tools.js +40 -87
  30. package/dist/jira/tools.js.map +1 -1
  31. package/dist/permissions/filter.d.ts +5 -0
  32. package/dist/permissions/filter.d.ts.map +1 -1
  33. package/dist/permissions/filter.js +29 -12
  34. package/dist/permissions/filter.js.map +1 -1
  35. package/dist/permissions/tool-registry.d.ts +23 -25
  36. package/dist/permissions/tool-registry.d.ts.map +1 -1
  37. package/dist/permissions/tool-registry.js +41 -45
  38. package/dist/permissions/tool-registry.js.map +1 -1
  39. package/dist/permissions/write-home-validator.d.ts +35 -0
  40. package/dist/permissions/write-home-validator.d.ts.map +1 -0
  41. package/dist/permissions/write-home-validator.js +140 -0
  42. package/dist/permissions/write-home-validator.js.map +1 -0
  43. package/dist/tempo/tools.d.ts.map +1 -1
  44. package/dist/tempo/tools.js +43 -44
  45. package/dist/tempo/tools.js.map +1 -1
  46. package/dist/utils/buffer-tools.d.ts +119 -1
  47. package/dist/utils/buffer-tools.d.ts.map +1 -1
  48. package/dist/utils/buffer-tools.js +610 -3
  49. package/dist/utils/buffer-tools.js.map +1 -1
  50. package/dist/utils/content-buffer.d.ts +34 -0
  51. package/dist/utils/content-buffer.d.ts.map +1 -1
  52. package/dist/utils/content-buffer.js +79 -0
  53. package/dist/utils/content-buffer.js.map +1 -1
  54. package/dist/utils/http-client.d.ts.map +1 -1
  55. package/dist/utils/http-client.js +4 -4
  56. package/dist/utils/http-client.js.map +1 -1
  57. package/dist/utils/jicon-help.d.ts +29 -0
  58. package/dist/utils/jicon-help.d.ts.map +1 -0
  59. package/dist/utils/jicon-help.js +873 -0
  60. package/dist/utils/jicon-help.js.map +1 -0
  61. package/dist/utils/plantuml/client.d.ts +40 -0
  62. package/dist/utils/plantuml/client.d.ts.map +1 -0
  63. package/dist/utils/plantuml/client.js +306 -0
  64. package/dist/utils/plantuml/client.js.map +1 -0
  65. package/dist/utils/plantuml/docker-manager.d.ts +35 -0
  66. package/dist/utils/plantuml/docker-manager.d.ts.map +1 -0
  67. package/dist/utils/plantuml/docker-manager.js +280 -0
  68. package/dist/utils/plantuml/docker-manager.js.map +1 -0
  69. package/dist/utils/plantuml/index.d.ts +11 -0
  70. package/dist/utils/plantuml/index.d.ts.map +1 -0
  71. package/dist/utils/plantuml/index.js +16 -0
  72. package/dist/utils/plantuml/index.js.map +1 -0
  73. package/dist/utils/plantuml/service.d.ts +46 -0
  74. package/dist/utils/plantuml/service.d.ts.map +1 -0
  75. package/dist/utils/plantuml/service.js +96 -0
  76. package/dist/utils/plantuml/service.js.map +1 -0
  77. package/dist/utils/plantuml/tools.d.ts +65 -0
  78. package/dist/utils/plantuml/tools.d.ts.map +1 -0
  79. package/dist/utils/plantuml/tools.js +272 -0
  80. package/dist/utils/plantuml/tools.js.map +1 -0
  81. package/dist/utils/plantuml/types.d.ts +130 -0
  82. package/dist/utils/plantuml/types.d.ts.map +1 -0
  83. package/dist/utils/plantuml/types.js +66 -0
  84. package/dist/utils/plantuml/types.js.map +1 -0
  85. package/dist/utils/response-formatter.d.ts +14 -0
  86. package/dist/utils/response-formatter.d.ts.map +1 -1
  87. package/dist/utils/response-formatter.js +84 -1
  88. package/dist/utils/response-formatter.js.map +1 -1
  89. package/dist/utils/url-tools.d.ts +49 -0
  90. package/dist/utils/url-tools.d.ts.map +1 -0
  91. package/dist/utils/url-tools.js +141 -0
  92. package/dist/utils/url-tools.js.map +1 -0
  93. package/dist/utils/xhtml/confluence-schema.d.ts +55 -0
  94. package/dist/utils/xhtml/confluence-schema.d.ts.map +1 -0
  95. package/dist/utils/xhtml/confluence-schema.js +215 -0
  96. package/dist/utils/xhtml/confluence-schema.js.map +1 -0
  97. package/dist/utils/xhtml/index.d.ts +17 -0
  98. package/dist/utils/xhtml/index.d.ts.map +1 -0
  99. package/dist/utils/xhtml/index.js +21 -0
  100. package/dist/utils/xhtml/index.js.map +1 -0
  101. package/dist/utils/xhtml/operations.d.ts +100 -0
  102. package/dist/utils/xhtml/operations.d.ts.map +1 -0
  103. package/dist/utils/xhtml/operations.js +596 -0
  104. package/dist/utils/xhtml/operations.js.map +1 -0
  105. package/dist/utils/xhtml/parser.d.ts +64 -0
  106. package/dist/utils/xhtml/parser.d.ts.map +1 -0
  107. package/dist/utils/xhtml/parser.js +180 -0
  108. package/dist/utils/xhtml/parser.js.map +1 -0
  109. package/dist/utils/xhtml/plantuml.d.ts +112 -0
  110. package/dist/utils/xhtml/plantuml.d.ts.map +1 -0
  111. package/dist/utils/xhtml/plantuml.js +251 -0
  112. package/dist/utils/xhtml/plantuml.js.map +1 -0
  113. package/dist/utils/xhtml/selector.d.ts +35 -0
  114. package/dist/utils/xhtml/selector.d.ts.map +1 -0
  115. package/dist/utils/xhtml/selector.js +358 -0
  116. package/dist/utils/xhtml/selector.js.map +1 -0
  117. package/dist/utils/xhtml/serializer.d.ts +26 -0
  118. package/dist/utils/xhtml/serializer.d.ts.map +1 -0
  119. package/dist/utils/xhtml/serializer.js +170 -0
  120. package/dist/utils/xhtml/serializer.js.map +1 -0
  121. package/dist/utils/xhtml/types.d.ts +134 -0
  122. package/dist/utils/xhtml/types.d.ts.map +1 -0
  123. package/dist/utils/xhtml/types.js +65 -0
  124. package/dist/utils/xhtml/types.js.map +1 -0
  125. package/dist/utils/xhtml/validator.d.ts +67 -0
  126. package/dist/utils/xhtml/validator.d.ts.map +1 -0
  127. package/dist/utils/xhtml/validator.js +300 -0
  128. package/dist/utils/xhtml/validator.js.map +1 -0
  129. package/package.json +5 -1
@@ -3,110 +3,155 @@
3
3
  */
4
4
  import { z } from "zod";
5
5
  import { formatSuccess, formatError, isApiError } from "../utils/response-formatter.js";
6
- import { formatPageMetadata, formatSearchResults, formatSpace, } from "./formatters.js";
6
+ import { contentBuffer } from "../utils/content-buffer.js";
7
+ import { formatPageMetadata } from "./formatters.js";
8
+ import { validateXhtmlAsync } from "../utils/xhtml/index.js";
9
+ import { detectRawPlantUml } from "../utils/xhtml/plantuml.js";
10
+ /**
11
+ * Resolve content from either direct content string or bufferId.
12
+ * Returns { content, error } - if error is set, return it as the tool result.
13
+ */
14
+ function resolveContentFromBuffer(contentArg, bufferIdArg) {
15
+ // Exactly one of content or bufferId must be provided
16
+ if (contentArg && bufferIdArg) {
17
+ return {
18
+ error: formatError({
19
+ error: true,
20
+ message: "Provide either 'content' or 'bufferId', not both",
21
+ statusCode: 400,
22
+ }),
23
+ };
24
+ }
25
+ if (bufferIdArg) {
26
+ const bufferChunk = contentBuffer.getChunk(bufferIdArg);
27
+ if (!bufferChunk) {
28
+ return {
29
+ error: formatError({
30
+ error: true,
31
+ message: `Buffer ${bufferIdArg} not found or expired`,
32
+ statusCode: 404,
33
+ }),
34
+ };
35
+ }
36
+ // Validate that buffer contains XHTML content for Confluence
37
+ const bufferInfo = contentBuffer.getInfo(bufferIdArg);
38
+ if (bufferInfo?.metadata?.contentType && bufferInfo.metadata.contentType !== "xhtml") {
39
+ return {
40
+ error: formatError({
41
+ error: true,
42
+ message: `Buffer ${bufferIdArg} contains ${bufferInfo.metadata.contentType} content, but Confluence requires XHTML.`,
43
+ statusCode: 400,
44
+ details: {
45
+ hint: "Create Confluence content with buffer_create, then edit with buffer_edit_xhtml.",
46
+ foundContentType: bufferInfo.metadata.contentType,
47
+ expectedContentType: "xhtml",
48
+ },
49
+ }),
50
+ };
51
+ }
52
+ // Get full content from buffer
53
+ const fullContent = contentBuffer.getChunk(bufferIdArg, 0, bufferChunk.totalSize);
54
+ if (!fullContent) {
55
+ return {
56
+ error: formatError({
57
+ error: true,
58
+ message: "Failed to retrieve full buffer content",
59
+ statusCode: 500,
60
+ }),
61
+ };
62
+ }
63
+ return { content: fullContent.chunk };
64
+ }
65
+ // Direct content provided
66
+ return { content: contentArg };
67
+ }
68
+ /**
69
+ * Validate XHTML content before Confluence write operations.
70
+ * Returns error result if validation fails, null if valid.
71
+ */
72
+ async function validateContentForWrite(content) {
73
+ const validation = await validateXhtmlAsync(content, { validatePlantUml: true });
74
+ if (!validation.valid) {
75
+ const errorMessages = [];
76
+ // XHTML structure errors
77
+ if (validation.errors && validation.errors.length > 0) {
78
+ errorMessages.push("XHTML validation errors:");
79
+ validation.errors.forEach((err) => {
80
+ const location = err.location?.line ? ` (line ${err.location.line}${err.location?.column ? `:${err.location.column}` : ""})` : "";
81
+ errorMessages.push(` - ${err.message}${location}`);
82
+ // Include context snippet if available
83
+ if (err.location?.context) {
84
+ const contextPreview = err.location.context.length > 60
85
+ ? err.location.context.substring(0, 60) + "..."
86
+ : err.location.context;
87
+ errorMessages.push(` Near: "${contextPreview}"`);
88
+ }
89
+ });
90
+ }
91
+ // PlantUML validation errors
92
+ if (validation.plantuml && validation.plantuml.length > 0) {
93
+ const invalidPlantUml = validation.plantuml.filter(p => !p.valid);
94
+ if (invalidPlantUml.length > 0) {
95
+ errorMessages.push("PlantUML validation errors:");
96
+ invalidPlantUml.forEach((p) => {
97
+ errorMessages.push(` Macro #${p.macroIndex + 1}:`);
98
+ p.errors?.forEach((err) => {
99
+ const location = err.line ? ` (line ${err.line}${err.column ? `:${err.column}` : ""})` : "";
100
+ errorMessages.push(` - ${err.message}${location}`);
101
+ });
102
+ });
103
+ // Add PlantUML format example
104
+ errorMessages.push("");
105
+ errorMessages.push("Correct PlantUML macro format:");
106
+ errorMessages.push(' <ac:structured-macro ac:name="plantuml" ac:schema-version="1">');
107
+ errorMessages.push(" <ac:plain-text-body><![CDATA[");
108
+ errorMessages.push(" @startuml");
109
+ errorMessages.push(" (your diagram code)");
110
+ errorMessages.push(" @enduml");
111
+ errorMessages.push(" ]]></ac:plain-text-body>");
112
+ errorMessages.push(" </ac:structured-macro>");
113
+ }
114
+ }
115
+ // Add help hints
116
+ errorMessages.push("");
117
+ errorMessages.push('TIP: Call help(topic="storage") for Confluence XHTML format guide.');
118
+ errorMessages.push('TIP: Call help(topic="plantuml") for PlantUML macro examples.');
119
+ errorMessages.push("");
120
+ errorMessages.push("ACTION REQUIRED: Fix content errors before calling this tool again.");
121
+ errorMessages.push("DO NOT claim success - the draft was NOT created.");
122
+ return formatError({
123
+ error: true,
124
+ message: "Content validation failed. Fix errors before writing to Confluence.",
125
+ statusCode: 400,
126
+ details: {
127
+ validationErrors: errorMessages,
128
+ hint: "Use buffer_validate_xhtml to check content, or plantuml_validate for PlantUML syntax",
129
+ },
130
+ });
131
+ }
132
+ return null; // Valid content
133
+ }
7
134
  export function createConfluenceTools(client) {
8
135
  return {
9
- confluence_cql_help: {
10
- description: "Get CQL (Confluence Query Language) syntax guide and examples.",
11
- inputSchema: z.object({}),
12
- handler: async () => {
13
- return {
14
- content: [
15
- {
16
- type: "text",
17
- text: `# CQL Syntax Guide
18
-
19
- ## Operators
20
- - Text search: text~"term" (searches both title and body)
21
- - Title only: title~"term"
22
- - By space: space=KEY (use space KEY, not name!)
23
- - By type: type=page or type=blogpost
24
- - Combine: AND, OR, NOT
25
- - Order: ORDER BY created/lastmodified ASC/DESC
26
-
27
- ## Critical Warnings
28
- ⚠️ Do NOT use 'content~' or 'body~' - they're invalid! Use 'text~' instead.
29
-
30
- ⚠️ WRONG: text~("term1" OR "term2") - Cannot use OR inside the operator
31
- ✅ RIGHT: (text~"term1" OR text~"term2") - Each term needs its own text~ operator
32
-
33
- ## Common Examples
34
- 1. Recently updated pages (last 7 days):
35
- type=page AND lastmodified >= now("-7d")
36
-
37
- 2. Pages created by a person:
38
- type=page AND creator="username"
39
-
40
- 3. Pages with specific label:
41
- type=page AND label="meeting-notes"
42
-
43
- 4. Pages in space modified recently:
44
- space=DOCS AND lastmodified >= now("-30d") ORDER BY lastmodified DESC
45
-
46
- 5. Text search in a space:
47
- text~"API" AND space=DOCS AND type=page
48
-
49
- 6. Date range:
50
- lastmodified >= "2025-01-01" AND lastmodified <= "2025-01-31"
51
-
52
- 7. Multiple spaces:
53
- space IN (DOCS, PROJ, WIKI)
54
-
55
- 8. Pages by title pattern:
56
- type=page AND title~"Sprint Review" ORDER BY created DESC
57
-
58
- 9. Find by creator name:
59
- creator.fullname~"John Smith"
60
-
61
- 10. Pages created this week:
62
- type=page AND created >= startOfWeek()
63
-
64
- ## Tips
65
- - Use space KEY (e.g., 'DOCS') not space name (e.g., 'Documentation')
66
- - Date functions: now(), startOfDay(), startOfWeek(), startOfMonth()
67
- - Relative dates: now("-7d") = 7 days ago
68
- - Use fetchAll=true to retrieve all matching results (up to 5000)`,
69
- },
70
- ],
71
- };
72
- },
73
- },
74
136
  confluence_search_content: {
75
- description: `Search Confluence content using CQL. Set fetchAll=true for all results (up to 5000).
137
+ description: `Search Confluence content using CQL. Auto-fetches all results (up to 5000).
138
+
139
+ BEFORE USING: Call help(topic="cql") for CQL syntax guide.
76
140
 
77
141
  Example: type=page AND space=DOCS AND text~"API" ORDER BY lastmodified DESC
78
142
 
79
- ⚠️ Use text~ (not content~ or body~). Use space KEY (not name).
80
- For full CQL syntax guide, use confluence_cql_help.
143
+ WARNING: Use text~ (not content~ or body~). Use space KEY (not name).
81
144
 
82
145
  Large responses are buffered - use buffer_get_chunk or buffer_grep to access.`,
83
146
  inputSchema: z.object({
84
147
  cql: z.string().describe("CQL query string"),
85
- limit: z.number().optional().describe("Maximum number of results per page (default: 25). When fetchAll=true, this is the maximum total results (default: 5000)."),
86
148
  expand: z.array(z.string()).optional().describe("Additional data to expand"),
87
- start: z.number().optional().describe("Index of first result (0-based, default: 0). Ignored when fetchAll=true."),
88
- fetchAll: z.boolean().optional().describe("Automatically fetch all results across multiple pages (default: false, max: 5000)"),
89
149
  }),
90
150
  handler: async (args) => {
91
151
  try {
92
- let result;
93
- if (args.fetchAll) {
94
- // Auto-fetch all results
95
- const maxTotal = args.limit || 5000;
96
- result = await client.searchContentAll(args.cql, args.expand, maxTotal);
97
- }
98
- else {
99
- // Single page
100
- result = await client.searchContent(args.cql, args.limit, args.expand, args.start);
101
- }
102
- return {
103
- content: [
104
- {
105
- type: "text",
106
- text: formatSearchResults(result),
107
- },
108
- ],
109
- };
152
+ const result = await client.searchContentAll(args.cql, args.expand);
153
+ // Use formatSuccess for automatic buffering of large responses
154
+ return formatSuccess(result);
110
155
  }
111
156
  catch (error) {
112
157
  // Enhanced error handling for common CQL errors
@@ -163,8 +208,8 @@ Large responses are buffered - use buffer_get_chunk or buffer_grep to access.`,
163
208
 
164
209
  PREFERRED method when you have a page ID from search results. Faster and more reliable than get_page_by_title.
165
210
 
166
- Returns full page data including content. Large responses are automatically buffered - use buffer_get_chunk or buffer_grep to access.
167
- For complex analysis, spawn a sub-agent with the bufferId.`,
211
+ Returns pageId, version, and bufferId containing the page content.
212
+ Use bufferId with buffer_edit/buffer_edit_xhtml to modify, then confluence_draft_create for user review.`,
168
213
  inputSchema: z.object({
169
214
  pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
170
215
  expand: z
@@ -175,10 +220,23 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
175
220
  handler: async (args) => {
176
221
  try {
177
222
  const result = await client.getPage(args.pageId, args.expand);
223
+ const content = result.body?.storage?.value || "";
224
+ // Always store content in buffer for consistent editing workflow
225
+ const bufferId = contentBuffer.store(content, {
226
+ resourceType: "confluence_page",
227
+ resourceId: String(result.id),
228
+ contentType: "xhtml",
229
+ version: result.version?.number,
230
+ spaceKey: result.space?.key,
231
+ title: result.title,
232
+ });
178
233
  return formatSuccess({
179
234
  ...formatPageMetadata(result),
180
- content: result.body?.storage?.value || "",
181
- data: result,
235
+ pageId: result.id,
236
+ version: result.version?.number,
237
+ bufferId,
238
+ contentSize: content.length,
239
+ message: "Page loaded. Use buffer_edit_xhtml to modify, then confluence_draft_create for user review.",
182
240
  });
183
241
  }
184
242
  catch (error) {
@@ -191,8 +249,8 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
191
249
 
192
250
  Use ONLY when you don't have a page ID. IMPORTANT: Use the space KEY (e.g. 'MESH', 'TC'), NOT the space name.
193
251
 
194
- Returns full page data including content. Large responses are automatically buffered - use buffer_get_chunk or buffer_grep to access.
195
- For complex analysis, spawn a sub-agent with the bufferId.`,
252
+ Returns pageId, version, and bufferId containing the page content.
253
+ Use bufferId with buffer_edit/buffer_edit_xhtml to modify, then confluence_draft_create for user review.`,
196
254
  inputSchema: z.object({
197
255
  spaceKey: z.string().describe("Space key (short code like 'MESH', 'TC'), NOT the full space name"),
198
256
  title: z.string().describe("Page title"),
@@ -208,10 +266,23 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
208
266
  statusCode: 404,
209
267
  });
210
268
  }
269
+ const content = result.body?.storage?.value || "";
270
+ // Always store content in buffer for consistent editing workflow
271
+ const bufferId = contentBuffer.store(content, {
272
+ resourceType: "confluence_page",
273
+ resourceId: String(result.id),
274
+ contentType: "xhtml",
275
+ version: result.version?.number,
276
+ spaceKey: result.space?.key,
277
+ title: result.title,
278
+ });
211
279
  return formatSuccess({
212
280
  ...formatPageMetadata(result),
213
- content: result.body?.storage?.value || "",
214
- data: result,
281
+ pageId: result.id,
282
+ version: result.version?.number,
283
+ bufferId,
284
+ contentSize: content.length,
285
+ message: "Page loaded. Use buffer_edit_xhtml to modify, then confluence_draft_create for user review.",
215
286
  });
216
287
  }
217
288
  catch (error) {
@@ -219,51 +290,9 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
219
290
  }
220
291
  },
221
292
  },
222
- confluence_create_page: {
223
- description: "Create a new Confluence page",
224
- inputSchema: z.object({
225
- spaceKey: z.string().describe("Space key"),
226
- title: z.string().describe("Page title"),
227
- content: z
228
- .string()
229
- .describe("Page content (Confluence storage format or HTML)"),
230
- parentId: z.string().optional().describe("Parent page ID"),
231
- labels: z.array(z.string()).optional().describe("Array of labels"),
232
- }),
233
- handler: async (args) => {
234
- try {
235
- const result = await client.createPage(args);
236
- return formatSuccess(result);
237
- }
238
- catch (error) {
239
- return formatError(isApiError(error) ? error : new Error(String(error)));
240
- }
241
- },
242
- },
243
- confluence_update_page: {
244
- description: "Update an existing Confluence page",
245
- inputSchema: z.object({
246
- pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
247
- title: z.string().optional().describe("New title"),
248
- content: z.string().optional().describe("New content"),
249
- version: z
250
- .number()
251
- .describe("Current version number (for optimistic locking)"),
252
- minorEdit: z
253
- .boolean()
254
- .optional()
255
- .describe("Whether this is a minor edit"),
256
- }),
257
- handler: async (args) => {
258
- try {
259
- const result = await client.updatePage(args.pageId, args.version, args.title, args.content, args.minorEdit);
260
- return formatSuccess(result);
261
- }
262
- catch (error) {
263
- return formatError(isApiError(error) ? error : new Error(String(error)));
264
- }
265
- },
266
- },
293
+ // All page writes go through draft workflow: LLM creates/edits drafts, user publishes via Confluence UI
294
+ // - New pages: confluence_draft_create user publishes
295
+ // - Existing pages: confluence_get_page → buffer_edit → confluence_draft_create → user publishes
267
296
  confluence_delete_page: {
268
297
  description: "Delete a Confluence page",
269
298
  inputSchema: z.object({
@@ -272,6 +301,11 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
272
301
  handler: async (args) => {
273
302
  try {
274
303
  await client.deletePage(args.pageId);
304
+ // Invalidate any cached buffers for this page
305
+ contentBuffer.invalidateByMetadata({
306
+ resourceType: "confluence_page",
307
+ resourceId: args.pageId,
308
+ });
275
309
  return formatSuccess({
276
310
  success: true,
277
311
  message: `Page ${args.pageId} deleted successfully`,
@@ -312,14 +346,8 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
312
346
  handler: async (args) => {
313
347
  try {
314
348
  const result = await client.getSpace(args.spaceKey, args.expand);
315
- return {
316
- content: [
317
- {
318
- type: "text",
319
- text: formatSpace(result),
320
- },
321
- ],
322
- };
349
+ // Use formatSuccess for automatic buffering of large responses
350
+ return formatSuccess(result);
323
351
  }
324
352
  catch (error) {
325
353
  return formatError(isApiError(error) ? error : new Error(String(error)));
@@ -414,6 +442,337 @@ For complex analysis, spawn a sub-agent with the bufferId.`,
414
442
  }
415
443
  },
416
444
  },
445
+ confluence_get_current_user_space: {
446
+ description: `Get the current user's personal/home Confluence space.
447
+
448
+ Use this tool when the user asks about:
449
+ - "my space", "my home space", "my personal space"
450
+ - "confluence home", "home space"
451
+ - "where can I write", "my confluence area"
452
+
453
+ 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.`,
454
+ inputSchema: z.object({}),
455
+ handler: async () => {
456
+ try {
457
+ const userSpace = await client.getCurrentUserSpace();
458
+ if (!userSpace) {
459
+ return formatError({
460
+ error: true,
461
+ message: "No personal space found for current user",
462
+ statusCode: 404,
463
+ });
464
+ }
465
+ return formatSuccess({
466
+ spaceKey: userSpace.key,
467
+ spaceName: userSpace.name,
468
+ spaceType: userSpace.type,
469
+ homePageId: userSpace.homepage?.id,
470
+ homePageTitle: userSpace.homepage?.title,
471
+ tip: "Use this spaceKey when creating pages with write-home restriction enabled",
472
+ });
473
+ }
474
+ catch (error) {
475
+ return formatError(isApiError(error) ? error : new Error(String(error)));
476
+ }
477
+ },
478
+ },
479
+ // ========================================
480
+ // Draft Tools
481
+ // ========================================
482
+ // Note: Confluence drafts cannot be updated via API, only created, read, or deleted.
483
+ // The workflow is: create draft → edit locally in buffer → save (delete+recreate) → publish
484
+ confluence_draft_create: {
485
+ description: `Create a Confluence draft for user review. Returns draftId, bufferId, and clickable URL.
486
+
487
+ IMPORTANT: User must validate the draft in Confluence UI before publishing.
488
+ IMPORTANT: Raw @startuml blocks are NOT supported. Use buffer_edit_xhtml with insert-plantuml operation.
489
+
490
+ BEFORE USING: Call help(topic="storage") for Confluence storage format syntax.
491
+ BEFORE USING: Call help(topic="plantuml") for diagram workflow.
492
+
493
+ Workflow:
494
+ 1. Create draft with this tool → returns URL for user review
495
+ 2. User reviews draft in Confluence UI (can edit there)
496
+ 3. If user requests changes: use confluence_draft_open to get user's edits
497
+ 4. Modify with buffer_edit, then confluence_draft_save for new review
498
+ 5. User publishes manually via Confluence UI
499
+
500
+ Content must be in Confluence storage format (XHTML-based).
501
+ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit).`,
502
+ inputSchema: z.object({
503
+ spaceKey: z.string().describe("Space key"),
504
+ title: z.string().describe("Page title"),
505
+ content: z
506
+ .string()
507
+ .optional()
508
+ .describe("Page content in Confluence storage format (XHTML-based)"),
509
+ bufferId: z.string().optional().describe("Buffer ID containing content (alternative to content)"),
510
+ parentId: z.string().optional().describe("Parent page ID"),
511
+ labels: z.array(z.string()).optional().describe("Array of labels"),
512
+ }),
513
+ handler: async (args) => {
514
+ // Resolve content from either content string or bufferId
515
+ const resolved = resolveContentFromBuffer(args.content, args.bufferId);
516
+ if (resolved.error) {
517
+ return resolved.error;
518
+ }
519
+ if (!resolved.content) {
520
+ return formatError({
521
+ error: true,
522
+ message: "Either 'content' or 'bufferId' must be provided",
523
+ statusCode: 400,
524
+ });
525
+ }
526
+ const content = resolved.content;
527
+ // Check for raw PlantUML that should use buffer_edit_xhtml instead
528
+ const rawPlantUml = detectRawPlantUml(content);
529
+ if (rawPlantUml) {
530
+ return formatError({
531
+ error: true,
532
+ message: "Content contains raw PlantUML code that is not wrapped in a Confluence macro.",
533
+ statusCode: 400,
534
+ details: {
535
+ hint: rawPlantUml.hint,
536
+ example: "buffer_edit_xhtml(bufferId, operation='insert-plantuml', code='@startuml...@enduml', semanticPosition='after-heading:Title')",
537
+ },
538
+ });
539
+ }
540
+ // Validate XHTML and PlantUML content before writing
541
+ const validationError = await validateContentForWrite(content);
542
+ if (validationError) {
543
+ return validationError;
544
+ }
545
+ try {
546
+ const result = await client.createDraft({
547
+ spaceKey: args.spaceKey,
548
+ title: args.title,
549
+ content,
550
+ parentId: args.parentId,
551
+ labels: args.labels,
552
+ });
553
+ // Store content in buffer for local editing
554
+ const newBufferId = contentBuffer.store(content, {
555
+ resourceType: "confluence_page",
556
+ resourceId: result.id,
557
+ contentType: "xhtml",
558
+ spaceKey: result.space?.key,
559
+ title: result.title,
560
+ isDraft: true,
561
+ });
562
+ // Build the draft edit URL (drafts use resumedraft.action, not the webui link)
563
+ const baseUrl = client.getBaseUrl();
564
+ const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${result.id}`;
565
+ return formatSuccess({
566
+ draftId: result.id,
567
+ bufferId: newBufferId,
568
+ title: result.title,
569
+ spaceKey: result.space?.key,
570
+ version: result.version?.number || 1,
571
+ url: draftUrl,
572
+ message: "Draft created. User should click URL to review in Confluence and publish manually.",
573
+ });
574
+ }
575
+ catch (error) {
576
+ return formatError(isApiError(error) ? error : new Error(String(error)));
577
+ }
578
+ },
579
+ },
580
+ confluence_draft_open: {
581
+ description: `Open an existing draft page for editing. Loads content into a local buffer.
582
+
583
+ Use buffer_edit to modify content, then confluence_draft_save. User publishes via Confluence UI.`,
584
+ inputSchema: z.object({
585
+ draftId: z.coerce.string().describe("Draft page ID"),
586
+ }),
587
+ handler: async (args) => {
588
+ try {
589
+ const result = await client.getDraft(args.draftId);
590
+ const content = result.body?.storage?.value || "";
591
+ // Store content in buffer for local editing
592
+ const bufferId = contentBuffer.store(content, {
593
+ resourceType: "confluence_page",
594
+ resourceId: result.id,
595
+ contentType: "xhtml",
596
+ spaceKey: result.space?.key,
597
+ title: result.title,
598
+ isDraft: true,
599
+ });
600
+ // Build the draft edit URL
601
+ const baseUrl = client.getBaseUrl();
602
+ const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${result.id}`;
603
+ // Content preview (first 500 chars)
604
+ const contentPreview = content.length > 500 ? content.substring(0, 500) + "..." : content;
605
+ return formatSuccess({
606
+ draftId: result.id,
607
+ bufferId,
608
+ title: result.title,
609
+ spaceKey: result.space?.key,
610
+ version: result.version?.number,
611
+ url: draftUrl,
612
+ contentPreview,
613
+ });
614
+ }
615
+ catch (error) {
616
+ return formatError(isApiError(error) ? error : new Error(String(error)));
617
+ }
618
+ },
619
+ },
620
+ confluence_draft_list: {
621
+ description: `List your draft pages. Use confluence_draft_open to load a draft for editing.`,
622
+ inputSchema: z.object({
623
+ spaceKey: z.string().optional().describe("Filter by space key"),
624
+ limit: z.number().optional().describe("Max results (default: 25)"),
625
+ }),
626
+ handler: async (args) => {
627
+ try {
628
+ const result = await client.listUserDrafts(args.spaceKey, args.limit);
629
+ // Build the base URL for constructing full URLs
630
+ const baseUrl = client.getBaseUrl();
631
+ const drafts = result.results.map((page) => ({
632
+ draftId: page.id,
633
+ title: page.title,
634
+ spaceKey: page.space?.key || "",
635
+ spaceName: page.space?.name || "",
636
+ created: page.version?.when || "",
637
+ url: `${baseUrl}/pages/resumedraft.action?draftId=${page.id}`,
638
+ }));
639
+ return formatSuccess({
640
+ drafts,
641
+ total: result.totalSize,
642
+ });
643
+ }
644
+ catch (error) {
645
+ return formatError(isApiError(error) ? error : new Error(String(error)));
646
+ }
647
+ },
648
+ },
649
+ confluence_draft_save: {
650
+ description: `Save buffer content to server as a new draft for user review.
651
+
652
+ IMPORTANT: Raw @startuml blocks are NOT supported. Use buffer_edit_xhtml with insert-plantuml operation.
653
+
654
+ Returns a new draftId and URL. Always use the returned draftId for subsequent operations.`,
655
+ inputSchema: z.object({
656
+ draftId: z.coerce.string().describe("Current draft ID"),
657
+ bufferId: z.string().describe("Buffer ID containing updated content"),
658
+ title: z.string().optional().describe("Update title (optional)"),
659
+ }),
660
+ handler: async (args) => {
661
+ try {
662
+ // Get the existing draft to preserve metadata
663
+ const existingDraft = await client.getDraft(args.draftId);
664
+ const spaceKey = existingDraft.space?.key;
665
+ if (!spaceKey) {
666
+ return formatError({
667
+ error: true,
668
+ message: "Could not determine space key from existing draft",
669
+ statusCode: 400,
670
+ });
671
+ }
672
+ // Get content from buffer
673
+ const bufferChunk = contentBuffer.getChunk(args.bufferId);
674
+ if (!bufferChunk) {
675
+ return formatError({
676
+ error: true,
677
+ message: `Buffer ${args.bufferId} not found or expired`,
678
+ statusCode: 404,
679
+ });
680
+ }
681
+ // Get full content from buffer
682
+ const fullContent = contentBuffer.getChunk(args.bufferId, 0, bufferChunk.totalSize);
683
+ if (!fullContent) {
684
+ return formatError({
685
+ error: true,
686
+ message: "Failed to retrieve full buffer content",
687
+ statusCode: 500,
688
+ });
689
+ }
690
+ const savedContent = fullContent.chunk;
691
+ // Check for raw PlantUML that should use buffer_edit_xhtml instead
692
+ const rawPlantUml = detectRawPlantUml(savedContent);
693
+ if (rawPlantUml) {
694
+ return formatError({
695
+ error: true,
696
+ message: "Content contains raw PlantUML code that is not wrapped in a Confluence macro.",
697
+ statusCode: 400,
698
+ details: {
699
+ hint: rawPlantUml.hint,
700
+ example: "buffer_edit_xhtml(bufferId, operation='insert-plantuml', code='@startuml...@enduml', semanticPosition='after-heading:Title')",
701
+ },
702
+ });
703
+ }
704
+ // Validate XHTML and PlantUML content before writing
705
+ const validationError = await validateContentForWrite(savedContent);
706
+ if (validationError) {
707
+ return validationError;
708
+ }
709
+ const title = args.title || existingDraft.title;
710
+ // Delete old draft
711
+ await client.deleteDraft(args.draftId);
712
+ // Create new draft with updated content
713
+ const newDraft = await client.createDraft({
714
+ spaceKey,
715
+ title,
716
+ content: savedContent,
717
+ parentId: existingDraft.ancestors?.[0]?.id,
718
+ });
719
+ // Update buffer metadata with new draft ID
720
+ contentBuffer.invalidateByMetadata({
721
+ resourceType: "confluence_page",
722
+ resourceId: args.draftId,
723
+ });
724
+ // Store updated content with new draft ID
725
+ const newBufferId = contentBuffer.store(savedContent, {
726
+ resourceType: "confluence_page",
727
+ resourceId: newDraft.id,
728
+ contentType: "xhtml",
729
+ spaceKey: newDraft.space?.key,
730
+ title: newDraft.title,
731
+ isDraft: true,
732
+ });
733
+ // Build the draft edit URL
734
+ const baseUrl = client.getBaseUrl();
735
+ const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${newDraft.id}`;
736
+ return formatSuccess({
737
+ previousDraftId: args.draftId,
738
+ newDraftId: newDraft.id,
739
+ bufferId: newBufferId,
740
+ title: newDraft.title,
741
+ url: draftUrl,
742
+ message: "Draft saved. User should click URL to review changes and publish manually via Confluence UI.",
743
+ });
744
+ }
745
+ catch (error) {
746
+ return formatError(isApiError(error) ? error : new Error(String(error)));
747
+ }
748
+ },
749
+ },
750
+ confluence_draft_delete: {
751
+ description: `Delete a draft page permanently.
752
+
753
+ Drafts are NOT sent to trash - they are permanently deleted.`,
754
+ inputSchema: z.object({
755
+ draftId: z.coerce.string().describe("Draft page ID to delete"),
756
+ }),
757
+ handler: async (args) => {
758
+ try {
759
+ await client.deleteDraft(args.draftId);
760
+ // Invalidate any cached buffers for this draft
761
+ contentBuffer.invalidateByMetadata({
762
+ resourceType: "confluence_page",
763
+ resourceId: args.draftId,
764
+ });
765
+ return formatSuccess({
766
+ deleted: true,
767
+ draftId: args.draftId,
768
+ message: "Draft deleted.",
769
+ });
770
+ }
771
+ catch (error) {
772
+ return formatError(isApiError(error) ? error : new Error(String(error)));
773
+ }
774
+ },
775
+ },
417
776
  };
418
777
  }
419
778
  //# sourceMappingURL=tools.js.map