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