@softspark/confluence-mcp 1.12.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/LICENSE +201 -0
- package/NOTICE +59 -0
- package/README.md +166 -0
- package/dist/cli.js +101 -0
- package/dist/index.d.ts +568 -0
- package/dist/index.js +88 -0
- package/package.json +66 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { ConfluenceSpaceInstanceConfig, AdfDocument, ConfluenceConfig, ToolResult, ToolDefinition } from '@softspark/atlassian-mcp-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Project-owned Confluence types.
|
|
6
|
+
*
|
|
7
|
+
* These are the lean shapes the connector returns. Raw REST response shapes
|
|
8
|
+
* stay private to {@link ../confluence/connector.ts} so the rest of the code
|
|
9
|
+
* never depends on Atlassian's field names or on which API version served a
|
|
10
|
+
* given call.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/** A Confluence space as returned by the v2 spaces endpoint. */
|
|
15
|
+
interface ConfluenceSpace {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly key: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
/** `global` or `personal`. */
|
|
20
|
+
readonly type: string;
|
|
21
|
+
readonly homepageId?: string;
|
|
22
|
+
readonly url?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Lightweight page record, used by search and listing calls. */
|
|
25
|
+
interface ConfluencePage {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly title: string;
|
|
28
|
+
readonly spaceId?: string;
|
|
29
|
+
readonly spaceKey?: string;
|
|
30
|
+
readonly parentId?: string;
|
|
31
|
+
readonly status: string;
|
|
32
|
+
readonly version: number;
|
|
33
|
+
readonly url?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Result of creating or updating a page. */
|
|
36
|
+
interface PageWriteResult {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly title: string;
|
|
39
|
+
readonly version: number;
|
|
40
|
+
readonly url?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A blog post.
|
|
44
|
+
*
|
|
45
|
+
* Structurally a page without a parent: blog posts live directly in a space,
|
|
46
|
+
* are ordered by date rather than by tree position, and cannot be re-parented.
|
|
47
|
+
*/
|
|
48
|
+
interface ConfluenceBlogPost {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly title: string;
|
|
51
|
+
readonly spaceId?: string;
|
|
52
|
+
readonly status: string;
|
|
53
|
+
readonly version: number;
|
|
54
|
+
readonly authorId?: string;
|
|
55
|
+
readonly createdAt?: string;
|
|
56
|
+
readonly url?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A whiteboard.
|
|
60
|
+
*
|
|
61
|
+
* Only the container is reachable over the API. Whiteboard content is a
|
|
62
|
+
* collaborative binary document with no REST representation, so these records
|
|
63
|
+
* carry a title and a position in the tree and nothing else.
|
|
64
|
+
*/
|
|
65
|
+
interface ConfluenceWhiteboard {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly title: string;
|
|
68
|
+
readonly spaceId?: string;
|
|
69
|
+
readonly parentId?: string;
|
|
70
|
+
readonly url?: string;
|
|
71
|
+
}
|
|
72
|
+
/** A user named in a content restriction. */
|
|
73
|
+
interface RestrictionUser {
|
|
74
|
+
readonly accountId: string;
|
|
75
|
+
readonly displayName?: string;
|
|
76
|
+
}
|
|
77
|
+
/** A group named in a content restriction. */
|
|
78
|
+
interface RestrictionGroup {
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
readonly name?: string;
|
|
81
|
+
}
|
|
82
|
+
/** Who may perform one operation on a page. */
|
|
83
|
+
interface RestrictionSet {
|
|
84
|
+
readonly users: readonly RestrictionUser[];
|
|
85
|
+
readonly groups: readonly RestrictionGroup[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Page restrictions, by operation.
|
|
89
|
+
*
|
|
90
|
+
* Empty sets mean the page inherits space permissions, which is not the same
|
|
91
|
+
* as "nobody may read it".
|
|
92
|
+
*/
|
|
93
|
+
interface ConfluenceRestrictions {
|
|
94
|
+
readonly read: RestrictionSet;
|
|
95
|
+
readonly update: RestrictionSet;
|
|
96
|
+
}
|
|
97
|
+
/** A footer comment on a page, body already converted to markdown. */
|
|
98
|
+
interface ConfluenceComment {
|
|
99
|
+
readonly id: string;
|
|
100
|
+
readonly pageId?: string;
|
|
101
|
+
readonly parentCommentId?: string;
|
|
102
|
+
readonly version: number;
|
|
103
|
+
readonly body: string;
|
|
104
|
+
readonly authorId?: string;
|
|
105
|
+
readonly url?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* An inline comment, anchored to highlighted text in the page body.
|
|
109
|
+
*
|
|
110
|
+
* `textSelection` is the highlighted passage. `resolutionStatus` is `dangling`
|
|
111
|
+
* when the anchored text no longer exists after an edit, which is how review
|
|
112
|
+
* threads get orphaned.
|
|
113
|
+
*/
|
|
114
|
+
interface ConfluenceInlineComment extends ConfluenceComment {
|
|
115
|
+
readonly resolutionStatus?: string;
|
|
116
|
+
readonly textSelection?: string;
|
|
117
|
+
}
|
|
118
|
+
/** A label attached to a page. */
|
|
119
|
+
interface ConfluenceLabel {
|
|
120
|
+
readonly id: string;
|
|
121
|
+
readonly name: string;
|
|
122
|
+
readonly prefix?: string;
|
|
123
|
+
}
|
|
124
|
+
/** Attachment metadata. Binary content is never inlined into a tool result. */
|
|
125
|
+
interface ConfluenceAttachment {
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly title: string;
|
|
128
|
+
readonly mediaType?: string;
|
|
129
|
+
readonly fileSize?: number;
|
|
130
|
+
readonly version: number;
|
|
131
|
+
/** Absolute URL for downloading the attachment. */
|
|
132
|
+
readonly downloadUrl?: string;
|
|
133
|
+
}
|
|
134
|
+
/** One hit from a CQL search, with the excerpt Confluence generated. */
|
|
135
|
+
interface ConfluenceSearchHit {
|
|
136
|
+
readonly id: string;
|
|
137
|
+
readonly title: string;
|
|
138
|
+
readonly type: string;
|
|
139
|
+
readonly spaceKey?: string;
|
|
140
|
+
readonly excerpt?: string;
|
|
141
|
+
readonly lastModified?: string;
|
|
142
|
+
readonly url?: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Body representation used for markdown-backed reads and writes. */
|
|
146
|
+
declare const ADF = "atlas_doc_format";
|
|
147
|
+
/**
|
|
148
|
+
* Confluence's own storage format: XHTML carrying macros, layouts, page links
|
|
149
|
+
* and attachment references. Anything authored in the Confluence editor, or
|
|
150
|
+
* by a pages-as-code pipeline, is stored this way.
|
|
151
|
+
*/
|
|
152
|
+
declare const STORAGE = "storage";
|
|
153
|
+
/** The two body representations this connector reads and writes. */
|
|
154
|
+
type BodyFormat = typeof ADF | typeof STORAGE;
|
|
155
|
+
declare class ConfluenceConnector {
|
|
156
|
+
private readonly http;
|
|
157
|
+
readonly instanceUrl: string;
|
|
158
|
+
/**
|
|
159
|
+
* Space key to numeric space id, resolved lazily.
|
|
160
|
+
*
|
|
161
|
+
* The v2 API addresses spaces by id while humans and config use the key, so
|
|
162
|
+
* every write needs one extra lookup. Space ids never change, so caching
|
|
163
|
+
* them for the process lifetime is safe.
|
|
164
|
+
*/
|
|
165
|
+
private readonly spaceIdByKey;
|
|
166
|
+
constructor(config: ConfluenceSpaceInstanceConfig);
|
|
167
|
+
/**
|
|
168
|
+
* Send an authenticated request to the Confluence REST API.
|
|
169
|
+
*
|
|
170
|
+
* Transport concerns live in {@link AtlassianHttpClient}, shared with the
|
|
171
|
+
* Jira connector. Status-to-error mapping stays in
|
|
172
|
+
* {@link mapConfluenceError}.
|
|
173
|
+
*/
|
|
174
|
+
private request;
|
|
175
|
+
/**
|
|
176
|
+
* Follow `_links.next` until `limit` items are collected.
|
|
177
|
+
*
|
|
178
|
+
* v2 paginates with an opaque cursor rather than an offset, so the only way
|
|
179
|
+
* to reach page two is to follow the link the server hands back.
|
|
180
|
+
*/
|
|
181
|
+
private collect;
|
|
182
|
+
/**
|
|
183
|
+
* Resolve a Confluence link against the site's `/wiki` context path.
|
|
184
|
+
*
|
|
185
|
+
* The API returns links like `/spaces/DOCS/pages/77`, rooted at the wiki
|
|
186
|
+
* context rather than at the host. Handing that to `new URL` as-is would
|
|
187
|
+
* resolve it against the origin and silently drop `/wiki`, producing a link
|
|
188
|
+
* that 404s in the browser. The leading slash is stripped so the base's
|
|
189
|
+
* path segment survives.
|
|
190
|
+
*/
|
|
191
|
+
private wikiUrl;
|
|
192
|
+
/** Turn a relative `_links.webui` into an absolute browser URL. */
|
|
193
|
+
private absoluteUrl;
|
|
194
|
+
/**
|
|
195
|
+
* Extract the ADF document from a response body.
|
|
196
|
+
*
|
|
197
|
+
* Returns `null` when the page carries no ADF body, which happens for
|
|
198
|
+
* content authored through legacy storage-format-only routes. Callers hand
|
|
199
|
+
* `null` to `adfToMarkdown`, which already renders it as "(No content)".
|
|
200
|
+
*/
|
|
201
|
+
private static extractAdf;
|
|
202
|
+
/** Wrap an ADF document into the write shape the API expects. */
|
|
203
|
+
private static adfBody;
|
|
204
|
+
/** Wrap a raw storage-format body into the write shape the API expects. */
|
|
205
|
+
private static storageBody;
|
|
206
|
+
/**
|
|
207
|
+
* Pick the write body from whichever representation the caller supplied.
|
|
208
|
+
*
|
|
209
|
+
* Exactly one must be present. Storage wins when both are, because storage
|
|
210
|
+
* is the lossless one and silently preferring the lossy side is how content
|
|
211
|
+
* gets destroyed.
|
|
212
|
+
*/
|
|
213
|
+
private static writeBody;
|
|
214
|
+
/** List spaces visible to the account. */
|
|
215
|
+
listSpaces(limit?: number): Promise<ConfluenceSpace[]>;
|
|
216
|
+
/**
|
|
217
|
+
* Resolve a space key to its full record.
|
|
218
|
+
*
|
|
219
|
+
* @throws {PageNotFoundError} If no visible space carries that key.
|
|
220
|
+
*/
|
|
221
|
+
getSpaceByKey(spaceKey: string): Promise<ConfluenceSpace>;
|
|
222
|
+
/** Resolve and memoise a space key to its numeric id. */
|
|
223
|
+
resolveSpaceId(spaceKey: string): Promise<string>;
|
|
224
|
+
private mapSpace;
|
|
225
|
+
/**
|
|
226
|
+
* Full-text search via CQL.
|
|
227
|
+
*
|
|
228
|
+
* Uses v1 `/wiki/rest/api/search`; v2 exposes no search endpoint.
|
|
229
|
+
*/
|
|
230
|
+
searchPages(cql: string, limit?: number): Promise<ConfluenceSearchHit[]>;
|
|
231
|
+
/**
|
|
232
|
+
* Fetch one page.
|
|
233
|
+
*
|
|
234
|
+
* `format` selects the body representation the API returns. Ask for
|
|
235
|
+
* `storage` when the body is going to be written back: storage is what
|
|
236
|
+
* Confluence actually stores, and it is the only representation in which
|
|
237
|
+
* macros, layouts, page links and attachment references survive a
|
|
238
|
+
* read-modify-write intact.
|
|
239
|
+
*/
|
|
240
|
+
getPage(pageId: string, format?: BodyFormat): Promise<{
|
|
241
|
+
readonly page: ConfluencePage;
|
|
242
|
+
readonly adf: AdfDocument | null;
|
|
243
|
+
readonly storage: string | null;
|
|
244
|
+
readonly authorId?: string;
|
|
245
|
+
readonly createdAt?: string;
|
|
246
|
+
}>;
|
|
247
|
+
/** List direct children of a page, for walking the page tree. */
|
|
248
|
+
getChildPages(pageId: string, limit?: number): Promise<ConfluencePage[]>;
|
|
249
|
+
/** List pages in a space, addressed by space id. */
|
|
250
|
+
getSpacePages(spaceId: string, limit?: number): Promise<ConfluencePage[]>;
|
|
251
|
+
private mapPage;
|
|
252
|
+
/** Create a page from an ADF document or a raw storage body. */
|
|
253
|
+
createPage(input: {
|
|
254
|
+
readonly spaceId: string;
|
|
255
|
+
readonly title: string;
|
|
256
|
+
readonly adf?: AdfDocument;
|
|
257
|
+
readonly storage?: string;
|
|
258
|
+
readonly parentId?: string;
|
|
259
|
+
readonly status?: 'current' | 'draft';
|
|
260
|
+
}): Promise<PageWriteResult>;
|
|
261
|
+
/**
|
|
262
|
+
* Update a page.
|
|
263
|
+
*
|
|
264
|
+
* `version` must be the current version number; the API is given
|
|
265
|
+
* `version + 1`. Passing a stale number is what raises
|
|
266
|
+
* {@link VersionConflictError} rather than silently overwriting.
|
|
267
|
+
*/
|
|
268
|
+
updatePage(input: {
|
|
269
|
+
readonly pageId: string;
|
|
270
|
+
readonly title: string;
|
|
271
|
+
readonly adf?: AdfDocument;
|
|
272
|
+
readonly storage?: string;
|
|
273
|
+
readonly version: number;
|
|
274
|
+
readonly parentId?: string;
|
|
275
|
+
readonly versionMessage?: string;
|
|
276
|
+
readonly status?: 'current' | 'draft';
|
|
277
|
+
}): Promise<PageWriteResult>;
|
|
278
|
+
/** Move a page to a different parent within the same space. */
|
|
279
|
+
movePage(input: {
|
|
280
|
+
readonly pageId: string;
|
|
281
|
+
readonly title: string;
|
|
282
|
+
readonly adf?: AdfDocument;
|
|
283
|
+
readonly storage?: string;
|
|
284
|
+
readonly version: number;
|
|
285
|
+
readonly parentId: string;
|
|
286
|
+
}): Promise<PageWriteResult>;
|
|
287
|
+
/**
|
|
288
|
+
* Move a page relative to a target page, across spaces if needed.
|
|
289
|
+
*
|
|
290
|
+
* The v2 update endpoint cannot change a page's space. This v1 endpoint
|
|
291
|
+
* can, because it moves the page in the content tree rather than editing
|
|
292
|
+
* its fields, and the space follows the new parent.
|
|
293
|
+
*
|
|
294
|
+
* `position` is `append` (become a child of the target), `before` or
|
|
295
|
+
* `after` (become a sibling of the target). Never use `before`/`after`
|
|
296
|
+
* against a top-level page: the result does not appear in the page tree.
|
|
297
|
+
*/
|
|
298
|
+
movePageToTarget(pageId: string, position: 'append' | 'before' | 'after', targetId: string): Promise<{
|
|
299
|
+
readonly pageId: string;
|
|
300
|
+
}>;
|
|
301
|
+
/** Move a page to the trash. */
|
|
302
|
+
deletePage(pageId: string): Promise<void>;
|
|
303
|
+
/** List blog posts in a space, newest first as the API orders them. */
|
|
304
|
+
getSpaceBlogPosts(spaceId: string, limit?: number): Promise<ConfluenceBlogPost[]>;
|
|
305
|
+
/** Fetch one blog post with its ADF body. */
|
|
306
|
+
getBlogPost(blogPostId: string): Promise<{
|
|
307
|
+
readonly post: ConfluenceBlogPost;
|
|
308
|
+
readonly adf: AdfDocument | null;
|
|
309
|
+
}>;
|
|
310
|
+
/** Create a blog post from an ADF document. */
|
|
311
|
+
createBlogPost(input: {
|
|
312
|
+
readonly spaceId: string;
|
|
313
|
+
readonly title: string;
|
|
314
|
+
readonly adf: AdfDocument;
|
|
315
|
+
readonly status?: 'current' | 'draft';
|
|
316
|
+
}): Promise<PageWriteResult>;
|
|
317
|
+
/** Update a blog post. Same version contract as {@link updatePage}. */
|
|
318
|
+
updateBlogPost(input: {
|
|
319
|
+
readonly blogPostId: string;
|
|
320
|
+
readonly title: string;
|
|
321
|
+
readonly adf: AdfDocument;
|
|
322
|
+
readonly version: number;
|
|
323
|
+
readonly versionMessage?: string;
|
|
324
|
+
}): Promise<PageWriteResult>;
|
|
325
|
+
/** Move a blog post to the trash. */
|
|
326
|
+
deleteBlogPost(blogPostId: string): Promise<void>;
|
|
327
|
+
private mapBlogPost;
|
|
328
|
+
/** Fetch whiteboard metadata. Whiteboard content has no REST representation. */
|
|
329
|
+
getWhiteboard(whiteboardId: string): Promise<ConfluenceWhiteboard>;
|
|
330
|
+
/** Create an empty whiteboard. There is no way to seed its content. */
|
|
331
|
+
createWhiteboard(input: {
|
|
332
|
+
readonly spaceId: string;
|
|
333
|
+
readonly title?: string;
|
|
334
|
+
readonly parentId?: string;
|
|
335
|
+
}): Promise<ConfluenceWhiteboard>;
|
|
336
|
+
/** Move a whiteboard to the trash. */
|
|
337
|
+
deleteWhiteboard(whiteboardId: string): Promise<void>;
|
|
338
|
+
private mapWhiteboard;
|
|
339
|
+
private mapWriteResult;
|
|
340
|
+
/** Read root footer comments on a page. */
|
|
341
|
+
getFooterComments(pageId: string, limit?: number): Promise<{
|
|
342
|
+
readonly comment: ConfluenceComment;
|
|
343
|
+
readonly adf: AdfDocument | null;
|
|
344
|
+
}[]>;
|
|
345
|
+
/** Add a footer comment, optionally as a reply to another comment. */
|
|
346
|
+
addFooterComment(input: {
|
|
347
|
+
readonly pageId: string;
|
|
348
|
+
readonly adf: AdfDocument;
|
|
349
|
+
readonly parentCommentId?: string;
|
|
350
|
+
}): Promise<ConfluenceComment>;
|
|
351
|
+
/** Delete a footer comment. */
|
|
352
|
+
deleteFooterComment(commentId: string): Promise<void>;
|
|
353
|
+
private mapComment;
|
|
354
|
+
/**
|
|
355
|
+
* Read inline comments on a page.
|
|
356
|
+
*
|
|
357
|
+
* These are the review threads anchored to highlighted text, distinct from
|
|
358
|
+
* the footer comments at the bottom of the page.
|
|
359
|
+
*/
|
|
360
|
+
getInlineComments(pageId: string, limit?: number): Promise<{
|
|
361
|
+
readonly comment: ConfluenceInlineComment;
|
|
362
|
+
readonly adf: AdfDocument | null;
|
|
363
|
+
}[]>;
|
|
364
|
+
/**
|
|
365
|
+
* Add an inline comment anchored to a passage, or reply to an existing one.
|
|
366
|
+
*
|
|
367
|
+
* A top-level inline comment must say which text it highlights, and how
|
|
368
|
+
* many times that text occurs, so Confluence can anchor it to the right
|
|
369
|
+
* occurrence. A reply must not carry those properties.
|
|
370
|
+
*/
|
|
371
|
+
addInlineComment(input: {
|
|
372
|
+
readonly pageId: string;
|
|
373
|
+
readonly adf: AdfDocument;
|
|
374
|
+
readonly textSelection?: string;
|
|
375
|
+
readonly matchIndex?: number;
|
|
376
|
+
readonly matchCount?: number;
|
|
377
|
+
readonly parentCommentId?: string;
|
|
378
|
+
}): Promise<ConfluenceInlineComment>;
|
|
379
|
+
private mapInlineComment;
|
|
380
|
+
/**
|
|
381
|
+
* Read who may view and edit a page.
|
|
382
|
+
*
|
|
383
|
+
* Restrictions live only on v1. Empty sets mean the page inherits space
|
|
384
|
+
* permissions, which is not the same as nobody having access.
|
|
385
|
+
*/
|
|
386
|
+
getRestrictions(pageId: string): Promise<ConfluenceRestrictions>;
|
|
387
|
+
/**
|
|
388
|
+
* Replace the read and update restrictions on a page.
|
|
389
|
+
*
|
|
390
|
+
* This is a full replacement, not a merge: whatever is not listed loses
|
|
391
|
+
* access. Passing no users and no groups for both operations removes the
|
|
392
|
+
* restrictions entirely and the page falls back to space permissions.
|
|
393
|
+
*/
|
|
394
|
+
setRestrictions(pageId: string, input: {
|
|
395
|
+
readonly readAccountIds: readonly string[];
|
|
396
|
+
readonly readGroupIds: readonly string[];
|
|
397
|
+
readonly updateAccountIds: readonly string[];
|
|
398
|
+
readonly updateGroupIds: readonly string[];
|
|
399
|
+
}): Promise<ConfluenceRestrictions>;
|
|
400
|
+
private static restrictionUpdate;
|
|
401
|
+
private static mapRestrictionSet;
|
|
402
|
+
/** Read the labels on a page. */
|
|
403
|
+
getLabels(pageId: string, limit?: number): Promise<ConfluenceLabel[]>;
|
|
404
|
+
/**
|
|
405
|
+
* Add labels to a page.
|
|
406
|
+
*
|
|
407
|
+
* Label writes live only on v1: v2 exposes labels read-only.
|
|
408
|
+
*/
|
|
409
|
+
addLabels(pageId: string, labels: readonly string[]): Promise<ConfluenceLabel[]>;
|
|
410
|
+
/** Remove one label from a page. */
|
|
411
|
+
removeLabel(pageId: string, name: string): Promise<void>;
|
|
412
|
+
private static mapLabel;
|
|
413
|
+
/** List attachment metadata for a page. Binary content is never fetched. */
|
|
414
|
+
listAttachments(pageId: string, limit?: number): Promise<ConfluenceAttachment[]>;
|
|
415
|
+
/**
|
|
416
|
+
* Upload a file as a page attachment.
|
|
417
|
+
*
|
|
418
|
+
* Multipart upload exists only on v1, and Confluence rejects it without the
|
|
419
|
+
* `X-Atlassian-Token: no-check` header (its XSRF guard). `allowDuplicate`
|
|
420
|
+
* maps to the v1 create-or-update endpoint so re-uploading the same name
|
|
421
|
+
* adds a version instead of failing.
|
|
422
|
+
*/
|
|
423
|
+
uploadAttachment(input: {
|
|
424
|
+
readonly pageId: string;
|
|
425
|
+
readonly filename: string;
|
|
426
|
+
/** File bytes. Build with `Uint8Array.from(buffer)` to satisfy Blob's type. */
|
|
427
|
+
readonly content: Uint8Array<ArrayBuffer>;
|
|
428
|
+
readonly mediaType?: string;
|
|
429
|
+
readonly comment?: string;
|
|
430
|
+
}): Promise<ConfluenceAttachment>;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Pool of ConfluenceConnector instances keyed by site URL.
|
|
435
|
+
*
|
|
436
|
+
* Mirrors {@link ../connector/instance-pool.ts} with one deliberate
|
|
437
|
+
* difference: Jira routes by parsing the project key out of an issue key
|
|
438
|
+
* (`PROJ-123` → `PROJ`), but Confluence page ids are opaque numbers that
|
|
439
|
+
* carry no space information. A page id alone therefore cannot pick a
|
|
440
|
+
* connector, so callers either name a space or fall back to the default
|
|
441
|
+
* space. On a single-site setup that fallback is exact; on a multi-site
|
|
442
|
+
* setup naming the space is what keeps the call unambiguous.
|
|
443
|
+
*
|
|
444
|
+
* @module
|
|
445
|
+
*/
|
|
446
|
+
|
|
447
|
+
/** Metadata about a single Confluence site in the pool. */
|
|
448
|
+
interface PooledSite {
|
|
449
|
+
readonly connector: ConfluenceConnector;
|
|
450
|
+
readonly spaceKeys: readonly string[];
|
|
451
|
+
}
|
|
452
|
+
declare class ConfluenceInstancePool {
|
|
453
|
+
#private;
|
|
454
|
+
/** Connectors keyed by space key for fast lookup. */
|
|
455
|
+
private readonly bySpace;
|
|
456
|
+
/** Deduplicated sites keyed by URL. */
|
|
457
|
+
private readonly byUrl;
|
|
458
|
+
private readonly config;
|
|
459
|
+
constructor(config: ConfluenceConfig);
|
|
460
|
+
/**
|
|
461
|
+
* Get the connector for a specific space key.
|
|
462
|
+
*
|
|
463
|
+
* @throws {ConfigValidationError} If the space key is not configured.
|
|
464
|
+
*/
|
|
465
|
+
getConnector(spaceKey: string): ConfluenceConnector;
|
|
466
|
+
/**
|
|
467
|
+
* Resolve the space key a call should use.
|
|
468
|
+
*
|
|
469
|
+
* Order: explicit argument, then `default_space`, then the only configured
|
|
470
|
+
* space. With several spaces and no default, an unnamed call is an error
|
|
471
|
+
* rather than a guess, because writing a page into the wrong space is not
|
|
472
|
+
* something the caller can see before it happens.
|
|
473
|
+
*
|
|
474
|
+
* @throws {ConfigValidationError} If nothing resolves.
|
|
475
|
+
*/
|
|
476
|
+
resolveSpaceKey(spaceKey?: string): string;
|
|
477
|
+
/** Get the connector for a call, applying {@link resolveSpaceKey} first. */
|
|
478
|
+
getConnectorForSpace(spaceKey?: string): ConfluenceConnector;
|
|
479
|
+
/** Get all unique sites with their associated space keys. */
|
|
480
|
+
getSites(): ReadonlyMap<string, PooledSite>;
|
|
481
|
+
/** Every configured space key, in configuration order. */
|
|
482
|
+
getSpaceKeys(): readonly string[];
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Shared utilities for Confluence MCP tool handlers.
|
|
487
|
+
*
|
|
488
|
+
* Response formatting is reused from the Jira tool helpers so both servers
|
|
489
|
+
* emit the same `{ success, ... }` envelope and the same error codes.
|
|
490
|
+
*
|
|
491
|
+
* @module
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/** Dependencies every Confluence tool handler receives. */
|
|
495
|
+
interface ConfluenceDeps {
|
|
496
|
+
readonly pool: ConfluenceInstancePool;
|
|
497
|
+
readonly config: ConfluenceConfig;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Tool handlers: list_spaces, get_space_language
|
|
502
|
+
*
|
|
503
|
+
* @module
|
|
504
|
+
*/
|
|
505
|
+
|
|
506
|
+
interface ListSpacesArgs {
|
|
507
|
+
readonly space_key?: string;
|
|
508
|
+
readonly limit?: number;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* List Confluence spaces.
|
|
512
|
+
*
|
|
513
|
+
* Reports both what the site exposes and which keys are configured locally,
|
|
514
|
+
* because only configured keys can be used to route a write.
|
|
515
|
+
*/
|
|
516
|
+
declare function handleListSpaces(args: ListSpacesArgs, deps: ConfluenceDeps): Promise<ToolResult>;
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* MCP tool definitions for the Confluence MCP server.
|
|
520
|
+
*
|
|
521
|
+
* Names are deliberately page/space-shaped (`get_page`, not `get_content`)
|
|
522
|
+
* so that a client running both servers side by side never has two tools
|
|
523
|
+
* whose names could plausibly describe the same call.
|
|
524
|
+
*
|
|
525
|
+
* `space_key` is optional everywhere: it falls back to `default_space`, and
|
|
526
|
+
* then to the single configured space. It becomes required only when several
|
|
527
|
+
* spaces are configured without a default.
|
|
528
|
+
*
|
|
529
|
+
* @module
|
|
530
|
+
*/
|
|
531
|
+
|
|
532
|
+
declare const CONFLUENCE_TOOL_DEFINITIONS: readonly ToolDefinition[];
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* MCP server for Confluence integration.
|
|
536
|
+
*
|
|
537
|
+
* A second stdio server in the same package, deliberately separate from
|
|
538
|
+
* {@link ./server.ts}. Both read the same `~/.softspark/jira-mcp/config.json`
|
|
539
|
+
* and the same credentials, but each advertises only its own tools: merging
|
|
540
|
+
* them would put near-homonyms (`search_tasks` and `search_pages`,
|
|
541
|
+
* `get_task_details` and `get_page`) in one list and make tool selection
|
|
542
|
+
* measurably worse.
|
|
543
|
+
*
|
|
544
|
+
* Tool definitions live in {@link ./confluence/tools/definitions.ts}.
|
|
545
|
+
*
|
|
546
|
+
* @module
|
|
547
|
+
*/
|
|
548
|
+
|
|
549
|
+
declare function createConfluenceServer(): Server;
|
|
550
|
+
/**
|
|
551
|
+
* Route one tool call to its handler.
|
|
552
|
+
*
|
|
553
|
+
* Never throws. Argument extraction runs before a handler's own try/catch, so
|
|
554
|
+
* a missing parameter would otherwise escape as a transport-level JSON-RPC
|
|
555
|
+
* error while every other failure arrives as a `{ success: false, code }`
|
|
556
|
+
* envelope. Catching here gives the caller one error shape to read.
|
|
557
|
+
*
|
|
558
|
+
* Exported separately from {@link startConfluenceServer} so tests can drive
|
|
559
|
+
* every tool without standing up a stdio transport.
|
|
560
|
+
*/
|
|
561
|
+
declare function dispatchConfluenceTool(name: string, args: Record<string, unknown>, deps: ConfluenceDeps): ReturnType<typeof handleListSpaces>;
|
|
562
|
+
/**
|
|
563
|
+
* Boot the Confluence MCP server: load config, build the pool, register
|
|
564
|
+
* handlers, and connect via stdio transport.
|
|
565
|
+
*/
|
|
566
|
+
declare function startConfluenceServer(): Promise<void>;
|
|
567
|
+
|
|
568
|
+
export { CONFLUENCE_TOOL_DEFINITIONS, createConfluenceServer, dispatchConfluenceTool, startConfluenceServer };
|