@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.
- package/README.md +73 -67
- package/TOOL_LIST.md +785 -133
- package/dist/config/constants.d.ts +18 -7
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +21 -8
- package/dist/config/constants.js.map +1 -1
- package/dist/config/loader.d.ts +11 -11
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +53 -93
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +3 -6
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +2 -4
- package/dist/config/types.js.map +1 -1
- package/dist/confluence/formatters.js +1 -1
- package/dist/confluence/formatters.js.map +1 -1
- package/dist/confluence/tools.d.ts +8 -12
- package/dist/confluence/tools.d.ts.map +1 -1
- package/dist/confluence/tools.js +285 -233
- package/dist/confluence/tools.js.map +1 -1
- package/dist/index.js +17 -26
- package/dist/index.js.map +1 -1
- package/dist/jira/formatters.d.ts +1 -0
- package/dist/jira/formatters.d.ts.map +1 -1
- package/dist/jira/formatters.js +13 -12
- package/dist/jira/formatters.js.map +1 -1
- package/dist/jira/tools.d.ts +4 -0
- package/dist/jira/tools.d.ts.map +1 -1
- package/dist/jira/tools.js +234 -44
- package/dist/jira/tools.js.map +1 -1
- package/dist/permissions/tool-registry.d.ts +2 -2
- package/dist/permissions/tool-registry.d.ts.map +1 -1
- package/dist/permissions/tool-registry.js +4 -2
- package/dist/permissions/tool-registry.js.map +1 -1
- package/dist/permissions/write-home-validator.d.ts.map +1 -1
- package/dist/permissions/write-home-validator.js +13 -3
- package/dist/permissions/write-home-validator.js.map +1 -1
- package/dist/tempo/defaults.d.ts +17 -0
- package/dist/tempo/defaults.d.ts.map +1 -0
- package/dist/tempo/defaults.js +26 -0
- package/dist/tempo/defaults.js.map +1 -0
- package/dist/tempo/tools.d.ts +5 -0
- package/dist/tempo/tools.d.ts.map +1 -1
- package/dist/tempo/tools.js +161 -35
- package/dist/tempo/tools.js.map +1 -1
- package/dist/utils/buffer-pipeline/index.d.ts +30 -0
- package/dist/utils/buffer-pipeline/index.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/index.js +317 -0
- package/dist/utils/buffer-pipeline/index.js.map +1 -0
- package/dist/utils/buffer-pipeline/output/csv.d.ts +20 -0
- package/dist/utils/buffer-pipeline/output/csv.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/output/csv.js +117 -0
- package/dist/utils/buffer-pipeline/output/csv.js.map +1 -0
- package/dist/utils/buffer-pipeline/output/json.d.ts +16 -0
- package/dist/utils/buffer-pipeline/output/json.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/output/json.js +48 -0
- package/dist/utils/buffer-pipeline/output/json.js.map +1 -0
- package/dist/utils/buffer-pipeline/output/markdown.d.ts +15 -0
- package/dist/utils/buffer-pipeline/output/markdown.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/output/markdown.js +105 -0
- package/dist/utils/buffer-pipeline/output/markdown.js.map +1 -0
- package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts +16 -0
- package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/output/xhtml-list.js +81 -0
- package/dist/utils/buffer-pipeline/output/xhtml-list.js.map +1 -0
- package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts +15 -0
- package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/output/xhtml-table.js +176 -0
- package/dist/utils/buffer-pipeline/output/xhtml-table.js.map +1 -0
- package/dist/utils/buffer-pipeline/schema.d.ts +1878 -0
- package/dist/utils/buffer-pipeline/schema.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/schema.js +168 -0
- package/dist/utils/buffer-pipeline/schema.js.map +1 -0
- package/dist/utils/buffer-pipeline/stages/filter.d.ts +32 -0
- package/dist/utils/buffer-pipeline/stages/filter.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/stages/filter.js +208 -0
- package/dist/utils/buffer-pipeline/stages/filter.js.map +1 -0
- package/dist/utils/buffer-pipeline/stages/format.d.ts +45 -0
- package/dist/utils/buffer-pipeline/stages/format.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/stages/format.js +160 -0
- package/dist/utils/buffer-pipeline/stages/format.js.map +1 -0
- package/dist/utils/buffer-pipeline/stages/group-by.d.ts +25 -0
- package/dist/utils/buffer-pipeline/stages/group-by.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/stages/group-by.js +190 -0
- package/dist/utils/buffer-pipeline/stages/group-by.js.map +1 -0
- package/dist/utils/buffer-pipeline/stages/select.d.ts +54 -0
- package/dist/utils/buffer-pipeline/stages/select.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/stages/select.js +228 -0
- package/dist/utils/buffer-pipeline/stages/select.js.map +1 -0
- package/dist/utils/buffer-pipeline/stages/sort.d.ts +20 -0
- package/dist/utils/buffer-pipeline/stages/sort.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/stages/sort.js +96 -0
- package/dist/utils/buffer-pipeline/stages/sort.js.map +1 -0
- package/dist/utils/buffer-pipeline/types.d.ts +277 -0
- package/dist/utils/buffer-pipeline/types.d.ts.map +1 -0
- package/dist/utils/buffer-pipeline/types.js +8 -0
- package/dist/utils/buffer-pipeline/types.js.map +1 -0
- package/dist/utils/buffer-tools.d.ts +749 -19
- package/dist/utils/buffer-tools.d.ts.map +1 -1
- package/dist/utils/buffer-tools.js +738 -491
- package/dist/utils/buffer-tools.js.map +1 -1
- package/dist/utils/content-buffer.d.ts +55 -4
- package/dist/utils/content-buffer.d.ts.map +1 -1
- package/dist/utils/content-buffer.js +107 -9
- package/dist/utils/content-buffer.js.map +1 -1
- package/dist/utils/jicon-help.d.ts +1 -1
- package/dist/utils/jicon-help.d.ts.map +1 -1
- package/dist/utils/jicon-help.js +345 -99
- package/dist/utils/jicon-help.js.map +1 -1
- package/dist/utils/json-structure.d.ts +121 -0
- package/dist/utils/json-structure.d.ts.map +1 -0
- package/dist/utils/json-structure.js +637 -0
- package/dist/utils/json-structure.js.map +1 -0
- package/dist/utils/plantuml/include-expander.d.ts +31 -30
- package/dist/utils/plantuml/include-expander.d.ts.map +1 -1
- package/dist/utils/plantuml/include-expander.js +167 -133
- package/dist/utils/plantuml/include-expander.js.map +1 -1
- package/dist/utils/plantuml/index.d.ts +3 -3
- package/dist/utils/plantuml/index.d.ts.map +1 -1
- package/dist/utils/plantuml/index.js +4 -4
- package/dist/utils/plantuml/index.js.map +1 -1
- package/dist/utils/plantuml/service.d.ts +13 -24
- package/dist/utils/plantuml/service.d.ts.map +1 -1
- package/dist/utils/plantuml/service.js +49 -99
- package/dist/utils/plantuml/service.js.map +1 -1
- package/dist/utils/plantuml/tools.d.ts.map +1 -1
- package/dist/utils/plantuml/tools.js +33 -72
- package/dist/utils/plantuml/tools.js.map +1 -1
- package/dist/utils/plantuml/types.d.ts +1 -35
- package/dist/utils/plantuml/types.d.ts.map +1 -1
- package/dist/utils/plantuml/types.js +1 -11
- package/dist/utils/plantuml/types.js.map +1 -1
- package/dist/utils/plantuml/validation-helper.d.ts +1 -1
- package/dist/utils/plantuml/validation-helper.js +12 -12
- package/dist/utils/plantuml/validation-helper.js.map +1 -1
- package/dist/utils/response-formatter.d.ts +68 -0
- package/dist/utils/response-formatter.d.ts.map +1 -1
- package/dist/utils/response-formatter.js +186 -78
- package/dist/utils/response-formatter.js.map +1 -1
- package/dist/utils/url-tools.d.ts.map +1 -1
- package/dist/utils/url-tools.js +22 -0
- package/dist/utils/url-tools.js.map +1 -1
- package/dist/utils/xhtml/error-locator.js +2 -2
- package/dist/utils/xhtml/error-locator.js.map +1 -1
- package/dist/utils/xhtml/index.d.ts +1 -1
- package/dist/utils/xhtml/index.d.ts.map +1 -1
- package/dist/utils/xhtml/index.js +1 -1
- package/dist/utils/xhtml/index.js.map +1 -1
- package/dist/utils/xhtml/parser.d.ts +34 -5
- package/dist/utils/xhtml/parser.d.ts.map +1 -1
- package/dist/utils/xhtml/parser.js +66 -11
- package/dist/utils/xhtml/parser.js.map +1 -1
- package/dist/utils/xhtml/plantuml.d.ts.map +1 -1
- package/dist/utils/xhtml/plantuml.js +5 -3
- package/dist/utils/xhtml/plantuml.js.map +1 -1
- package/dist/utils/xhtml/serializer.d.ts.map +1 -1
- package/dist/utils/xhtml/serializer.js +12 -15
- package/dist/utils/xhtml/serializer.js.map +1 -1
- package/dist/utils/xhtml/types.d.ts +1 -0
- package/dist/utils/xhtml/types.d.ts.map +1 -1
- package/package.json +12 -4
package/dist/confluence/tools.js
CHANGED
|
@@ -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,
|
|
8
|
+
import { validateXhtmlAsync, parseXhtml, enhanceXhtmlError } from "../utils/xhtml/index.js";
|
|
9
9
|
import { detectRawPlantUml, detectDiagramType } from "../utils/xhtml/plantuml.js";
|
|
10
|
-
import {
|
|
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
|
|
107
|
+
// Add recovery instructions
|
|
155
108
|
errorMessages.push("");
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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~
|
|
150
|
+
Example: confluence_search_content(cql="type=page AND space=DOCS AND text~'API'")
|
|
193
151
|
|
|
194
|
-
WARNING: Use text~
|
|
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
|
-
|
|
203
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: `
|
|
311
|
+
description: `Load page/draft for editing. Returns bufferId, structure (element IDs), pageId, version.
|
|
350
312
|
|
|
351
|
-
ACCEPTS
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
385
|
-
const
|
|
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
|
|
409
|
-
const
|
|
410
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
591
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
709
|
+
description: `Create draft for user review. Requires bufferId with valid XHTML.
|
|
718
710
|
|
|
719
|
-
|
|
720
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
|
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
|
-
|
|
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: `
|
|
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
|
-
//
|
|
863
|
-
|
|
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
|
|
926
|
-
resourceType: "
|
|
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
|
|
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
|
|
1015
|
-
resourceType: "
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
1133
|
-
|
|
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: "
|
|
1189
|
+
resourceType: "confluence_draft",
|
|
1165
1190
|
resourceId: args.draftId,
|
|
1166
1191
|
});
|
|
1167
1192
|
// Store content with element IDs for structured editing
|
|
1168
|
-
const
|
|
1169
|
-
resourceType: "
|
|
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
|
-
|
|
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
|
-
|
|
1301
|
-
|
|
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
|