@vinhnguyen/confluence-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/index.js +954 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Confluence MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that provides tools for interacting with Confluence. Read, create, update, and delete pages, manage spaces, search content, and more.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Page Operations**: Get, create, update, delete pages
|
|
8
|
+
- **Space Operations**: List spaces, get space details, browse space content
|
|
9
|
+
- **Search**: Full CQL support, search by text or title
|
|
10
|
+
- **Labels**: Get, add, remove page labels
|
|
11
|
+
- **Comments**: Read and add page comments
|
|
12
|
+
- **Attachments**: List page attachments
|
|
13
|
+
- **Special**: Extract DONE sections from daily reports
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd confluence-mcp
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
The MCP server requires three environment variables:
|
|
25
|
+
|
|
26
|
+
| Variable | Description | Example |
|
|
27
|
+
|----------|-------------|---------|
|
|
28
|
+
| `ATLASSIAN_EMAIL` | Your Atlassian account email | `user@company.com` |
|
|
29
|
+
| `ATLASSIAN_API_TOKEN` | API token from Atlassian | `ATATT3xFfGF0...` |
|
|
30
|
+
| `ATLASSIAN_DOMAIN` | Your Confluence domain | `company.atlassian.net` |
|
|
31
|
+
|
|
32
|
+
### Getting an API Token
|
|
33
|
+
|
|
34
|
+
1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
|
|
35
|
+
2. Click "Create API token"
|
|
36
|
+
3. Give it a label and copy the token
|
|
37
|
+
|
|
38
|
+
## Usage with Claude Code
|
|
39
|
+
|
|
40
|
+
Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json` or project settings):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"confluence": {
|
|
46
|
+
"command": "node",
|
|
47
|
+
"args": ["/path/to/confluence-mcp/index.js"],
|
|
48
|
+
"env": {
|
|
49
|
+
"ATLASSIAN_EMAIL": "your.email@company.com",
|
|
50
|
+
"ATLASSIAN_API_TOKEN": "your_api_token",
|
|
51
|
+
"ATLASSIAN_DOMAIN": "your-domain.atlassian.net"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Available Tools
|
|
59
|
+
|
|
60
|
+
### Page Operations
|
|
61
|
+
|
|
62
|
+
| Tool | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `confluence_get_page` | Get page details including content, version, metadata |
|
|
65
|
+
| `confluence_get_page_content` | Get page content as text or HTML |
|
|
66
|
+
| `confluence_get_child_pages` | Get all child pages of a parent (with pagination) |
|
|
67
|
+
| `confluence_create_page` | Create a new page in a space |
|
|
68
|
+
| `confluence_update_page` | Update an existing page |
|
|
69
|
+
| `confluence_delete_page` | Delete a page (moves to trash) |
|
|
70
|
+
|
|
71
|
+
### Space Operations
|
|
72
|
+
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `confluence_list_spaces` | List all accessible spaces |
|
|
76
|
+
| `confluence_get_space` | Get space details |
|
|
77
|
+
| `confluence_get_space_content` | Get pages or blogposts in a space |
|
|
78
|
+
|
|
79
|
+
### Search Operations
|
|
80
|
+
|
|
81
|
+
| Tool | Description |
|
|
82
|
+
|------|-------------|
|
|
83
|
+
| `confluence_search` | Search using CQL query |
|
|
84
|
+
| `confluence_search_by_text` | Search pages by text content |
|
|
85
|
+
| `confluence_search_by_title` | Search pages by title |
|
|
86
|
+
|
|
87
|
+
### Labels Operations
|
|
88
|
+
|
|
89
|
+
| Tool | Description |
|
|
90
|
+
|------|-------------|
|
|
91
|
+
| `confluence_get_page_labels` | Get labels on a page |
|
|
92
|
+
| `confluence_add_page_label` | Add a label to a page |
|
|
93
|
+
| `confluence_remove_page_label` | Remove a label from a page |
|
|
94
|
+
|
|
95
|
+
### Comments & Attachments
|
|
96
|
+
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `confluence_get_page_comments` | Get comments on a page |
|
|
100
|
+
| `confluence_add_page_comment` | Add a comment to a page |
|
|
101
|
+
| `confluence_get_page_attachments` | List attachments on a page |
|
|
102
|
+
|
|
103
|
+
### Special Tools
|
|
104
|
+
|
|
105
|
+
| Tool | Description |
|
|
106
|
+
|------|-------------|
|
|
107
|
+
| `confluence_extract_done_sections` | Extract DONE sections from daily reports |
|
|
108
|
+
|
|
109
|
+
## Example Usage
|
|
110
|
+
|
|
111
|
+
### Get a page and its content
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Use confluence_get_page with pageId "12345678"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Create a new page
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Use confluence_create_page with:
|
|
121
|
+
- spaceKey: "DEV"
|
|
122
|
+
- title: "Weekly Report - Week 5"
|
|
123
|
+
- content: "<h1>Weekly Summary</h1><p>This week we completed...</p>"
|
|
124
|
+
- parentId: "87654321" (optional)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Search for pages
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Use confluence_search_by_title with title "Daily Report" and spaceKey "TEAM"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Extract DONE sections for report generation
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Use confluence_extract_done_sections with pageId "12345678"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Content Format
|
|
140
|
+
|
|
141
|
+
When creating or updating pages, use Confluence storage format (XHTML):
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<h1>Heading 1</h1>
|
|
145
|
+
<h2>Heading 2</h2>
|
|
146
|
+
<p>Paragraph text</p>
|
|
147
|
+
<ul>
|
|
148
|
+
<li>List item 1</li>
|
|
149
|
+
<li>List item 2</li>
|
|
150
|
+
</ul>
|
|
151
|
+
<ac:structured-macro ac:name="code">
|
|
152
|
+
<ac:plain-text-body><![CDATA[code here]]></ac:plain-text-body>
|
|
153
|
+
</ac:structured-macro>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## CQL Query Examples
|
|
157
|
+
|
|
158
|
+
Confluence Query Language (CQL) is used for advanced searches:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
# Pages in a specific space
|
|
162
|
+
type=page AND space=DEV
|
|
163
|
+
|
|
164
|
+
# Pages modified recently
|
|
165
|
+
type=page AND lastmodified > now("-7d")
|
|
166
|
+
|
|
167
|
+
# Pages with specific label
|
|
168
|
+
type=page AND label=weekly-report
|
|
169
|
+
|
|
170
|
+
# Pages by creator
|
|
171
|
+
type=page AND creator=currentUser()
|
|
172
|
+
|
|
173
|
+
# Combined query
|
|
174
|
+
type=page AND space=TEAM AND text ~ "report" AND lastmodified > now("-30d")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { convert } from "html-to-text";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Configuration
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const ATLASSIAN_EMAIL = process.env.ATLASSIAN_EMAIL;
|
|
16
|
+
const ATLASSIAN_API_TOKEN = process.env.ATLASSIAN_API_TOKEN;
|
|
17
|
+
const ATLASSIAN_DOMAIN = process.env.ATLASSIAN_DOMAIN;
|
|
18
|
+
|
|
19
|
+
function getAuth() {
|
|
20
|
+
if (!ATLASSIAN_EMAIL || !ATLASSIAN_API_TOKEN) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Missing required environment variables: ATLASSIAN_EMAIL and ATLASSIAN_API_TOKEN"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return Buffer.from(`${ATLASSIAN_EMAIL}:${ATLASSIAN_API_TOKEN}`).toString(
|
|
26
|
+
"base64"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getBaseUrl() {
|
|
31
|
+
if (!ATLASSIAN_DOMAIN) {
|
|
32
|
+
throw new Error("Missing required environment variable: ATLASSIAN_DOMAIN");
|
|
33
|
+
}
|
|
34
|
+
return `https://${ATLASSIAN_DOMAIN}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Confluence API Client
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
class ConfluenceClient {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.baseUrl = getBaseUrl();
|
|
44
|
+
this.auth = getAuth();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async request(endpoint, options = {}) {
|
|
48
|
+
const url = endpoint.startsWith("http")
|
|
49
|
+
? endpoint
|
|
50
|
+
: `${this.baseUrl}${endpoint}`;
|
|
51
|
+
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
...options,
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Basic ${this.auth}`,
|
|
56
|
+
Accept: "application/json",
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
...options.headers,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorText = await response.text();
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Confluence API error (${response.status}): ${errorText}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle 204 No Content
|
|
70
|
+
if (response.status === 204) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return response.json();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Convert HTML to plain text
|
|
78
|
+
htmlToText(html) {
|
|
79
|
+
return convert(html, {
|
|
80
|
+
wordwrap: false,
|
|
81
|
+
preserveNewlines: true,
|
|
82
|
+
selectors: [
|
|
83
|
+
{ selector: "img", format: "skip" },
|
|
84
|
+
{ selector: "a", options: { hideLinkHrefIfSameAsText: true } },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
// Page Operations
|
|
91
|
+
// -------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
async getPage(pageId, expand = "body.storage,version,space") {
|
|
94
|
+
const data = await this.request(
|
|
95
|
+
`/wiki/rest/api/content/${pageId}?expand=${expand}`
|
|
96
|
+
);
|
|
97
|
+
return {
|
|
98
|
+
id: data.id,
|
|
99
|
+
title: data.title,
|
|
100
|
+
spaceKey: data.space?.key,
|
|
101
|
+
version: data.version?.number,
|
|
102
|
+
content: data.body?.storage?.value,
|
|
103
|
+
contentAsText: data.body?.storage?.value
|
|
104
|
+
? this.htmlToText(data.body.storage.value)
|
|
105
|
+
: null,
|
|
106
|
+
webUrl: data._links?.webui
|
|
107
|
+
? `${this.baseUrl}/wiki${data._links.webui}`
|
|
108
|
+
: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getPageContent(pageId, format = "text") {
|
|
113
|
+
const data = await this.request(
|
|
114
|
+
`/wiki/rest/api/content/${pageId}?expand=body.storage`
|
|
115
|
+
);
|
|
116
|
+
const html = data.body?.storage?.value || "";
|
|
117
|
+
return format === "html" ? html : this.htmlToText(html);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getChildPages(parentId, limit = 250) {
|
|
121
|
+
const allChildren = [];
|
|
122
|
+
let nextUrl = `/wiki/api/v2/pages/${parentId}/children?limit=${limit}`;
|
|
123
|
+
|
|
124
|
+
while (nextUrl) {
|
|
125
|
+
const url = nextUrl.startsWith("http")
|
|
126
|
+
? nextUrl
|
|
127
|
+
: `${this.baseUrl}${nextUrl}`;
|
|
128
|
+
const data = await this.request(url);
|
|
129
|
+
|
|
130
|
+
if (data.results) {
|
|
131
|
+
allChildren.push(
|
|
132
|
+
...data.results.map((page) => ({
|
|
133
|
+
id: page.id,
|
|
134
|
+
title: page.title,
|
|
135
|
+
status: page.status,
|
|
136
|
+
}))
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
nextUrl = data._links?.next || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return allChildren;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async createPage(spaceKey, title, content, parentId = null) {
|
|
147
|
+
const body = {
|
|
148
|
+
type: "page",
|
|
149
|
+
title,
|
|
150
|
+
space: { key: spaceKey },
|
|
151
|
+
body: {
|
|
152
|
+
storage: {
|
|
153
|
+
value: content,
|
|
154
|
+
representation: "storage",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (parentId) {
|
|
160
|
+
body.ancestors = [{ id: parentId }];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = await this.request("/wiki/rest/api/content", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
body: JSON.stringify(body),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
id: data.id,
|
|
170
|
+
title: data.title,
|
|
171
|
+
webUrl: data._links?.webui
|
|
172
|
+
? `${this.baseUrl}/wiki${data._links.webui}`
|
|
173
|
+
: null,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async updatePage(pageId, title, content, version) {
|
|
178
|
+
const body = {
|
|
179
|
+
type: "page",
|
|
180
|
+
title,
|
|
181
|
+
body: {
|
|
182
|
+
storage: {
|
|
183
|
+
value: content,
|
|
184
|
+
representation: "storage",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
version: {
|
|
188
|
+
number: version + 1,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const data = await this.request(`/wiki/rest/api/content/${pageId}`, {
|
|
193
|
+
method: "PUT",
|
|
194
|
+
body: JSON.stringify(body),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
id: data.id,
|
|
199
|
+
title: data.title,
|
|
200
|
+
version: data.version?.number,
|
|
201
|
+
webUrl: data._links?.webui
|
|
202
|
+
? `${this.baseUrl}/wiki${data._links.webui}`
|
|
203
|
+
: null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async deletePage(pageId) {
|
|
208
|
+
await this.request(`/wiki/rest/api/content/${pageId}`, {
|
|
209
|
+
method: "DELETE",
|
|
210
|
+
});
|
|
211
|
+
return { success: true, deletedPageId: pageId };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
// Space Operations
|
|
216
|
+
// -------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
async listSpaces(limit = 25) {
|
|
219
|
+
const data = await this.request(`/wiki/rest/api/space?limit=${limit}`);
|
|
220
|
+
return data.results.map((space) => ({
|
|
221
|
+
key: space.key,
|
|
222
|
+
name: space.name,
|
|
223
|
+
type: space.type,
|
|
224
|
+
webUrl: space._links?.webui
|
|
225
|
+
? `${this.baseUrl}/wiki${space._links.webui}`
|
|
226
|
+
: null,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async getSpace(spaceKey) {
|
|
231
|
+
const data = await this.request(
|
|
232
|
+
`/wiki/rest/api/space/${spaceKey}?expand=description.plain,homepage`
|
|
233
|
+
);
|
|
234
|
+
return {
|
|
235
|
+
key: data.key,
|
|
236
|
+
name: data.name,
|
|
237
|
+
type: data.type,
|
|
238
|
+
description: data.description?.plain?.value,
|
|
239
|
+
homepageId: data.homepage?.id,
|
|
240
|
+
webUrl: data._links?.webui
|
|
241
|
+
? `${this.baseUrl}/wiki${data._links.webui}`
|
|
242
|
+
: null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async getSpaceContent(spaceKey, type = "page", limit = 25) {
|
|
247
|
+
const data = await this.request(
|
|
248
|
+
`/wiki/rest/api/space/${spaceKey}/content/${type}?limit=${limit}`
|
|
249
|
+
);
|
|
250
|
+
return data.results.map((item) => ({
|
|
251
|
+
id: item.id,
|
|
252
|
+
title: item.title,
|
|
253
|
+
type: item.type,
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// -------------------------------------------------------------------------
|
|
258
|
+
// Search Operations
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
async search(cql, limit = 25) {
|
|
262
|
+
const encodedCql = encodeURIComponent(cql);
|
|
263
|
+
const data = await this.request(
|
|
264
|
+
`/wiki/rest/api/content/search?cql=${encodedCql}&limit=${limit}`
|
|
265
|
+
);
|
|
266
|
+
return data.results.map((item) => ({
|
|
267
|
+
id: item.id,
|
|
268
|
+
title: item.title,
|
|
269
|
+
type: item.type,
|
|
270
|
+
excerpt: item.excerpt,
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async searchByText(text, spaceKey = null, limit = 25) {
|
|
275
|
+
let cql = `text ~ "${text}"`;
|
|
276
|
+
if (spaceKey) {
|
|
277
|
+
cql += ` AND space = "${spaceKey}"`;
|
|
278
|
+
}
|
|
279
|
+
return this.search(cql, limit);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async searchByTitle(title, spaceKey = null, limit = 25) {
|
|
283
|
+
let cql = `title ~ "${title}"`;
|
|
284
|
+
if (spaceKey) {
|
|
285
|
+
cql += ` AND space = "${spaceKey}"`;
|
|
286
|
+
}
|
|
287
|
+
return this.search(cql, limit);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// -------------------------------------------------------------------------
|
|
291
|
+
// Labels Operations
|
|
292
|
+
// -------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
async getPageLabels(pageId) {
|
|
295
|
+
const data = await this.request(
|
|
296
|
+
`/wiki/rest/api/content/${pageId}/label`
|
|
297
|
+
);
|
|
298
|
+
return data.results.map((label) => ({
|
|
299
|
+
name: label.name,
|
|
300
|
+
prefix: label.prefix,
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async addPageLabel(pageId, labelName) {
|
|
305
|
+
const data = await this.request(
|
|
306
|
+
`/wiki/rest/api/content/${pageId}/label`,
|
|
307
|
+
{
|
|
308
|
+
method: "POST",
|
|
309
|
+
body: JSON.stringify([{ name: labelName, prefix: "global" }]),
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
return data.results.map((label) => ({
|
|
313
|
+
name: label.name,
|
|
314
|
+
prefix: label.prefix,
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async removePageLabel(pageId, labelName) {
|
|
319
|
+
await this.request(
|
|
320
|
+
`/wiki/rest/api/content/${pageId}/label/${labelName}`,
|
|
321
|
+
{
|
|
322
|
+
method: "DELETE",
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
return { success: true };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// -------------------------------------------------------------------------
|
|
329
|
+
// Comments Operations
|
|
330
|
+
// -------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
async getPageComments(pageId, limit = 25) {
|
|
333
|
+
const data = await this.request(
|
|
334
|
+
`/wiki/rest/api/content/${pageId}/child/comment?expand=body.storage&limit=${limit}`
|
|
335
|
+
);
|
|
336
|
+
return data.results.map((comment) => ({
|
|
337
|
+
id: comment.id,
|
|
338
|
+
content: comment.body?.storage?.value
|
|
339
|
+
? this.htmlToText(comment.body.storage.value)
|
|
340
|
+
: null,
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async addPageComment(pageId, content) {
|
|
345
|
+
const body = {
|
|
346
|
+
type: "comment",
|
|
347
|
+
container: { id: pageId, type: "page" },
|
|
348
|
+
body: {
|
|
349
|
+
storage: {
|
|
350
|
+
value: content,
|
|
351
|
+
representation: "storage",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const data = await this.request("/wiki/rest/api/content", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
body: JSON.stringify(body),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
id: data.id,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// -------------------------------------------------------------------------
|
|
367
|
+
// Attachments Operations
|
|
368
|
+
// -------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
async getPageAttachments(pageId, limit = 25) {
|
|
371
|
+
const data = await this.request(
|
|
372
|
+
`/wiki/rest/api/content/${pageId}/child/attachment?limit=${limit}`
|
|
373
|
+
);
|
|
374
|
+
return data.results.map((attachment) => ({
|
|
375
|
+
id: attachment.id,
|
|
376
|
+
title: attachment.title,
|
|
377
|
+
mediaType: attachment.metadata?.mediaType,
|
|
378
|
+
fileSize: attachment.extensions?.fileSize,
|
|
379
|
+
downloadUrl: attachment._links?.download
|
|
380
|
+
? `${this.baseUrl}/wiki${attachment._links.download}`
|
|
381
|
+
: null,
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// -------------------------------------------------------------------------
|
|
386
|
+
// Extract DONE sections (from existing app)
|
|
387
|
+
// -------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
async extractDoneSections(pageId) {
|
|
390
|
+
const content = await this.getPageContent(pageId, "text");
|
|
391
|
+
const doneRegex = /DONE([\s\S]*?)(?=TODO|$)/g;
|
|
392
|
+
const matches = [];
|
|
393
|
+
let match;
|
|
394
|
+
|
|
395
|
+
while ((match = doneRegex.exec(content)) !== null) {
|
|
396
|
+
matches.push(match[1].trim());
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
pageId,
|
|
401
|
+
doneSections: matches,
|
|
402
|
+
fullContent: content,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// MCP Server Setup
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
const server = new Server(
|
|
412
|
+
{
|
|
413
|
+
name: "confluence-mcp",
|
|
414
|
+
version: "1.0.0",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
capabilities: {
|
|
418
|
+
tools: {},
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const client = new ConfluenceClient();
|
|
424
|
+
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// Tool Definitions
|
|
427
|
+
// ============================================================================
|
|
428
|
+
|
|
429
|
+
const TOOLS = [
|
|
430
|
+
// Page Operations
|
|
431
|
+
{
|
|
432
|
+
name: "confluence_get_page",
|
|
433
|
+
description:
|
|
434
|
+
"Get a Confluence page by ID, including its content, version, and metadata",
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: "object",
|
|
437
|
+
properties: {
|
|
438
|
+
pageId: {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "The ID of the page to retrieve",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
required: ["pageId"],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "confluence_get_page_content",
|
|
448
|
+
description:
|
|
449
|
+
"Get the content of a Confluence page as plain text or HTML",
|
|
450
|
+
inputSchema: {
|
|
451
|
+
type: "object",
|
|
452
|
+
properties: {
|
|
453
|
+
pageId: {
|
|
454
|
+
type: "string",
|
|
455
|
+
description: "The ID of the page",
|
|
456
|
+
},
|
|
457
|
+
format: {
|
|
458
|
+
type: "string",
|
|
459
|
+
enum: ["text", "html"],
|
|
460
|
+
description: "Output format: 'text' (default) or 'html'",
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
required: ["pageId"],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "confluence_get_child_pages",
|
|
468
|
+
description:
|
|
469
|
+
"Get all child pages of a parent page (handles pagination automatically)",
|
|
470
|
+
inputSchema: {
|
|
471
|
+
type: "object",
|
|
472
|
+
properties: {
|
|
473
|
+
parentId: {
|
|
474
|
+
type: "string",
|
|
475
|
+
description: "The ID of the parent page",
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
required: ["parentId"],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "confluence_create_page",
|
|
483
|
+
description:
|
|
484
|
+
"Create a new Confluence page in a space, optionally as a child of another page",
|
|
485
|
+
inputSchema: {
|
|
486
|
+
type: "object",
|
|
487
|
+
properties: {
|
|
488
|
+
spaceKey: {
|
|
489
|
+
type: "string",
|
|
490
|
+
description: "The key of the space (e.g., 'DEV', 'TEAM')",
|
|
491
|
+
},
|
|
492
|
+
title: {
|
|
493
|
+
type: "string",
|
|
494
|
+
description: "The title of the new page",
|
|
495
|
+
},
|
|
496
|
+
content: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description:
|
|
499
|
+
"The content in Confluence storage format (XHTML). Use <p> tags for paragraphs, <h1>-<h6> for headings, etc.",
|
|
500
|
+
},
|
|
501
|
+
parentId: {
|
|
502
|
+
type: "string",
|
|
503
|
+
description:
|
|
504
|
+
"Optional: ID of the parent page if creating a child page",
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
required: ["spaceKey", "title", "content"],
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "confluence_update_page",
|
|
512
|
+
description:
|
|
513
|
+
"Update an existing Confluence page content and/or title",
|
|
514
|
+
inputSchema: {
|
|
515
|
+
type: "object",
|
|
516
|
+
properties: {
|
|
517
|
+
pageId: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "The ID of the page to update",
|
|
520
|
+
},
|
|
521
|
+
title: {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: "The new title of the page",
|
|
524
|
+
},
|
|
525
|
+
content: {
|
|
526
|
+
type: "string",
|
|
527
|
+
description: "The new content in Confluence storage format (XHTML)",
|
|
528
|
+
},
|
|
529
|
+
version: {
|
|
530
|
+
type: "number",
|
|
531
|
+
description:
|
|
532
|
+
"The current version number of the page (required for update)",
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
required: ["pageId", "title", "content", "version"],
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "confluence_delete_page",
|
|
540
|
+
description:
|
|
541
|
+
"Delete a Confluence page by ID (moves to trash)",
|
|
542
|
+
inputSchema: {
|
|
543
|
+
type: "object",
|
|
544
|
+
properties: {
|
|
545
|
+
pageId: {
|
|
546
|
+
type: "string",
|
|
547
|
+
description: "The ID of the page to delete",
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
required: ["pageId"],
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// Space Operations
|
|
555
|
+
{
|
|
556
|
+
name: "confluence_list_spaces",
|
|
557
|
+
description: "List all accessible Confluence spaces",
|
|
558
|
+
inputSchema: {
|
|
559
|
+
type: "object",
|
|
560
|
+
properties: {
|
|
561
|
+
limit: {
|
|
562
|
+
type: "number",
|
|
563
|
+
description: "Maximum number of spaces to return (default: 25)",
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "confluence_get_space",
|
|
570
|
+
description: "Get details about a specific Confluence space",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
spaceKey: {
|
|
575
|
+
type: "string",
|
|
576
|
+
description: "The key of the space (e.g., 'DEV', 'TEAM')",
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
required: ["spaceKey"],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "confluence_get_space_content",
|
|
584
|
+
description: "Get pages or blogposts in a specific space",
|
|
585
|
+
inputSchema: {
|
|
586
|
+
type: "object",
|
|
587
|
+
properties: {
|
|
588
|
+
spaceKey: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "The key of the space",
|
|
591
|
+
},
|
|
592
|
+
type: {
|
|
593
|
+
type: "string",
|
|
594
|
+
enum: ["page", "blogpost"],
|
|
595
|
+
description: "Content type: 'page' (default) or 'blogpost'",
|
|
596
|
+
},
|
|
597
|
+
limit: {
|
|
598
|
+
type: "number",
|
|
599
|
+
description: "Maximum number of items to return (default: 25)",
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
required: ["spaceKey"],
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
// Search Operations
|
|
607
|
+
{
|
|
608
|
+
name: "confluence_search",
|
|
609
|
+
description:
|
|
610
|
+
"Search Confluence using CQL (Confluence Query Language)",
|
|
611
|
+
inputSchema: {
|
|
612
|
+
type: "object",
|
|
613
|
+
properties: {
|
|
614
|
+
cql: {
|
|
615
|
+
type: "string",
|
|
616
|
+
description:
|
|
617
|
+
'CQL query string (e.g., \'type=page AND space=DEV AND text ~ "report"\')',
|
|
618
|
+
},
|
|
619
|
+
limit: {
|
|
620
|
+
type: "number",
|
|
621
|
+
description: "Maximum number of results (default: 25)",
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
required: ["cql"],
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "confluence_search_by_text",
|
|
629
|
+
description: "Search Confluence pages by text content",
|
|
630
|
+
inputSchema: {
|
|
631
|
+
type: "object",
|
|
632
|
+
properties: {
|
|
633
|
+
text: {
|
|
634
|
+
type: "string",
|
|
635
|
+
description: "Text to search for",
|
|
636
|
+
},
|
|
637
|
+
spaceKey: {
|
|
638
|
+
type: "string",
|
|
639
|
+
description: "Optional: Limit search to a specific space",
|
|
640
|
+
},
|
|
641
|
+
limit: {
|
|
642
|
+
type: "number",
|
|
643
|
+
description: "Maximum number of results (default: 25)",
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
required: ["text"],
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: "confluence_search_by_title",
|
|
651
|
+
description: "Search Confluence pages by title",
|
|
652
|
+
inputSchema: {
|
|
653
|
+
type: "object",
|
|
654
|
+
properties: {
|
|
655
|
+
title: {
|
|
656
|
+
type: "string",
|
|
657
|
+
description: "Title text to search for",
|
|
658
|
+
},
|
|
659
|
+
spaceKey: {
|
|
660
|
+
type: "string",
|
|
661
|
+
description: "Optional: Limit search to a specific space",
|
|
662
|
+
},
|
|
663
|
+
limit: {
|
|
664
|
+
type: "number",
|
|
665
|
+
description: "Maximum number of results (default: 25)",
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
required: ["title"],
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
// Labels Operations
|
|
673
|
+
{
|
|
674
|
+
name: "confluence_get_page_labels",
|
|
675
|
+
description: "Get all labels attached to a page",
|
|
676
|
+
inputSchema: {
|
|
677
|
+
type: "object",
|
|
678
|
+
properties: {
|
|
679
|
+
pageId: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "The ID of the page",
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
required: ["pageId"],
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "confluence_add_page_label",
|
|
689
|
+
description: "Add a label to a page",
|
|
690
|
+
inputSchema: {
|
|
691
|
+
type: "object",
|
|
692
|
+
properties: {
|
|
693
|
+
pageId: {
|
|
694
|
+
type: "string",
|
|
695
|
+
description: "The ID of the page",
|
|
696
|
+
},
|
|
697
|
+
labelName: {
|
|
698
|
+
type: "string",
|
|
699
|
+
description: "The label name to add",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
required: ["pageId", "labelName"],
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: "confluence_remove_page_label",
|
|
707
|
+
description: "Remove a label from a page",
|
|
708
|
+
inputSchema: {
|
|
709
|
+
type: "object",
|
|
710
|
+
properties: {
|
|
711
|
+
pageId: {
|
|
712
|
+
type: "string",
|
|
713
|
+
description: "The ID of the page",
|
|
714
|
+
},
|
|
715
|
+
labelName: {
|
|
716
|
+
type: "string",
|
|
717
|
+
description: "The label name to remove",
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
required: ["pageId", "labelName"],
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
// Comments Operations
|
|
725
|
+
{
|
|
726
|
+
name: "confluence_get_page_comments",
|
|
727
|
+
description: "Get comments on a page",
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: "object",
|
|
730
|
+
properties: {
|
|
731
|
+
pageId: {
|
|
732
|
+
type: "string",
|
|
733
|
+
description: "The ID of the page",
|
|
734
|
+
},
|
|
735
|
+
limit: {
|
|
736
|
+
type: "number",
|
|
737
|
+
description: "Maximum number of comments to return (default: 25)",
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
required: ["pageId"],
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
name: "confluence_add_page_comment",
|
|
745
|
+
description: "Add a comment to a page",
|
|
746
|
+
inputSchema: {
|
|
747
|
+
type: "object",
|
|
748
|
+
properties: {
|
|
749
|
+
pageId: {
|
|
750
|
+
type: "string",
|
|
751
|
+
description: "The ID of the page",
|
|
752
|
+
},
|
|
753
|
+
content: {
|
|
754
|
+
type: "string",
|
|
755
|
+
description: "The comment content in HTML format",
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
required: ["pageId", "content"],
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
|
|
762
|
+
// Attachments Operations
|
|
763
|
+
{
|
|
764
|
+
name: "confluence_get_page_attachments",
|
|
765
|
+
description: "Get attachments on a page",
|
|
766
|
+
inputSchema: {
|
|
767
|
+
type: "object",
|
|
768
|
+
properties: {
|
|
769
|
+
pageId: {
|
|
770
|
+
type: "string",
|
|
771
|
+
description: "The ID of the page",
|
|
772
|
+
},
|
|
773
|
+
limit: {
|
|
774
|
+
type: "number",
|
|
775
|
+
description: "Maximum number of attachments to return (default: 25)",
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
required: ["pageId"],
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
|
|
782
|
+
// Special Operations
|
|
783
|
+
{
|
|
784
|
+
name: "confluence_extract_done_sections",
|
|
785
|
+
description:
|
|
786
|
+
"Extract DONE sections from a page (useful for daily reports). Returns content between 'DONE' and 'TODO' markers.",
|
|
787
|
+
inputSchema: {
|
|
788
|
+
type: "object",
|
|
789
|
+
properties: {
|
|
790
|
+
pageId: {
|
|
791
|
+
type: "string",
|
|
792
|
+
description: "The ID of the page to extract DONE sections from",
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
required: ["pageId"],
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
];
|
|
799
|
+
|
|
800
|
+
// ============================================================================
|
|
801
|
+
// Tool Handlers
|
|
802
|
+
// ============================================================================
|
|
803
|
+
|
|
804
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
805
|
+
return { tools: TOOLS };
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
809
|
+
const { name, arguments: args } = request.params;
|
|
810
|
+
|
|
811
|
+
try {
|
|
812
|
+
let result;
|
|
813
|
+
|
|
814
|
+
switch (name) {
|
|
815
|
+
// Page Operations
|
|
816
|
+
case "confluence_get_page":
|
|
817
|
+
result = await client.getPage(args.pageId);
|
|
818
|
+
break;
|
|
819
|
+
|
|
820
|
+
case "confluence_get_page_content":
|
|
821
|
+
result = await client.getPageContent(args.pageId, args.format || "text");
|
|
822
|
+
break;
|
|
823
|
+
|
|
824
|
+
case "confluence_get_child_pages":
|
|
825
|
+
result = await client.getChildPages(args.parentId);
|
|
826
|
+
break;
|
|
827
|
+
|
|
828
|
+
case "confluence_create_page":
|
|
829
|
+
result = await client.createPage(
|
|
830
|
+
args.spaceKey,
|
|
831
|
+
args.title,
|
|
832
|
+
args.content,
|
|
833
|
+
args.parentId
|
|
834
|
+
);
|
|
835
|
+
break;
|
|
836
|
+
|
|
837
|
+
case "confluence_update_page":
|
|
838
|
+
result = await client.updatePage(
|
|
839
|
+
args.pageId,
|
|
840
|
+
args.title,
|
|
841
|
+
args.content,
|
|
842
|
+
args.version
|
|
843
|
+
);
|
|
844
|
+
break;
|
|
845
|
+
|
|
846
|
+
case "confluence_delete_page":
|
|
847
|
+
result = await client.deletePage(args.pageId);
|
|
848
|
+
break;
|
|
849
|
+
|
|
850
|
+
// Space Operations
|
|
851
|
+
case "confluence_list_spaces":
|
|
852
|
+
result = await client.listSpaces(args.limit);
|
|
853
|
+
break;
|
|
854
|
+
|
|
855
|
+
case "confluence_get_space":
|
|
856
|
+
result = await client.getSpace(args.spaceKey);
|
|
857
|
+
break;
|
|
858
|
+
|
|
859
|
+
case "confluence_get_space_content":
|
|
860
|
+
result = await client.getSpaceContent(
|
|
861
|
+
args.spaceKey,
|
|
862
|
+
args.type || "page",
|
|
863
|
+
args.limit
|
|
864
|
+
);
|
|
865
|
+
break;
|
|
866
|
+
|
|
867
|
+
// Search Operations
|
|
868
|
+
case "confluence_search":
|
|
869
|
+
result = await client.search(args.cql, args.limit);
|
|
870
|
+
break;
|
|
871
|
+
|
|
872
|
+
case "confluence_search_by_text":
|
|
873
|
+
result = await client.searchByText(args.text, args.spaceKey, args.limit);
|
|
874
|
+
break;
|
|
875
|
+
|
|
876
|
+
case "confluence_search_by_title":
|
|
877
|
+
result = await client.searchByTitle(
|
|
878
|
+
args.title,
|
|
879
|
+
args.spaceKey,
|
|
880
|
+
args.limit
|
|
881
|
+
);
|
|
882
|
+
break;
|
|
883
|
+
|
|
884
|
+
// Labels Operations
|
|
885
|
+
case "confluence_get_page_labels":
|
|
886
|
+
result = await client.getPageLabels(args.pageId);
|
|
887
|
+
break;
|
|
888
|
+
|
|
889
|
+
case "confluence_add_page_label":
|
|
890
|
+
result = await client.addPageLabel(args.pageId, args.labelName);
|
|
891
|
+
break;
|
|
892
|
+
|
|
893
|
+
case "confluence_remove_page_label":
|
|
894
|
+
result = await client.removePageLabel(args.pageId, args.labelName);
|
|
895
|
+
break;
|
|
896
|
+
|
|
897
|
+
// Comments Operations
|
|
898
|
+
case "confluence_get_page_comments":
|
|
899
|
+
result = await client.getPageComments(args.pageId, args.limit);
|
|
900
|
+
break;
|
|
901
|
+
|
|
902
|
+
case "confluence_add_page_comment":
|
|
903
|
+
result = await client.addPageComment(args.pageId, args.content);
|
|
904
|
+
break;
|
|
905
|
+
|
|
906
|
+
// Attachments Operations
|
|
907
|
+
case "confluence_get_page_attachments":
|
|
908
|
+
result = await client.getPageAttachments(args.pageId, args.limit);
|
|
909
|
+
break;
|
|
910
|
+
|
|
911
|
+
// Special Operations
|
|
912
|
+
case "confluence_extract_done_sections":
|
|
913
|
+
result = await client.extractDoneSections(args.pageId);
|
|
914
|
+
break;
|
|
915
|
+
|
|
916
|
+
default:
|
|
917
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return {
|
|
921
|
+
content: [
|
|
922
|
+
{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: JSON.stringify(result, null, 2),
|
|
925
|
+
},
|
|
926
|
+
],
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
return {
|
|
930
|
+
content: [
|
|
931
|
+
{
|
|
932
|
+
type: "text",
|
|
933
|
+
text: `Error: ${error.message}`,
|
|
934
|
+
},
|
|
935
|
+
],
|
|
936
|
+
isError: true,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// ============================================================================
|
|
942
|
+
// Start Server
|
|
943
|
+
// ============================================================================
|
|
944
|
+
|
|
945
|
+
async function main() {
|
|
946
|
+
const transport = new StdioServerTransport();
|
|
947
|
+
await server.connect(transport);
|
|
948
|
+
console.error("Confluence MCP server running on stdio");
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
main().catch((error) => {
|
|
952
|
+
console.error("Fatal error:", error);
|
|
953
|
+
process.exit(1);
|
|
954
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vinhnguyen/confluence-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Confluence - read, create, update, delete pages and manage spaces",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"confluence-mcp": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"confluence",
|
|
16
|
+
"atlassian",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"claude",
|
|
19
|
+
"ai",
|
|
20
|
+
"llm"
|
|
21
|
+
],
|
|
22
|
+
"author": "vinhnguyen",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/vinhnguyen/confluence-mcp"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/vinhnguyen/confluence-mcp#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/vinhnguyen/confluence-mcp/issues"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
41
|
+
"html-to-text": "^9.0.5"
|
|
42
|
+
}
|
|
43
|
+
}
|