nodebb-plugin-integration 0.4.2 → 0.4.4

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/src/mcp/tools.js CHANGED
@@ -1,654 +1,195 @@
1
1
  "use strict";
2
2
 
3
- /*
4
-
5
- NodeBB Integration Layer
6
-
7
-
8
- MCP Tools
9
-
10
-
11
-
12
- */
13
-
14
-
15
-
16
-
17
- const Meta =
18
- require("../nodebb/meta");
19
-
20
- const Posts =
21
- require("../nodebb/posts");
22
-
23
- const Topics =
24
- require("../nodebb/topics");
25
-
26
- const Users =
27
- require("../nodebb/users");
28
-
29
- const Search =
30
- require("../ai/search");
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
3
  const definitions = [
45
-
46
- {
47
-
48
-
49
- name:
50
-
51
- "get_forum_info",
52
-
53
-
54
-
55
- description:
56
-
57
- "Get NodeBB forum information",
58
-
59
-
60
-
61
- inputSchema:
62
-
63
4
  {
64
-
65
- type:
66
-
67
- "object",
68
-
69
- properties: {}
70
-
71
- }
72
-
73
-
74
-
75
- },
76
-
77
-
78
-
79
-
80
-
81
- {
82
-
83
-
84
- name:
85
-
86
- "get_post",
87
-
88
-
89
-
90
- description:
91
-
92
- "Get NodeBB post by pid",
93
-
94
-
95
-
96
- inputSchema:
97
-
5
+ name: "get_forum_info",
6
+ description: "Get public NodeBB forum information",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {},
10
+ additionalProperties: false
11
+ }
12
+ },
98
13
  {
99
-
100
- type:
101
-
102
- "object",
103
-
104
-
105
- properties:
106
-
107
- {
108
-
109
-
110
- pid:
111
-
112
- {
113
-
114
- type:
115
-
116
- "number"
117
-
118
- }
119
-
120
-
121
- },
122
-
123
-
124
- required:
125
-
126
- [
127
-
128
- "pid"
129
-
130
- ]
131
-
132
- }
133
-
134
-
135
-
136
- },
137
-
138
-
139
-
140
-
141
-
142
- {
143
-
144
-
145
- name:
146
-
147
- "get_topic",
148
-
149
-
150
-
151
- description:
152
-
153
- "Get NodeBB topic by tid",
154
-
155
-
156
-
157
- inputSchema:
158
-
14
+ name: "get_post",
15
+ description: "Get a forum post by its post ID. Access permissions are checked by NodeBB.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ pid: { type: "integer", minimum: 1 }
20
+ },
21
+ required: ["pid"],
22
+ additionalProperties: false
23
+ }
24
+ },
159
25
  {
160
-
161
- type:
162
-
163
- "object",
164
-
165
-
166
- properties:
167
-
168
- {
169
-
170
-
171
- tid:
172
-
173
- {
174
-
175
- type:
176
-
177
- "number"
178
-
179
- }
180
-
181
-
182
- },
183
-
184
-
185
- required:
186
-
187
- [
188
-
189
- "tid"
190
-
191
- ]
192
-
193
- }
194
-
195
-
196
-
197
- },
198
-
199
-
200
-
201
-
202
-
203
- {
204
-
205
-
206
- name:
207
-
208
- "get_user",
209
-
210
-
211
-
212
- description:
213
-
214
- "Get NodeBB user by uid",
215
-
216
-
217
-
218
- inputSchema:
219
-
26
+ name: "get_topic",
27
+ description: "Get a forum topic by its topic ID. Access permissions are checked by NodeBB.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ tid: { type: "integer", minimum: 1 }
32
+ },
33
+ required: ["tid"],
34
+ additionalProperties: false
35
+ }
36
+ },
220
37
  {
221
-
222
- type:
223
-
224
- "object",
225
-
226
-
227
- properties:
228
-
229
- {
230
-
231
-
232
- uid:
233
-
234
- {
235
-
236
- type:
237
-
238
- "number"
239
-
240
- }
241
-
242
-
243
- },
244
-
245
-
246
- required:
247
-
248
- [
249
-
250
- "uid"
251
-
252
- ]
253
-
254
- }
255
-
256
-
257
-
258
- },
259
-
260
-
261
-
262
-
263
-
264
- {
265
-
266
-
267
- name:
268
-
269
- "search_knowledge",
270
-
271
-
272
-
273
- description:
274
-
275
- "Semantic search in forum knowledge base",
276
-
277
-
278
-
279
- inputSchema:
280
-
38
+ name: "get_user",
39
+ description: "Get public profile information for a forum user",
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ uid: { type: "integer", minimum: 1 }
44
+ },
45
+ required: ["uid"],
46
+ additionalProperties: false
47
+ }
48
+ },
281
49
  {
282
-
283
- type:
284
-
285
- "object",
286
-
287
-
288
- properties:
289
-
290
- {
291
-
292
-
293
- query:
294
-
295
- {
296
-
297
- type:
298
-
299
- "string"
300
-
50
+ name: "search_knowledge",
51
+ description: "Search the forum knowledge base using semantic search",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ query: { type: "string", minLength: 1, maxLength: 2000 },
56
+ limit: { type: "integer", minimum: 1, maximum: 20 }
301
57
  },
302
-
303
-
304
- limit:
305
-
306
- {
307
-
308
- type:
309
-
310
- "number"
311
-
312
- }
313
-
314
-
315
- },
316
-
317
-
318
- required:
319
-
320
- [
321
-
322
- "query"
323
-
324
- ]
325
-
58
+ required: ["query"],
59
+ additionalProperties: false
60
+ }
326
61
  }
