nodebb-plugin-integration 0.4.4 → 0.6.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 +156 -145
- package/README.ru.md +147 -136
- package/docs/MCP.md +718 -0
- package/docs/MCP.ru.md +719 -0
- package/package.json +3 -2
- package/src/mcp/server.js +32 -28
- package/src/mcp/tools.js +294 -76
- package/src/nodebb/posts.js +72 -21
- package/src/nodebb/topics.js +121 -24
- package/src/nodebb/users.js +19 -4
- package/src/routes/api.js +297 -35
- package/src/routes/index.js +24 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-integration",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI integration layer for NodeBB forums with MCP, semantic search, RAG and Qdrant knowledge pipeline",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"languages",
|
|
17
17
|
"README.md",
|
|
18
18
|
"README.ru.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"docs"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [
|
|
22
23
|
"nodebb",
|
package/src/mcp/server.js
CHANGED
|
@@ -187,30 +187,9 @@ async function start(rawConfig) {
|
|
|
187
187
|
await stop();
|
|
188
188
|
|
|
189
189
|
const api = createApiClient(config);
|
|
190
|
-
|
|
191
|
-
const nextMcpServer = new Server(
|
|
192
|
-
{
|
|
193
|
-
name: "nodebb-integration-mcp",
|
|
194
|
-
version: "0.4.2"
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
capabilities: {
|
|
198
|
-
tools: {}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const nextTransport = new StreamableHTTPServerTransport({
|
|
204
|
-
sessionIdGenerator: undefined
|
|
205
|
-
});
|
|
206
|
-
|
|
207
190
|
let nextHttpServer = null;
|
|
208
191
|
|
|
209
192
|
try {
|
|
210
|
-
Tools.register(nextMcpServer, api);
|
|
211
|
-
|
|
212
|
-
await nextMcpServer.connect(nextTransport);
|
|
213
|
-
|
|
214
193
|
nextHttpServer = http.createServer(async (req, res) => {
|
|
215
194
|
let pathname;
|
|
216
195
|
|
|
@@ -242,10 +221,32 @@ async function start(rawConfig) {
|
|
|
242
221
|
return;
|
|
243
222
|
}
|
|
244
223
|
|
|
224
|
+
let requestMcpServer = null;
|
|
225
|
+
let requestTransport = null;
|
|
226
|
+
|
|
245
227
|
try {
|
|
246
|
-
|
|
228
|
+
requestMcpServer = new Server(
|
|
229
|
+
{
|
|
230
|
+
name: "nodebb-integration-mcp",
|
|
231
|
+
version: "0.6.0"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
capabilities: {
|
|
235
|
+
tools: {}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
requestTransport = new StreamableHTTPServerTransport({
|
|
241
|
+
sessionIdGenerator: undefined
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
Tools.register(requestMcpServer, api);
|
|
245
|
+
|
|
246
|
+
await requestMcpServer.connect(requestTransport);
|
|
247
|
+
await requestTransport.handleRequest(req, res);
|
|
247
248
|
} catch (error) {
|
|
248
|
-
console.error("[MCP] Request failed:", error.
|
|
249
|
+
console.error("[MCP] Request failed:", error.stack || error);
|
|
249
250
|
|
|
250
251
|
if (!res.headersSent) {
|
|
251
252
|
res.writeHead(500, {
|
|
@@ -253,6 +254,14 @@ async function start(rawConfig) {
|
|
|
253
254
|
});
|
|
254
255
|
res.end(JSON.stringify({ error: "MCP request failed" }));
|
|
255
256
|
}
|
|
257
|
+
} finally {
|
|
258
|
+
if (requestTransport) {
|
|
259
|
+
await requestTransport.close().catch(() => {});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (requestMcpServer) {
|
|
263
|
+
await requestMcpServer.close().catch(() => {});
|
|
264
|
+
}
|
|
256
265
|
}
|
|
257
266
|
});
|
|
258
267
|
|
|
@@ -273,8 +282,6 @@ async function start(rawConfig) {
|
|
|
273
282
|
});
|
|
274
283
|
|
|
275
284
|
httpServer = nextHttpServer;
|
|
276
|
-
transport = nextTransport;
|
|
277
|
-
mcpServer = nextMcpServer;
|
|
278
285
|
|
|
279
286
|
console.log(
|
|
280
287
|
`[MCP] Streamable HTTP listening on ${config.host}:${config.port}/mcp`
|
|
@@ -287,9 +294,6 @@ async function start(rawConfig) {
|
|
|
287
294
|
});
|
|
288
295
|
}
|
|
289
296
|
|
|
290
|
-
await nextTransport.close().catch(() => {});
|
|
291
|
-
await nextMcpServer.close().catch(() => {});
|
|
292
|
-
|
|
293
297
|
throw error;
|
|
294
298
|
}
|
|
295
299
|
}
|
package/src/mcp/tools.js
CHANGED
|
@@ -12,24 +12,20 @@ const definitions = [
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: "get_post",
|
|
15
|
-
description: "Get a forum post by
|
|
15
|
+
description: "Get a forum post by ID. NodeBB access permissions are checked.",
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
pid: { type: "integer", minimum: 1 }
|
|
20
|
-
},
|
|
18
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
21
19
|
required: ["pid"],
|
|
22
20
|
additionalProperties: false
|
|
23
21
|
}
|
|
24
22
|
},
|
|
25
23
|
{
|
|
26
24
|
name: "get_topic",
|
|
27
|
-
description: "Get a forum topic by
|
|
25
|
+
description: "Get a forum topic by ID. NodeBB access permissions are checked.",
|
|
28
26
|
inputSchema: {
|
|
29
27
|
type: "object",
|
|
30
|
-
properties: {
|
|
31
|
-
tid: { type: "integer", minimum: 1 }
|
|
32
|
-
},
|
|
28
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
33
29
|
required: ["tid"],
|
|
34
30
|
additionalProperties: false
|
|
35
31
|
}
|
|
@@ -39,9 +35,7 @@ const definitions = [
|
|
|
39
35
|
description: "Get public profile information for a forum user",
|
|
40
36
|
inputSchema: {
|
|
41
37
|
type: "object",
|
|
42
|
-
properties: {
|
|
43
|
-
uid: { type: "integer", minimum: 1 }
|
|
44
|
-
},
|
|
38
|
+
properties: { uid: { type: "integer", minimum: 1 } },
|
|
45
39
|
required: ["uid"],
|
|
46
40
|
additionalProperties: false
|
|
47
41
|
}
|
|
@@ -58,66 +52,292 @@ const definitions = [
|
|
|
58
52
|
required: ["query"],
|
|
59
53
|
additionalProperties: false
|
|
60
54
|
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "create_topic",
|
|
58
|
+
description: "Create a topic in a NodeBB category as the configured MCP service user",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
cid: { type: "integer", minimum: 1 },
|
|
63
|
+
title: { type: "string", minLength: 1, maxLength: 255 },
|
|
64
|
+
content: { type: "string", minLength: 1, maxLength: 20000 },
|
|
65
|
+
tags: {
|
|
66
|
+
type: "array",
|
|
67
|
+
maxItems: 10,
|
|
68
|
+
items: { type: "string", minLength: 1, maxLength: 32 }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
required: ["cid", "title", "content"],
|
|
72
|
+
additionalProperties: false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "delete_topic",
|
|
77
|
+
description: "Soft-delete a topic using NodeBB permissions and hooks",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
81
|
+
required: ["tid"],
|
|
82
|
+
additionalProperties: false
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "restore_topic",
|
|
87
|
+
description: "Restore a soft-deleted topic using NodeBB permissions and hooks",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
91
|
+
required: ["tid"],
|
|
92
|
+
additionalProperties: false
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "list_topics",
|
|
97
|
+
description: "List topics visible to the configured MCP service user",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
start: { type: "integer", minimum: 0 },
|
|
102
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
103
|
+
sort: {
|
|
104
|
+
type: "string",
|
|
105
|
+
enum: ["recent", "old", "create", "posts", "votes", "views"]
|
|
106
|
+
},
|
|
107
|
+
cid: { type: "integer", minimum: 1 }
|
|
108
|
+
},
|
|
109
|
+
additionalProperties: false
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "create_post",
|
|
114
|
+
description: "Create a reply in a topic using NodeBB permissions and hooks",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
tid: { type: "integer", minimum: 1 },
|
|
119
|
+
content: { type: "string", minLength: 1, maxLength: 20000 }
|
|
120
|
+
},
|
|
121
|
+
required: ["tid", "content"],
|
|
122
|
+
additionalProperties: false
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "edit_post",
|
|
127
|
+
description: "Edit a post using NodeBB permissions and edit restrictions",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
pid: { type: "integer", minimum: 1 },
|
|
132
|
+
content: { type: "string", minLength: 1, maxLength: 20000 }
|
|
133
|
+
},
|
|
134
|
+
required: ["pid", "content"],
|
|
135
|
+
additionalProperties: false
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "delete_post",
|
|
140
|
+
description: "Soft-delete a post using NodeBB permissions and hooks",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
144
|
+
required: ["pid"],
|
|
145
|
+
additionalProperties: false
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "restore_post",
|
|
150
|
+
description: "Restore a soft-deleted post using NodeBB permissions and hooks",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
154
|
+
required: ["pid"],
|
|
155
|
+
additionalProperties: false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "delete_user_posts",
|
|
160
|
+
description: "Soft-delete up to 100 replies by a user. The first post of every topic is skipped. Use start to process subsequent batches.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
uid: { type: "integer", minimum: 1 },
|
|
165
|
+
start: { type: "integer", minimum: 0 },
|
|
166
|
+
limit: { type: "integer", minimum: 1, maximum: 100 }
|
|
167
|
+
},
|
|
168
|
+
required: ["uid"],
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "list_posts",
|
|
174
|
+
description: "List posts in a topic with NodeBB access filtering and pagination",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
tid: { type: "integer", minimum: 1 },
|
|
179
|
+
start: { type: "integer", minimum: 0 },
|
|
180
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
181
|
+
reverse: { type: "boolean" }
|
|
182
|
+
},
|
|
183
|
+
required: ["tid"],
|
|
184
|
+
additionalProperties: false
|
|
185
|
+
}
|
|
61
186
|
}
|
|
62
187
|
];
|
|
63
188
|
|
|
189
|
+
const allowedArguments = {
|
|
190
|
+
get_forum_info: [],
|
|
191
|
+
get_post: ["pid"],
|
|
192
|
+
get_topic: ["tid"],
|
|
193
|
+
get_user: ["uid"],
|
|
194
|
+
search_knowledge: ["query", "limit"],
|
|
195
|
+
create_topic: ["cid", "title", "content", "tags"],
|
|
196
|
+
delete_topic: ["tid"],
|
|
197
|
+
restore_topic: ["tid"],
|
|
198
|
+
list_topics: ["start", "limit", "sort", "cid"],
|
|
199
|
+
create_post: ["tid", "content"],
|
|
200
|
+
edit_post: ["pid", "content"],
|
|
201
|
+
delete_post: ["pid"],
|
|
202
|
+
restore_post: ["pid"],
|
|
203
|
+
delete_user_posts: ["uid", "start", "limit"],
|
|
204
|
+
list_posts: ["tid", "start", "limit", "reverse"]
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
function requireId(args, key) {
|
|
208
|
+
if (!Number.isSafeInteger(args[key]) || args[key] < 1) {
|
|
209
|
+
throw new Error(`Invalid ${key}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function requireText(args, key, maximum) {
|
|
214
|
+
if (typeof args[key] !== "string" ||
|
|
215
|
+
!args[key].trim() || args[key].length > maximum) {
|
|
216
|
+
throw new Error(`Invalid ${key}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pageValue(value, fallback, maximum, name) {
|
|
221
|
+
const result = value === undefined ? fallback : value;
|
|
222
|
+
|
|
223
|
+
if (!Number.isSafeInteger(result) || result < 0 || result > maximum) {
|
|
224
|
+
throw new Error(`Invalid ${name}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
64
230
|
function validateArguments(name, args) {
|
|
65
231
|
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
66
232
|
throw new Error("Tool arguments must be an object");
|
|
67
233
|
}
|
|
68
234
|
|
|
69
|
-
const allowed =
|
|
70
|
-
get_forum_info: [],
|
|
71
|
-
get_post: ["pid"],
|
|
72
|
-
get_topic: ["tid"],
|
|
73
|
-
get_user: ["uid"],
|
|
74
|
-
search_knowledge: ["query", "limit"]
|
|
75
|
-
}[name];
|
|
235
|
+
const allowed = allowedArguments[name];
|
|
76
236
|
|
|
77
|
-
if (!allowed) {
|
|
78
|
-
throw new Error(`Unknown MCP tool: ${name}`);
|
|
79
|
-
}
|
|
237
|
+
if (!allowed) throw new Error(`Unknown MCP tool: ${name}`);
|
|
80
238
|
|
|
81
|
-
if (Object.keys(args).some(
|
|
239
|
+
if (Object.keys(args).some(key => !allowed.includes(key))) {
|
|
82
240
|
throw new Error("Unexpected tool argument");
|
|
83
241
|
}
|
|
84
242
|
|
|
85
|
-
if (name === "get_forum_info") {
|
|
86
|
-
|
|
243
|
+
if (name === "get_forum_info") return {};
|
|
244
|
+
|
|
245
|
+
if (["get_post", "get_topic", "get_user",
|
|
246
|
+
"delete_topic", "restore_topic",
|
|
247
|
+
"delete_post", "restore_post"].includes(name)) {
|
|
248
|
+
const key = name.endsWith("_post") || name === "get_post"
|
|
249
|
+
? "pid" : name.endsWith("_topic") || name === "get_topic"
|
|
250
|
+
? "tid" : "uid";
|
|
251
|
+
|
|
252
|
+
requireId(args, key);
|
|
253
|
+
return { [key]: args[key] };
|
|
87
254
|
}
|
|
88
255
|
|
|
89
|
-
if (name === "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
: name === "get_topic"
|
|
93
|
-
? "tid"
|
|
94
|
-
: "uid";
|
|
256
|
+
if (name === "search_knowledge") {
|
|
257
|
+
requireText(args, "query", 2000);
|
|
258
|
+
const limit = args.limit === undefined ? 5 : args.limit;
|
|
95
259
|
|
|
96
|
-
if (!Number.isSafeInteger(
|
|
97
|
-
throw new Error(
|
|
260
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 20) {
|
|
261
|
+
throw new Error("Search limit must be from 1 to 20");
|
|
98
262
|
}
|
|
99
263
|
|
|
100
|
-
return {
|
|
264
|
+
return { query: args.query.trim(), limit };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (name === "create_topic") {
|
|
268
|
+
requireId(args, "cid");
|
|
269
|
+
requireText(args, "title", 255);
|
|
270
|
+
requireText(args, "content", 20000);
|
|
271
|
+
|
|
272
|
+
const tags = args.tags === undefined ? [] : args.tags;
|
|
273
|
+
|
|
274
|
+
if (!Array.isArray(tags) || tags.length > 10 ||
|
|
275
|
+
tags.some(tag => typeof tag !== "string" ||
|
|
276
|
+
!tag.trim() || tag.length > 32)) {
|
|
277
|
+
throw new Error("Invalid tags");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
cid: args.cid,
|
|
282
|
+
title: args.title.trim(),
|
|
283
|
+
content: args.content,
|
|
284
|
+
tags
|
|
285
|
+
};
|
|
101
286
|
}
|
|
102
287
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
args
|
|
107
|
-
|
|
108
|
-
throw new Error("Search query must contain 1 to 2000 characters");
|
|
288
|
+
if (name === "create_post" || name === "edit_post") {
|
|
289
|
+
const key = name === "create_post" ? "tid" : "pid";
|
|
290
|
+
requireId(args, key);
|
|
291
|
+
requireText(args, "content", 20000);
|
|
292
|
+
return { [key]: args[key], content: args.content };
|
|
109
293
|
}
|
|
110
294
|
|
|
111
|
-
|
|
295
|
+
if (name === "list_topics") {
|
|
296
|
+
const result = {
|
|
297
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
298
|
+
limit: pageValue(args.limit, 20, 100, "limit"),
|
|
299
|
+
sort: args.sort === undefined ? "recent" : args.sort
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (!["recent", "old", "create", "posts", "votes", "views"]
|
|
303
|
+
.includes(result.sort)) {
|
|
304
|
+
throw new Error("Invalid sort");
|
|
305
|
+
}
|
|
112
306
|
|
|
113
|
-
|
|
114
|
-
|
|
307
|
+
if (args.cid !== undefined) {
|
|
308
|
+
requireId(args, "cid");
|
|
309
|
+
result.cid = args.cid;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return result;
|
|
115
313
|
}
|
|
116
314
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
315
|
+
if (name === "delete_user_posts") {
|
|
316
|
+
requireId(args, "uid");
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
uid: args.uid,
|
|
320
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
321
|
+
limit: pageValue(args.limit, 100, 100, "limit")
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (name === "list_posts") {
|
|
326
|
+
requireId(args, "tid");
|
|
327
|
+
|
|
328
|
+
if (args.reverse !== undefined && typeof args.reverse !== "boolean") {
|
|
329
|
+
throw new Error("Invalid reverse");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
tid: args.tid,
|
|
334
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
335
|
+
limit: pageValue(args.limit, 20, 100, "limit"),
|
|
336
|
+
reverse: args.reverse ?? false
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new Error(`Unknown MCP tool: ${name}`);
|
|
121
341
|
}
|
|
122
342
|
|
|
123
343
|
async function execute(name, args = {}, api) {
|
|
@@ -127,25 +347,27 @@ async function execute(name, args = {}, api) {
|
|
|
127
347
|
|
|
128
348
|
const validated = validateArguments(name, args);
|
|
129
349
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
350
|
+
const routes = {
|
|
351
|
+
get_forum_info: ["GET", "/api/v3/integration/internal/info"],
|
|
352
|
+
get_post: ["POST", "/api/v3/integration/internal/post"],
|
|
353
|
+
get_topic: ["POST", "/api/v3/integration/internal/topic"],
|
|
354
|
+
get_user: ["POST", "/api/v3/integration/internal/user"],
|
|
355
|
+
search_knowledge: ["POST", "/api/v3/integration/internal/search"],
|
|
356
|
+
create_topic: ["POST", "/api/v3/integration/internal/topic/create"],
|
|
357
|
+
delete_topic: ["POST", "/api/v3/integration/internal/topic/delete"],
|
|
358
|
+
restore_topic: ["POST", "/api/v3/integration/internal/topic/restore"],
|
|
359
|
+
list_topics: ["POST", "/api/v3/integration/internal/topics"],
|
|
360
|
+
create_post: ["POST", "/api/v3/integration/internal/post/create"],
|
|
361
|
+
edit_post: ["POST", "/api/v3/integration/internal/post/edit"],
|
|
362
|
+
delete_post: ["POST", "/api/v3/integration/internal/post/delete"],
|
|
363
|
+
restore_post: ["POST", "/api/v3/integration/internal/post/restore"],
|
|
364
|
+
delete_user_posts: ["POST", "/api/v3/integration/internal/user/delete-posts"],
|
|
365
|
+
list_posts: ["POST", "/api/v3/integration/internal/posts"]
|
|
366
|
+
};
|
|
142
367
|
|
|
143
|
-
|
|
144
|
-
return api.request("POST", "/api/v3/integration/internal/search", validated);
|
|
368
|
+
const [method, path] = routes[name];
|
|
145
369
|
|
|
146
|
-
|
|
147
|
-
throw new Error(`Unknown MCP tool: ${name}`);
|
|
148
|
-
}
|
|
370
|
+
return api.request(method, path, validated);
|
|
149
371
|
}
|
|
150
372
|
|
|
151
373
|
function register(server, api) {
|
|
@@ -158,7 +380,7 @@ function register(server, api) {
|
|
|
158
380
|
tools: definitions
|
|
159
381
|
}));
|
|
160
382
|
|
|
161
|
-
server.setRequestHandler(CallToolRequestSchema, async
|
|
383
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
162
384
|
const name = request.params.name;
|
|
163
385
|
const args = request.params.arguments || {};
|
|
164
386
|
|
|
@@ -166,22 +388,18 @@ function register(server, api) {
|
|
|
166
388
|
const result = await execute(name, args, api);
|
|
167
389
|
|
|
168
390
|
return {
|
|
169
|
-
content: [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
]
|
|
391
|
+
content: [{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: JSON.stringify(result, null, 2)
|
|
394
|
+
}]
|
|
175
395
|
};
|
|
176
396
|
} catch (error) {
|
|
177
397
|
return {
|
|
178
398
|
isError: true,
|
|
179
|
-
content: [
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
]
|
|
399
|
+
content: [{
|
|
400
|
+
type: "text",
|
|
401
|
+
text: error.message || "Tool execution failed"
|
|
402
|
+
}]
|
|
185
403
|
};
|
|
186
404
|
}
|
|
187
405
|
});
|