@crypto512/jicon-mcp 1.0.4 → 1.1.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 +40 -3
- package/TOOL_LIST.md +261 -140
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +2 -0
- package/dist/config/constants.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +2 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -0
- package/dist/config/types.js.map +1 -1
- package/dist/confluence/client.d.ts +11 -2
- package/dist/confluence/client.d.ts.map +1 -1
- package/dist/confluence/client.js +29 -12
- package/dist/confluence/client.js.map +1 -1
- package/dist/confluence/tools.d.ts +2 -2
- package/dist/confluence/tools.d.ts.map +1 -1
- package/dist/confluence/tools.js +95 -58
- package/dist/confluence/tools.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/jira/client.d.ts.map +1 -1
- package/dist/jira/client.js +3 -13
- package/dist/jira/client.js.map +1 -1
- package/dist/jira/tools.d.ts.map +1 -1
- package/dist/jira/tools.js +9 -30
- package/dist/jira/tools.js.map +1 -1
- package/dist/permissions/tool-registry.d.ts +4 -2
- package/dist/permissions/tool-registry.d.ts.map +1 -1
- package/dist/permissions/tool-registry.js +14 -2
- package/dist/permissions/tool-registry.js.map +1 -1
- package/dist/tempo/formatters.d.ts.map +1 -1
- package/dist/tempo/formatters.js +1 -16
- package/dist/tempo/formatters.js.map +1 -1
- package/dist/tempo/tools.d.ts +2 -2
- package/dist/tempo/tools.d.ts.map +1 -1
- package/dist/tempo/tools.js +5 -13
- package/dist/tempo/tools.js.map +1 -1
- package/dist/utils/buffer-tools.d.ts +115 -63
- package/dist/utils/buffer-tools.d.ts.map +1 -1
- package/dist/utils/buffer-tools.js +385 -388
- package/dist/utils/buffer-tools.js.map +1 -1
- package/dist/utils/content-buffer.d.ts +24 -1
- package/dist/utils/content-buffer.d.ts.map +1 -1
- package/dist/utils/content-buffer.js +49 -39
- package/dist/utils/content-buffer.js.map +1 -1
- package/dist/utils/jicon-help.d.ts +5 -5
- package/dist/utils/jicon-help.d.ts.map +1 -1
- package/dist/utils/jicon-help.js +258 -588
- package/dist/utils/jicon-help.js.map +1 -1
- package/dist/utils/plantuml/client.d.ts +1 -1
- package/dist/utils/plantuml/client.d.ts.map +1 -1
- package/dist/utils/plantuml/client.js +34 -59
- package/dist/utils/plantuml/client.js.map +1 -1
- package/dist/utils/plantuml/docker-manager.d.ts +3 -1
- package/dist/utils/plantuml/docker-manager.d.ts.map +1 -1
- package/dist/utils/plantuml/docker-manager.js +6 -2
- package/dist/utils/plantuml/docker-manager.js.map +1 -1
- package/dist/utils/plantuml/include-expander.d.ts +32 -0
- package/dist/utils/plantuml/include-expander.d.ts.map +1 -0
- package/dist/utils/plantuml/include-expander.js +129 -0
- package/dist/utils/plantuml/include-expander.js.map +1 -0
- package/dist/utils/plantuml/index.d.ts +2 -1
- package/dist/utils/plantuml/index.d.ts.map +1 -1
- package/dist/utils/plantuml/index.js +3 -1
- package/dist/utils/plantuml/index.js.map +1 -1
- package/dist/utils/plantuml/service.d.ts +32 -8
- package/dist/utils/plantuml/service.d.ts.map +1 -1
- package/dist/utils/plantuml/service.js +108 -15
- package/dist/utils/plantuml/service.js.map +1 -1
- package/dist/utils/plantuml/tools.d.ts +4 -4
- package/dist/utils/plantuml/tools.d.ts.map +1 -1
- package/dist/utils/plantuml/tools.js +50 -5
- package/dist/utils/plantuml/tools.js.map +1 -1
- package/dist/utils/plantuml/types.d.ts +4 -4
- package/dist/utils/plantuml/validation-helper.d.ts +33 -0
- package/dist/utils/plantuml/validation-helper.d.ts.map +1 -0
- package/dist/utils/plantuml/validation-helper.js +97 -0
- package/dist/utils/plantuml/validation-helper.js.map +1 -0
- package/dist/utils/response-formatter.js +7 -7
- package/dist/utils/response-formatter.js.map +1 -1
- package/dist/utils/time-formatter.d.ts +10 -0
- package/dist/utils/time-formatter.d.ts.map +1 -0
- package/dist/utils/time-formatter.js +22 -0
- package/dist/utils/time-formatter.js.map +1 -0
- package/dist/utils/xhtml/error-locator.d.ts +70 -0
- package/dist/utils/xhtml/error-locator.d.ts.map +1 -0
- package/dist/utils/xhtml/error-locator.js +229 -0
- package/dist/utils/xhtml/error-locator.js.map +1 -0
- package/dist/utils/xhtml/index.d.ts +6 -3
- package/dist/utils/xhtml/index.d.ts.map +1 -1
- package/dist/utils/xhtml/index.js +7 -3
- package/dist/utils/xhtml/index.js.map +1 -1
- package/dist/utils/xhtml/operations.d.ts +54 -0
- package/dist/utils/xhtml/operations.d.ts.map +1 -1
- package/dist/utils/xhtml/operations.js +205 -0
- package/dist/utils/xhtml/operations.js.map +1 -1
- package/dist/utils/xhtml/plantuml.d.ts +1 -1
- package/dist/utils/xhtml/plantuml.d.ts.map +1 -1
- package/dist/utils/xhtml/plantuml.js +19 -30
- package/dist/utils/xhtml/plantuml.js.map +1 -1
- package/dist/utils/xhtml/serializer.d.ts +5 -0
- package/dist/utils/xhtml/serializer.d.ts.map +1 -1
- package/dist/utils/xhtml/serializer.js +18 -4
- package/dist/utils/xhtml/serializer.js.map +1 -1
- package/dist/utils/xhtml/types.d.ts +3 -3
- package/dist/utils/xhtml/types.d.ts.map +1 -1
- package/dist/utils/xhtml/validator.d.ts.map +1 -1
- package/dist/utils/xhtml/validator.js +87 -4
- package/dist/utils/xhtml/validator.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,8 +10,10 @@ import * as fs from "fs";
|
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { contentBuffer } from "./content-buffer.js";
|
|
12
12
|
import { formatSuccess, formatError, getMaxOutputSize } from "./response-formatter.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import { parseXhtml, serializeXhtml, validateXhtmlAsync, buildPlantUmlMacro, detectRawPlantUml,
|
|
14
|
+
// Element ID-based operations
|
|
15
|
+
parseStructure, insertById, appendToDocument, replaceById, removeById, } from "./xhtml/index.js";
|
|
16
|
+
import { validatePlantUmlWithFallback } from "./plantuml/index.js";
|
|
15
17
|
/**
|
|
16
18
|
* Format grep results as text output similar to grep CLI
|
|
17
19
|
*/
|
|
@@ -86,9 +88,14 @@ function isBinaryContentType(contentType) {
|
|
|
86
88
|
export function createBufferTools() {
|
|
87
89
|
return {
|
|
88
90
|
buffer_create: {
|
|
89
|
-
description: `Create a new buffer with initial content.
|
|
91
|
+
description: `Create a new buffer with initial content. Returns bufferId and structure (for XHTML).
|
|
92
|
+
|
|
93
|
+
For XHTML content, returns structure with element IDs for use with buffer_edit:
|
|
94
|
+
- Each element gets a unique ID (stable during session)
|
|
95
|
+
- Use buffer_edit with after/before/replace to modify by element ID`,
|
|
90
96
|
inputSchema: z.object({
|
|
91
97
|
content: z.string().describe("Initial content for the buffer"),
|
|
98
|
+
contentType: z.enum(["xhtml", "plain", "json"]).describe("Content type: 'xhtml' for Confluence, 'plain' for text, 'json' for data"),
|
|
92
99
|
metadata: z
|
|
93
100
|
.record(z.unknown())
|
|
94
101
|
.optional()
|
|
@@ -96,15 +103,65 @@ export function createBufferTools() {
|
|
|
96
103
|
}),
|
|
97
104
|
handler: async (args) => {
|
|
98
105
|
try {
|
|
99
|
-
const
|
|
106
|
+
const mergedMetadata = {
|
|
107
|
+
...args.metadata,
|
|
108
|
+
contentType: args.contentType,
|
|
109
|
+
};
|
|
110
|
+
let bufferId;
|
|
111
|
+
let structure;
|
|
112
|
+
let nextId;
|
|
113
|
+
if (args.contentType === "xhtml") {
|
|
114
|
+
// Check for common CDATA mistake: wrapping entire content in CDATA
|
|
115
|
+
const trimmedContent = args.content.trim();
|
|
116
|
+
if (trimmedContent.startsWith("<![CDATA[")) {
|
|
117
|
+
return formatError({
|
|
118
|
+
error: true,
|
|
119
|
+
message: "Content starts with CDATA wrapper. Remove <![CDATA[ and ]]> from the content parameter.",
|
|
120
|
+
statusCode: 400,
|
|
121
|
+
details: {
|
|
122
|
+
hint: "CDATA is only used inside <ac:plain-text-body> elements (for code blocks and PlantUML). Never wrap the entire content in CDATA.",
|
|
123
|
+
example: "Use: <h1>Title</h1><p>Text</p>\nNot: <![CDATA[<h1>Title</h1><p>Text</p>]]>",
|
|
124
|
+
helpTopic: 'Call help(topic="storage") for Confluence XHTML format guide.',
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Parse XHTML and build structure with element IDs
|
|
129
|
+
const parseResult = parseXhtml(args.content);
|
|
130
|
+
if (!parseResult.document) {
|
|
131
|
+
return formatError({
|
|
132
|
+
error: true,
|
|
133
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
134
|
+
statusCode: 400,
|
|
135
|
+
details: {
|
|
136
|
+
parseError: parseResult.error?.message,
|
|
137
|
+
line: parseResult.error?.line,
|
|
138
|
+
column: parseResult.error?.column,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Assign IDs and get structure
|
|
143
|
+
const structureResult = parseStructure(parseResult.document);
|
|
144
|
+
structure = structureResult.structure;
|
|
145
|
+
nextId = structureResult.nextId;
|
|
146
|
+
// Serialize back (now with data-jicon-id attributes)
|
|
147
|
+
const contentWithIds = serializeXhtml(parseResult.document);
|
|
148
|
+
bufferId = contentBuffer.storeWithStructure(contentWithIds, structure, nextId, mergedMetadata);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
bufferId = contentBuffer.store(args.content, mergedMetadata);
|
|
152
|
+
}
|
|
100
153
|
const info = contentBuffer.getInfo(bufferId);
|
|
101
154
|
return formatSuccess({
|
|
102
155
|
bufferId,
|
|
103
156
|
totalSize: info?.totalSize ?? args.content.length,
|
|
157
|
+
contentType: args.contentType,
|
|
104
158
|
createdAt: info ? new Date(info.createdAt).toISOString() : new Date().toISOString(),
|
|
105
159
|
expiresAt: info ? new Date(info.expiresAt).toISOString() : undefined,
|
|
106
|
-
metadata:
|
|
107
|
-
|
|
160
|
+
metadata: mergedMetadata,
|
|
161
|
+
...(structure && { structure, nextId }),
|
|
162
|
+
message: args.contentType === "xhtml"
|
|
163
|
+
? "Buffer created with element IDs. Use buffer_edit with after/before/replace to modify."
|
|
164
|
+
: "Buffer created.",
|
|
108
165
|
});
|
|
109
166
|
}
|
|
110
167
|
catch (error) {
|
|
@@ -343,81 +400,57 @@ Example: Find buffer for draft 12345 → look for metadata.resourceId === "12345
|
|
|
343
400
|
},
|
|
344
401
|
},
|
|
345
402
|
buffer_edit: {
|
|
346
|
-
description: `
|
|
403
|
+
description: `Edit buffer content. For XHTML buffers, use element IDs. For plain/json, use string replacement.
|
|
347
404
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.default(false)
|
|
357
|
-
.describe("Replace all occurrences (default: false, fails if not unique)"),
|
|
358
|
-
}),
|
|
359
|
-
handler: async (args) => {
|
|
360
|
-
try {
|
|
361
|
-
// Check if buffer contains XHTML content - reject to force use of buffer_edit_xhtml
|
|
362
|
-
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
363
|
-
if (bufferInfo?.metadata?.contentType === "xhtml") {
|
|
364
|
-
return formatError({
|
|
365
|
-
error: true,
|
|
366
|
-
message: "This buffer contains Confluence XHTML content. Use buffer_edit_xhtml instead for proper structure-aware editing.",
|
|
367
|
-
statusCode: 400,
|
|
368
|
-
details: {
|
|
369
|
-
hint: "For PlantUML diagrams, use: buffer_edit_xhtml(bufferId, operation='insert-plantuml', ...)",
|
|
370
|
-
contentType: "xhtml",
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
const result = contentBuffer.edit(args.bufferId, args.old_string, args.new_string, args.replace_all ?? false);
|
|
375
|
-
if (!result) {
|
|
376
|
-
return formatError({
|
|
377
|
-
error: true,
|
|
378
|
-
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
379
|
-
statusCode: 404,
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
// Check if it's an error result
|
|
383
|
-
if ("error" in result && result.error) {
|
|
384
|
-
return formatError({
|
|
385
|
-
error: true,
|
|
386
|
-
message: result.message,
|
|
387
|
-
statusCode: 400,
|
|
388
|
-
details: { occurrences: result.occurrences },
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
return formatSuccess(result);
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
395
|
-
}
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
buffer_edit_xhtml: {
|
|
399
|
-
description: `Structure-aware XHTML editing for Confluence storage format. Supports CSS-like selectors to target elements, with operations: insert, insert-plantuml, update, update-plantuml, remove, move, wrap.
|
|
405
|
+
XHTML editing (by element ID) - single operation:
|
|
406
|
+
- after: Insert content after element with this ID
|
|
407
|
+
- before: Insert content before element with this ID
|
|
408
|
+
- replace: Replace element with this ID
|
|
409
|
+
- append: Add content at document end
|
|
410
|
+
- remove: Remove element with this ID
|
|
411
|
+
- content: XHTML to insert/replace
|
|
412
|
+
- plantuml: PlantUML code (auto-wrapped in Confluence macro)
|
|
400
413
|
|
|
401
|
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
- Pseudo: ':nth-child(2)', ':first-child', ':last-child'
|
|
406
|
-
- Descendants: 'table tbody tr'
|
|
407
|
-
- Direct child: 'ul > li'
|
|
414
|
+
XHTML batch editing - multiple operations in one call:
|
|
415
|
+
- operations: Array of {after?, before?, replace?, append?, remove?, content?, plantuml?}
|
|
416
|
+
- Operations are executed sequentially; stops on first error
|
|
417
|
+
- Much more efficient than multiple tool calls (parse once, serialize once)
|
|
408
418
|
|
|
409
|
-
|
|
410
|
-
- 'after-title': After first h1 or h2
|
|
411
|
-
- 'after-heading': After first heading (h1-h6)
|
|
412
|
-
- 'before-content': At document start
|
|
413
|
-
- 'end': At document end
|
|
414
|
-
- 'after-toc': After table of contents macro
|
|
419
|
+
Example batch: operations=[{after:6, plantuml:"@startuml..."}, {after:8, plantuml:"@startuml..."}]
|
|
415
420
|
|
|
416
|
-
|
|
417
|
-
|
|
421
|
+
Plain/JSON editing (string replacement):
|
|
422
|
+
- old_string: Text to replace
|
|
423
|
+
- new_string: Replacement text
|
|
424
|
+
- replace_all: Replace all occurrences
|
|
425
|
+
|
|
426
|
+
Returns updated structure for XHTML buffers.`,
|
|
427
|
+
inputSchema: z.object({
|
|
428
|
+
bufferId: z.string().describe("Buffer ID to modify"),
|
|
429
|
+
// XHTML batch operations
|
|
430
|
+
operations: z.array(z.object({
|
|
431
|
+
after: z.number().optional().describe("Insert after element with this ID"),
|
|
432
|
+
before: z.number().optional().describe("Insert before element with this ID"),
|
|
433
|
+
replace: z.number().optional().describe("Replace element with this ID"),
|
|
434
|
+
append: z.boolean().optional().describe("Append content at document end"),
|
|
435
|
+
remove: z.number().optional().describe("Remove element with this ID"),
|
|
436
|
+
content: z.string().optional().describe("XHTML content to insert/replace"),
|
|
437
|
+
plantuml: z.string().optional().describe("PlantUML code (auto-wrapped in macro)"),
|
|
438
|
+
})).optional().describe("Array of XHTML operations to execute sequentially (more efficient than multiple calls)"),
|
|
439
|
+
// XHTML element ID operations (single operation - backwards compatible)
|
|
440
|
+
after: z.number().optional().describe("Insert after element with this ID"),
|
|
441
|
+
before: z.number().optional().describe("Insert before element with this ID"),
|
|
442
|
+
replace: z.number().optional().describe("Replace element with this ID"),
|
|
443
|
+
append: z.boolean().optional().describe("Append content at document end"),
|
|
444
|
+
remove: z.number().optional().describe("Remove element with this ID"),
|
|
445
|
+
content: z.string().optional().describe("XHTML content to insert/replace"),
|
|
446
|
+
plantuml: z.string().optional().describe("PlantUML code (auto-wrapped in macro)"),
|
|
447
|
+
// Plain text operations
|
|
448
|
+
old_string: z.string().optional().describe("Text to replace (plain/json only)"),
|
|
449
|
+
new_string: z.string().optional().describe("Replacement text (plain/json only)"),
|
|
450
|
+
replace_all: z.boolean().optional().default(false).describe("Replace all occurrences"),
|
|
451
|
+
}),
|
|
418
452
|
handler: async (args) => {
|
|
419
453
|
try {
|
|
420
|
-
// Get buffer content
|
|
421
454
|
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
422
455
|
if (!bufferInfo) {
|
|
423
456
|
return formatError({
|
|
@@ -426,384 +459,334 @@ PlantUML: Use plantuml_validate first for full Docker-based validation. This too
|
|
|
426
459
|
statusCode: 404,
|
|
427
460
|
});
|
|
428
461
|
}
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
const originalContent = chunk.chunk;
|
|
438
|
-
const oldSize = originalContent.length;
|
|
439
|
-
// Parse XHTML
|
|
440
|
-
const parseResult = parseXhtml(originalContent);
|
|
441
|
-
if (!parseResult.document) {
|
|
442
|
-
return formatError({
|
|
443
|
-
error: true,
|
|
444
|
-
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
445
|
-
statusCode: 400,
|
|
446
|
-
details: {
|
|
447
|
-
parseError: parseResult.error?.message,
|
|
448
|
-
line: parseResult.error?.line,
|
|
449
|
-
column: parseResult.error?.column,
|
|
450
|
-
context: parseResult.error?.context,
|
|
451
|
-
hint: parseResult.error?.context
|
|
452
|
-
? `Check near: "${parseResult.error.context.substring(0, 60)}${parseResult.error.context.length > 60 ? "..." : ""}"`
|
|
453
|
-
: undefined,
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
// Handle PlantUML operations specially
|
|
458
|
-
let contentToInsert = args.content;
|
|
459
|
-
const operation = args.operation;
|
|
460
|
-
let resolvedSelector = args.selector;
|
|
461
|
-
let resolvedPosition = args.position;
|
|
462
|
-
let insertDiagramType; // Track diagram type for insert-plantuml
|
|
463
|
-
// Check if we need to derive selector from semanticPosition
|
|
464
|
-
if (!resolvedSelector && args.semanticPosition) {
|
|
465
|
-
const resolved = resolveSemanticPosition(parseResult.document, args.semanticPosition);
|
|
466
|
-
if (resolved) {
|
|
467
|
-
resolvedSelector = resolved.selector;
|
|
468
|
-
resolvedPosition = resolved.position;
|
|
462
|
+
const isXhtml = bufferInfo.metadata?.contentType === "xhtml";
|
|
463
|
+
// XHTML editing with element IDs
|
|
464
|
+
if (isXhtml) {
|
|
465
|
+
let operations;
|
|
466
|
+
if (args.operations && args.operations.length > 0) {
|
|
467
|
+
// Batch mode: use operations array
|
|
468
|
+
operations = args.operations;
|
|
469
469
|
}
|
|
470
470
|
else {
|
|
471
|
+
// Single operation mode (backwards compatible)
|
|
472
|
+
const hasIdOperation = args.after !== undefined || args.before !== undefined ||
|
|
473
|
+
args.replace !== undefined || args.append || args.remove !== undefined;
|
|
474
|
+
if (!hasIdOperation) {
|
|
475
|
+
return formatError({
|
|
476
|
+
error: true,
|
|
477
|
+
message: "XHTML buffer requires element ID operation: after, before, replace, append, remove, or operations array",
|
|
478
|
+
statusCode: 400,
|
|
479
|
+
details: {
|
|
480
|
+
structure: bufferInfo.structure,
|
|
481
|
+
nextId: bufferInfo.nextId,
|
|
482
|
+
hint: "Use after=ID, before=ID, replace=ID, append=true, remove=ID, or operations=[...]",
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
operations = [{
|
|
487
|
+
after: args.after,
|
|
488
|
+
before: args.before,
|
|
489
|
+
replace: args.replace,
|
|
490
|
+
append: args.append,
|
|
491
|
+
remove: args.remove,
|
|
492
|
+
content: args.content,
|
|
493
|
+
plantuml: args.plantuml,
|
|
494
|
+
}];
|
|
495
|
+
}
|
|
496
|
+
// Get buffer content
|
|
497
|
+
const chunk = contentBuffer.getChunk(args.bufferId, 0, bufferInfo.totalSize);
|
|
498
|
+
if (!chunk) {
|
|
471
499
|
return formatError({
|
|
472
500
|
error: true,
|
|
473
|
-
message: `
|
|
474
|
-
statusCode:
|
|
475
|
-
details: {
|
|
476
|
-
semanticPosition: args.semanticPosition,
|
|
477
|
-
hint: "Document may be empty or missing required elements",
|
|
478
|
-
},
|
|
501
|
+
message: `Failed to read buffer: ${args.bufferId}`,
|
|
502
|
+
statusCode: 500,
|
|
479
503
|
});
|
|
480
504
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return formatError({
|
|
485
|
-
error: true,
|
|
486
|
-
message: "Either selector or semanticPosition is required",
|
|
487
|
-
statusCode: 400,
|
|
488
|
-
details: {
|
|
489
|
-
hint: "Provide selector (e.g., 'h1', 'table', 'ac:structured-macro[ac:name=\"plantuml\"]') or semanticPosition (e.g., 'after-title', 'end')",
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
if (operation === "insert-plantuml") {
|
|
494
|
-
if (!args.plantuml) {
|
|
505
|
+
// Parse XHTML once
|
|
506
|
+
const parseResult = parseXhtml(chunk.chunk);
|
|
507
|
+
if (!parseResult.document) {
|
|
495
508
|
return formatError({
|
|
496
509
|
error: true,
|
|
497
|
-
message:
|
|
510
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
498
511
|
statusCode: 400,
|
|
499
512
|
});
|
|
500
513
|
}
|
|
501
|
-
|
|
502
|
-
let
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
514
|
+
const oldSize = chunk.chunk.length;
|
|
515
|
+
let nextId = bufferInfo.nextId ?? 1;
|
|
516
|
+
const allInsertedIds = [];
|
|
517
|
+
const diagramTypes = [];
|
|
518
|
+
// Execute all operations sequentially
|
|
519
|
+
for (let opIndex = 0; opIndex < operations.length; opIndex++) {
|
|
520
|
+
const op = operations[opIndex];
|
|
521
|
+
// Build content to insert (handle plantuml specially)
|
|
522
|
+
let contentToInsert = op.content;
|
|
523
|
+
let diagramType;
|
|
524
|
+
if (op.plantuml) {
|
|
525
|
+
// Validate PlantUML
|
|
526
|
+
const validation = await validatePlantUmlWithFallback(op.plantuml, "insert-plantuml", {});
|
|
527
|
+
if (!validation.valid) {
|
|
528
|
+
// Return error with operation index for debugging
|
|
529
|
+
const errorResult = validation.error;
|
|
530
|
+
const errorData = JSON.parse(errorResult.content[0].text);
|
|
531
|
+
errorData.operationIndex = opIndex;
|
|
532
|
+
errorData.completedOperations = opIndex;
|
|
533
|
+
return {
|
|
534
|
+
content: [{ type: "text", text: JSON.stringify(errorData, null, 2) }],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
contentToInsert = buildPlantUmlMacro(validation.normalizedCode);
|
|
538
|
+
diagramType = validation.diagramType;
|
|
539
|
+
if (diagramType)
|
|
540
|
+
diagramTypes.push(diagramType);
|
|
541
|
+
}
|
|
542
|
+
// Execute single operation
|
|
543
|
+
let result;
|
|
544
|
+
if (op.remove !== undefined) {
|
|
545
|
+
result = removeById(parseResult.document, op.remove);
|
|
546
|
+
}
|
|
547
|
+
else if (op.replace !== undefined) {
|
|
548
|
+
if (!contentToInsert) {
|
|
509
549
|
return formatError({
|
|
510
550
|
error: true,
|
|
511
|
-
message:
|
|
551
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for replace operation`,
|
|
512
552
|
statusCode: 400,
|
|
513
|
-
details: {
|
|
514
|
-
operation: "insert-plantuml",
|
|
515
|
-
plantumlErrors: dockerValidation.errors,
|
|
516
|
-
diagramType: dockerValidation.diagramType,
|
|
517
|
-
codePreview,
|
|
518
|
-
targetPosition: args.semanticPosition || args.selector,
|
|
519
|
-
recovery: [
|
|
520
|
-
"1. Use plantuml_validate(code=...) to check syntax separately",
|
|
521
|
-
"2. Fix the PlantUML code based on the errors above",
|
|
522
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
523
|
-
],
|
|
524
|
-
},
|
|
525
553
|
});
|
|
526
554
|
}
|
|
527
|
-
|
|
528
|
-
insertDiagramType = dockerValidation.diagramType;
|
|
555
|
+
result = replaceById(parseResult.document, op.replace, contentToInsert);
|
|
529
556
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
533
|
-
if (!basicValidation.valid) {
|
|
534
|
-
const codePreview = args.plantuml.substring(0, 200) + (args.plantuml.length > 200 ? "..." : "");
|
|
557
|
+
else if (op.append) {
|
|
558
|
+
if (!contentToInsert) {
|
|
535
559
|
return formatError({
|
|
536
560
|
error: true,
|
|
537
|
-
message:
|
|
561
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for append operation`,
|
|
538
562
|
statusCode: 400,
|
|
539
|
-
details: {
|
|
540
|
-
operation: "insert-plantuml",
|
|
541
|
-
plantumlError: basicValidation.error,
|
|
542
|
-
codePreview,
|
|
543
|
-
targetPosition: args.semanticPosition || args.selector,
|
|
544
|
-
recovery: [
|
|
545
|
-
"1. Use plantuml_validate(code=...) for detailed Docker-based validation",
|
|
546
|
-
"2. Fix the PlantUML code based on the error above",
|
|
547
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
548
|
-
],
|
|
549
|
-
},
|
|
550
563
|
});
|
|
551
564
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
// Use basic synchronous validation
|
|
558
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
559
|
-
if (!basicValidation.valid) {
|
|
560
|
-
const codePreview = args.plantuml.substring(0, 200) + (args.plantuml.length > 200 ? "..." : "");
|
|
561
|
-
return formatError({
|
|
562
|
-
error: true,
|
|
563
|
-
message: "PlantUML syntax error - cannot insert invalid diagram",
|
|
564
|
-
statusCode: 400,
|
|
565
|
-
details: {
|
|
566
|
-
operation: "insert-plantuml",
|
|
567
|
-
plantumlError: basicValidation.error,
|
|
568
|
-
codePreview,
|
|
569
|
-
targetPosition: args.semanticPosition || args.selector,
|
|
570
|
-
recovery: [
|
|
571
|
-
"1. Use plantuml_validate(code=...) for full Docker-based validation",
|
|
572
|
-
"2. Fix the PlantUML code based on the error above",
|
|
573
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
574
|
-
],
|
|
575
|
-
},
|
|
576
|
-
});
|
|
565
|
+
result = appendToDocument(parseResult.document, contentToInsert, nextId);
|
|
566
|
+
if (result.nextId)
|
|
567
|
+
nextId = result.nextId;
|
|
577
568
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
// Build the macro
|
|
582
|
-
contentToInsert = buildPlantUmlMacro(normalizedCode, args.macroId);
|
|
583
|
-
}
|
|
584
|
-
else if (operation === "update-plantuml") {
|
|
585
|
-
if (!args.plantuml) {
|
|
586
|
-
return formatError({
|
|
587
|
-
error: true,
|
|
588
|
-
message: "plantuml parameter is required for update-plantuml operation",
|
|
589
|
-
statusCode: 400,
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
// Validate PlantUML - use Docker service if available, else fallback to basic validation
|
|
593
|
-
let normalizedCode;
|
|
594
|
-
let updateDiagramType;
|
|
595
|
-
if (isPlantUmlServiceAvailable()) {
|
|
596
|
-
// Use Docker-based validation
|
|
597
|
-
try {
|
|
598
|
-
const dockerValidation = await validatePlantUmlWithDocker(args.plantuml);
|
|
599
|
-
if (!dockerValidation.valid) {
|
|
600
|
-
const codePreview = args.plantuml.substring(0, 200) + (args.plantuml.length > 200 ? "..." : "");
|
|
569
|
+
else if (op.after !== undefined) {
|
|
570
|
+
if (!contentToInsert) {
|
|
601
571
|
return formatError({
|
|
602
572
|
error: true,
|
|
603
|
-
message:
|
|
573
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for after operation`,
|
|
604
574
|
statusCode: 400,
|
|
605
|
-
details: {
|
|
606
|
-
operation: "update-plantuml",
|
|
607
|
-
plantumlErrors: dockerValidation.errors,
|
|
608
|
-
diagramType: dockerValidation.diagramType,
|
|
609
|
-
codePreview,
|
|
610
|
-
targetSelector: args.selector,
|
|
611
|
-
recovery: [
|
|
612
|
-
"1. Use plantuml_validate(code=...) to check syntax separately",
|
|
613
|
-
"2. Fix the PlantUML code based on the errors above",
|
|
614
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
615
|
-
],
|
|
616
|
-
},
|
|
617
575
|
});
|
|
618
576
|
}
|
|
619
|
-
|
|
620
|
-
|
|
577
|
+
result = insertById(parseResult.document, op.after, "after", contentToInsert, nextId);
|
|
578
|
+
if (result.nextId)
|
|
579
|
+
nextId = result.nextId;
|
|
621
580
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
625
|
-
if (!basicValidation.valid) {
|
|
626
|
-
const codePreview = args.plantuml.substring(0, 200) + (args.plantuml.length > 200 ? "..." : "");
|
|
581
|
+
else if (op.before !== undefined) {
|
|
582
|
+
if (!contentToInsert) {
|
|
627
583
|
return formatError({
|
|
628
584
|
error: true,
|
|
629
|
-
message:
|
|
585
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for before operation`,
|
|
630
586
|
statusCode: 400,
|
|
631
|
-
details: {
|
|
632
|
-
operation: "update-plantuml",
|
|
633
|
-
plantumlError: basicValidation.error,
|
|
634
|
-
codePreview,
|
|
635
|
-
targetSelector: args.selector,
|
|
636
|
-
recovery: [
|
|
637
|
-
"1. Use plantuml_validate(code=...) for detailed Docker-based validation",
|
|
638
|
-
"2. Fix the PlantUML code based on the error above",
|
|
639
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
640
|
-
],
|
|
641
|
-
},
|
|
642
587
|
});
|
|
643
588
|
}
|
|
644
|
-
|
|
645
|
-
|
|
589
|
+
result = insertById(parseResult.document, op.before, "before", contentToInsert, nextId);
|
|
590
|
+
if (result.nextId)
|
|
591
|
+
nextId = result.nextId;
|
|
646
592
|
}
|
|
647
|
-
|
|
648
|
-
else {
|
|
649
|
-
// Use basic synchronous validation
|
|
650
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
651
|
-
if (!basicValidation.valid) {
|
|
652
|
-
const codePreview = args.plantuml.substring(0, 200) + (args.plantuml.length > 200 ? "..." : "");
|
|
593
|
+
else {
|
|
653
594
|
return formatError({
|
|
654
595
|
error: true,
|
|
655
|
-
message:
|
|
596
|
+
message: `Operation ${opIndex + 1}: No valid operation specified (need after, before, replace, append, or remove)`,
|
|
656
597
|
statusCode: 400,
|
|
657
|
-
details: {
|
|
658
|
-
operation: "update-plantuml",
|
|
659
|
-
plantumlError: basicValidation.error,
|
|
660
|
-
codePreview,
|
|
661
|
-
targetSelector: args.selector,
|
|
662
|
-
recovery: [
|
|
663
|
-
"1. Use plantuml_validate(code=...) for full Docker-based validation",
|
|
664
|
-
"2. Fix the PlantUML code based on the error above",
|
|
665
|
-
"3. Retry buffer_edit_xhtml with corrected code",
|
|
666
|
-
],
|
|
667
|
-
},
|
|
668
598
|
});
|
|
669
599
|
}
|
|
670
|
-
|
|
671
|
-
updateDiagramType = basicValidation.diagramType;
|
|
672
|
-
}
|
|
673
|
-
// Find the macro element and update it directly
|
|
674
|
-
const selectorResult = querySelector(parseResult.document, args.selector);
|
|
675
|
-
if (selectorResult.matches.length === 0) {
|
|
676
|
-
const unsupportedPseudo = findUnsupportedPseudo(args.selector);
|
|
677
|
-
const hint = unsupportedPseudo
|
|
678
|
-
? `Pseudo-selector ':${unsupportedPseudo}' is not supported. Use ':contains("text")' to find elements by text content, or use 'ac:structured-macro[ac:name="plantuml"]' with matchIndex.`
|
|
679
|
-
: "Use 'ac:structured-macro[ac:name=\"plantuml\"]' to find PlantUML macros, or use matchIndex if multiple exist.";
|
|
680
|
-
return formatError({
|
|
681
|
-
error: true,
|
|
682
|
-
message: `No elements match selector: ${args.selector}`,
|
|
683
|
-
statusCode: 400,
|
|
684
|
-
details: { selector: args.selector, matchCount: 0, hint },
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
const targetIndex = args.matchIndex ?? 0;
|
|
688
|
-
if (targetIndex >= selectorResult.matches.length) {
|
|
689
|
-
return formatError({
|
|
690
|
-
error: true,
|
|
691
|
-
message: `matchIndex ${targetIndex} out of range (${selectorResult.matches.length} matches)`,
|
|
692
|
-
statusCode: 400,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
const macroElement = selectorResult.matches[targetIndex].element;
|
|
696
|
-
// Verify it's a plantuml macro
|
|
697
|
-
if (macroElement.tagName.toLowerCase() !== "ac:structured-macro" ||
|
|
698
|
-
macroElement.getAttribute("ac:name") !== "plantuml") {
|
|
699
|
-
return formatError({
|
|
700
|
-
error: true,
|
|
701
|
-
message: "Selected element is not a PlantUML macro",
|
|
702
|
-
statusCode: 400,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
// Update the macro content
|
|
706
|
-
const updated = updatePlantUmlInMacro(macroElement, normalizedCode);
|
|
707
|
-
if (!updated) {
|
|
708
|
-
return formatError({
|
|
709
|
-
error: true,
|
|
710
|
-
message: "Failed to update PlantUML macro content",
|
|
711
|
-
statusCode: 500,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
// Serialize and update buffer
|
|
715
|
-
const newContent = serializeXhtml(parseResult.document);
|
|
716
|
-
// Validate if requested
|
|
717
|
-
if (args.validate !== false) {
|
|
718
|
-
const validationResult = validateXhtml(newContent);
|
|
719
|
-
if (!validationResult.valid) {
|
|
720
|
-
const firstError = validationResult.errors[0];
|
|
600
|
+
if (!result.success) {
|
|
721
601
|
return formatError({
|
|
722
602
|
error: true,
|
|
723
|
-
message: `
|
|
603
|
+
message: `Operation ${opIndex + 1} failed: ${result.error || "Unknown error"}`,
|
|
724
604
|
statusCode: 400,
|
|
725
605
|
details: {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
606
|
+
operationIndex: opIndex,
|
|
607
|
+
completedOperations: opIndex,
|
|
608
|
+
operation: op,
|
|
609
|
+
structure: bufferInfo.structure,
|
|
610
|
+
nextId: bufferInfo.nextId,
|
|
730
611
|
},
|
|
731
612
|
});
|
|
732
613
|
}
|
|
614
|
+
// Collect inserted IDs
|
|
615
|
+
if (result.insertedIds) {
|
|
616
|
+
allInsertedIds.push(...result.insertedIds);
|
|
617
|
+
}
|
|
733
618
|
}
|
|
734
|
-
//
|
|
735
|
-
|
|
619
|
+
// Serialize and update buffer once (after all operations)
|
|
620
|
+
const newContent = serializeXhtml(parseResult.document);
|
|
621
|
+
// Rebuild structure
|
|
622
|
+
const newStructure = parseStructure(parseResult.document);
|
|
623
|
+
// Update buffer
|
|
624
|
+
contentBuffer.update(args.bufferId, newContent, { contentType: "xhtml" }, newStructure.structure, newStructure.nextId);
|
|
736
625
|
return formatSuccess({
|
|
737
626
|
bufferId: args.bufferId,
|
|
738
627
|
success: true,
|
|
739
|
-
|
|
740
|
-
matchCount: 1,
|
|
628
|
+
operationsCompleted: operations.length,
|
|
741
629
|
oldSize,
|
|
742
630
|
newSize: newContent.length,
|
|
743
|
-
|
|
744
|
-
|
|
631
|
+
structure: newStructure.structure,
|
|
632
|
+
nextId: newStructure.nextId,
|
|
633
|
+
...(allInsertedIds.length > 0 && { insertedIds: allInsertedIds }),
|
|
634
|
+
...(diagramTypes.length > 0 && { diagramTypes }),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
// Plain text/JSON editing
|
|
638
|
+
if (args.old_string === undefined || args.new_string === undefined) {
|
|
639
|
+
return formatError({
|
|
640
|
+
error: true,
|
|
641
|
+
message: "old_string and new_string required for plain/json buffer editing",
|
|
642
|
+
statusCode: 400,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const result = contentBuffer.edit(args.bufferId, args.old_string, args.new_string, args.replace_all ?? false);
|
|
646
|
+
if (!result) {
|
|
647
|
+
return formatError({
|
|
648
|
+
error: true,
|
|
649
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
650
|
+
statusCode: 404,
|
|
745
651
|
});
|
|
746
652
|
}
|
|
747
|
-
// Execute the operation
|
|
748
|
-
const result = executeOperation(parseResult.document, operation, resolvedSelector, {
|
|
749
|
-
position: resolvedPosition,
|
|
750
|
-
content: contentToInsert,
|
|
751
|
-
attributes: args.attributes,
|
|
752
|
-
targetSelector: args.targetSelector,
|
|
753
|
-
matchIndex: args.matchIndex,
|
|
754
|
-
matchAll: args.matchAll,
|
|
755
|
-
});
|
|
756
653
|
if ("error" in result && result.error) {
|
|
757
654
|
return formatError({
|
|
758
655
|
error: true,
|
|
759
656
|
message: result.message,
|
|
760
657
|
statusCode: 400,
|
|
761
|
-
details: result.
|
|
658
|
+
details: { occurrences: result.occurrences },
|
|
762
659
|
});
|
|
763
660
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}
|
|
661
|
+
return formatSuccess(result);
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
buffer_get_structure: {
|
|
669
|
+
description: `Get current element structure for an XHTML buffer. Returns element IDs for use with buffer_edit.
|
|
670
|
+
|
|
671
|
+
Each element has:
|
|
672
|
+
- id: Unique ID (stable during session)
|
|
673
|
+
- type: Element type ("h1", "h2", "p", "plantuml", "ul", etc.)
|
|
674
|
+
- text: Preview text (truncated)
|
|
675
|
+
- children: Child count for lists/tables`,
|
|
676
|
+
inputSchema: z.object({
|
|
677
|
+
bufferId: z.string().describe("Buffer ID to get structure for"),
|
|
678
|
+
}),
|
|
679
|
+
handler: async (args) => {
|
|
680
|
+
try {
|
|
681
|
+
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
682
|
+
if (!bufferInfo) {
|
|
683
|
+
return formatError({
|
|
684
|
+
error: true,
|
|
685
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
686
|
+
statusCode: 404,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
if (bufferInfo.metadata?.contentType !== "xhtml") {
|
|
690
|
+
return formatError({
|
|
691
|
+
error: true,
|
|
692
|
+
message: "Buffer is not XHTML content",
|
|
693
|
+
statusCode: 400,
|
|
694
|
+
details: {
|
|
695
|
+
contentType: bufferInfo.metadata?.contentType,
|
|
696
|
+
hint: "Structure is only available for XHTML buffers",
|
|
697
|
+
},
|
|
698
|
+
});
|
|
791
699
|
}
|
|
792
|
-
// Update buffer in place (keeps same buffer ID)
|
|
793
|
-
contentBuffer.update(args.bufferId, newContent, { contentType: "xhtml" });
|
|
794
|
-
// Check if buffer is associated with a Confluence draft and add nextStep hint
|
|
795
|
-
const updatedBufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
796
|
-
const isDraft = updatedBufferInfo?.metadata?.isDraft === true;
|
|
797
|
-
const isConfluencePage = updatedBufferInfo?.metadata?.resourceType === "confluence_page";
|
|
798
700
|
return formatSuccess({
|
|
799
|
-
...result,
|
|
800
701
|
bufferId: args.bufferId,
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
702
|
+
structure: bufferInfo.structure ?? [],
|
|
703
|
+
nextId: bufferInfo.nextId ?? 1,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
buffer_get_element: {
|
|
712
|
+
description: `Get the raw XHTML content of a specific element by ID.
|
|
713
|
+
|
|
714
|
+
Use this to inspect problematic elements when fixing XHTML parsing errors.
|
|
715
|
+
Returns the element's outerHTML, type, and line count.
|
|
716
|
+
|
|
717
|
+
Example: After a Confluence error at element 12, use this to see the content:
|
|
718
|
+
buffer_get_element(bufferId="buf_xxx", elementId=12)`,
|
|
719
|
+
inputSchema: z.object({
|
|
720
|
+
bufferId: z.string().describe("Buffer ID containing XHTML content"),
|
|
721
|
+
elementId: z.number().describe("Element ID from buffer structure"),
|
|
722
|
+
}),
|
|
723
|
+
handler: async (args) => {
|
|
724
|
+
try {
|
|
725
|
+
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
726
|
+
if (!bufferInfo) {
|
|
727
|
+
return formatError({
|
|
728
|
+
error: true,
|
|
729
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
730
|
+
statusCode: 404,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (bufferInfo.metadata?.contentType !== "xhtml") {
|
|
734
|
+
return formatError({
|
|
735
|
+
error: true,
|
|
736
|
+
message: "Buffer is not XHTML content",
|
|
737
|
+
statusCode: 400,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
// Get full buffer content
|
|
741
|
+
const chunk = contentBuffer.getChunk(args.bufferId, 0, bufferInfo.totalSize);
|
|
742
|
+
if (!chunk) {
|
|
743
|
+
return formatError({
|
|
744
|
+
error: true,
|
|
745
|
+
message: `Failed to read buffer: ${args.bufferId}`,
|
|
746
|
+
statusCode: 500,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
// Parse and find element
|
|
750
|
+
const parseResult = parseXhtml(chunk.chunk);
|
|
751
|
+
if (!parseResult.document) {
|
|
752
|
+
return formatError({
|
|
753
|
+
error: true,
|
|
754
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
755
|
+
statusCode: 400,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const root = parseResult.document.querySelector("xhtml-root");
|
|
759
|
+
if (!root) {
|
|
760
|
+
return formatError({
|
|
761
|
+
error: true,
|
|
762
|
+
message: "XHTML has no content root",
|
|
763
|
+
statusCode: 400,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const element = root.querySelector(`[data-jicon-id="${args.elementId}"]`);
|
|
767
|
+
if (!element) {
|
|
768
|
+
return formatError({
|
|
769
|
+
error: true,
|
|
770
|
+
message: `Element not found: ${args.elementId}`,
|
|
771
|
+
statusCode: 404,
|
|
772
|
+
details: {
|
|
773
|
+
availableIds: bufferInfo.structure?.map((e) => e.id) ?? [],
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const content = element.outerHTML;
|
|
778
|
+
const lineCount = (content.match(/\n/g) || []).length + 1;
|
|
779
|
+
// Determine element type
|
|
780
|
+
let elementType = element.tagName.toLowerCase();
|
|
781
|
+
if (elementType === "ac:structured-macro") {
|
|
782
|
+
elementType = element.getAttribute("ac:name") || "macro";
|
|
783
|
+
}
|
|
784
|
+
return formatSuccess({
|
|
785
|
+
bufferId: args.bufferId,
|
|
786
|
+
elementId: args.elementId,
|
|
787
|
+
elementType,
|
|
788
|
+
lineCount,
|
|
789
|
+
content,
|
|
807
790
|
});
|
|
808
791
|
}
|
|
809
792
|
catch (error) {
|
|
@@ -853,10 +836,24 @@ Use this to validate content before calling confluence_update_page or confluence
|
|
|
853
836
|
statusCode: 500,
|
|
854
837
|
});
|
|
855
838
|
}
|
|
839
|
+
// Check for raw PlantUML (same check as confluence_draft_create)
|
|
840
|
+
// This ensures validation and draft creation agree on what's valid
|
|
841
|
+
const rawPlantUml = detectRawPlantUml(chunk.chunk);
|
|
856
842
|
// Validate XHTML with async PlantUML validation
|
|
857
843
|
const validationResult = await validateXhtmlAsync(chunk.chunk, {
|
|
858
844
|
validatePlantUml: args.validatePlantUml !== false,
|
|
859
845
|
});
|
|
846
|
+
// Add raw PlantUML error if detected
|
|
847
|
+
if (rawPlantUml) {
|
|
848
|
+
validationResult.errors.push({
|
|
849
|
+
type: "raw_plantuml",
|
|
850
|
+
message: "Content contains raw PlantUML code that is not wrapped in a Confluence macro.",
|
|
851
|
+
location: {
|
|
852
|
+
context: rawPlantUml.hint,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
validationResult.valid = false;
|
|
856
|
+
}
|
|
860
857
|
return formatSuccess({
|
|
861
858
|
bufferId: args.bufferId,
|
|
862
859
|
valid: validationResult.valid,
|