327
-
328
-
329
-
330
- }
331
-
332
-
333
-
334
-
335
62
  ];
336
63
 
64
+ function validateArguments(name, args) {
65
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
66
+ throw new Error("Tool arguments must be an object");
67
+ }
337
68
 
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];
338
76
 
77
+ if (!allowed) {
78
+ throw new Error(`Unknown MCP tool: ${name}`);
79
+ }
339
80
 
81
+ if (Object.keys(args).some((key) => !allowed.includes(key))) {
82
+ throw new Error("Unexpected tool argument");
83
+ }
340
84
 
85
+ if (name === "get_forum_info") {
86
+ return {};
87
+ }
341
88
 
89
+ if (name === "get_post" || name === "get_topic" || name === "get_user") {
90
+ const key = name === "get_post"
91
+ ? "pid"
92
+ : name === "get_topic"
93
+ ? "tid"
94
+ : "uid";
342
95
 
96
+ if (!Number.isSafeInteger(args[key]) || args[key] < 1) {
97
+ throw new Error(`Invalid ${key}`);
98
+ }
343
99
 
100
+ return { [key]: args[key] };
101
+ }
344
102
 
103
+ if (
104
+ typeof args.query !== "string" ||
105
+ !args.query.trim() ||
106
+ args.query.length > 2000
107
+ ) {
108
+ throw new Error("Search query must contain 1 to 2000 characters");
109
+ }
345
110
 
111
+ const limit = args.limit === undefined ? 5 : args.limit;
346
112
 
113
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 20) {
114
+ throw new Error("Search limit must be an integer from 1 to 20");
115
+ }
347
116
 
348
-
349
- async function execute(
350
-
351
- name,
352
-
353
- args = {}
354
-
355
- ) {
356
-
357
-
358
-
359
-
360
- switch(name) {
361
-
362
-
363
-
364
- case "get_forum_info":
365
-
366
-
367
-
368
- return Meta.getInfo();
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
- case "get_post":
377
-
378
-
379
-
380
- return Posts.getPost(
381
-
382
- args.pid
383
-
384
- );
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
- case "get_topic":
393
-
394
-
395
-
396
- return Topics.getTopic(
397
-
398
- args.tid
399
-
400
- );
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
- case "get_user":
409
-
410
-
411
-
412
- return Users.getUser(
413
-
414
- args.uid
415
-
416
- );
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
- case "search_knowledge":
425
-
426
-
427
-
428
- return Search.searchText(
429
-
430
- args.query,
431
-
432
- args.limit || 5
433
-
434
- );
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
- default:
443
-
444
-
445
-
446
- throw new Error(
447
-
448
- `Unknown MCP tool: ${name}`
449
-
450
- );
451
-
452
-
453
-
454
- }
455
-
456
-
457
-
458
-
459
- }
460
-
461
-
462
-
463
-
464
-
465
-
466
-
467
-
468
-
469
-
470
-
471
-
472
-
473
- function register(server) {
474
-
475
-
476
-
477
-
478
- /*
479
- * MCP SDK 1.x compatibility
480
- */
481
-
482
-
483
- if(
484
-
485
- typeof server.setRequestHandler !== "function"
486
-
487
- ) {
488
-
489
-
490
-
491
- throw new Error(
492
-
493
- "Invalid MCP server instance"
494
-
495
- );
496
-
497
-
117
+ return {
118
+ query: args.query.trim(),
119
+ limit
120
+ };
498
121
  }
499
122
 
500
-
501
-
502
-
503
-
504
-
505
-
506
-
507
- server.setRequestHandler(
508
-
509
- {
510
-
511
- method:
512
-
513
- "tools/list"
514
-
515
-
516
- },
517
-
518
-
519
- async () => ({
520
-
521
-
522
- tools:
523
-
524
- definitions
525
-
526
-
527
- })
528
-
529
- );
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
- server.setRequestHandler(
539
-
540
- {
541
-
542
- method:
543
-
544
- "tools/call"
545
-
546
-
547
- },
548
-
549
-
550
- async request => {
551
-
552
-
553
-
554
- const result =
555
-
556
- await execute(
557
-
558
- request.params.name,
559
-
560
- request.params.arguments || {}
561
-
562
- );
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
- return {
571
-
572
-
573
- content:
574
-
575
- [
576
-
577
- {
578
-
579
-
580
- type:
581
-
582
- "text",
583
-
584
-
585
-
586
- text:
587
-
588
- JSON.stringify(
589
-
590
- result,
591
-
592
- null,
593
-
594
- 2
595
-
596
- )
597
-
598
-
599
- }
600
-
601
- ]
602
-
603
- };
604
-
605
-
606
-
123
+ async function execute(name, args = {}, api) {
124
+ if (!api || typeof api.request !== "function") {
125
+ throw new Error("NodeBB API client is not configured");
607
126
  }
608
127
 
609
- );
610
-
611
-
612
-
128
+ const validated = validateArguments(name, args);
613
129
 
130
+ switch (name) {
131
+ case "get_forum_info":
132
+ return api.request("GET", "/api/v3/integration/internal/info");
614
133
 
134
+ case "get_post":
135
+ return api.request("POST", "/api/v3/integration/internal/post", validated);
615
136
 
137
+ case "get_topic":
138
+ return api.request("POST", "/api/v3/integration/internal/topic", validated);
616
139
 
140
+ case "get_user":
141
+ return api.request("POST", "/api/v3/integration/internal/user", validated);
617
142
 
618
- console.log(
619
-
620
- "[MCP] tools registered"
621
-
622
- );
623
-
624
-
625
-
143
+ case "search_knowledge":
144
+ return api.request("POST", "/api/v3/integration/internal/search", validated);
626
145
 
146
+ default:
147
+ throw new Error(`Unknown MCP tool: ${name}`);
148
+ }
627
149
  }
628
150
 
629
-
630
-
631
-
632
-
633
-
634
-
635
-
636
-
637
-
638
-
639
-
151
+ function register(server, api) {
152
+ const {
153
+ ListToolsRequestSchema,
154
+ CallToolRequestSchema
155
+ } = require("@modelcontextprotocol/sdk/types.js");
156
+
157
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
158
+ tools: definitions
159
+ }));
160
+
161
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
162
+ const name = request.params.name;
163
+ const args = request.params.arguments || {};
164
+
165
+ try {
166
+ const result = await execute(name, args, api);
167
+
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: JSON.stringify(result, null, 2)
173
+ }
174
+ ]
175
+ };
176
+ } catch (error) {
177
+ return {
178
+ isError: true,
179
+ content: [
180
+ {
181
+ type: "text",
182
+ text: error.message || "Tool execution failed"
183
+ }
184
+ ]
185
+ };
186
+ }
187
+ });
188
+ }
640
189
 
641
190
  module.exports = {
642
-
643
- register,
644
-
645
-
646
- execute,
647
-
648
-
649
- definitions
650
-
651
-
652
-
653
-
654
- };
191
+ definitions,
192
+ validateArguments,
193
+ execute,
194
+ register
195
+ };