@wumx-labs/noxaeapi-mcp 0.0.0-stage → 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/dist/index.js ADDED
@@ -0,0 +1,2129 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/config.ts
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ var CONFIG_PATH = join(homedir(), ".noxaeapi-cli", "config.json");
12
+ function loadCliConfig() {
13
+ if (!existsSync(CONFIG_PATH)) {
14
+ return { current: null, profiles: {} };
15
+ }
16
+ try {
17
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
18
+ } catch {
19
+ return { current: null, profiles: {} };
20
+ }
21
+ }
22
+ function parseArgs() {
23
+ const args = process.argv.slice(2);
24
+ const parsed = {};
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ if (arg === "--hub") {
28
+ parsed.hub = true;
29
+ } else if (arg.startsWith("--") && i + 1 < args.length && !args[i + 1].startsWith("--")) {
30
+ parsed[arg.slice(2)] = args[++i];
31
+ } else if (arg.startsWith("--")) {
32
+ parsed[arg.slice(2)] = true;
33
+ }
34
+ }
35
+ return parsed;
36
+ }
37
+ function resolveConfig() {
38
+ const args = parseArgs();
39
+ const env = process.env;
40
+ if (args.url && typeof args.url === "string") {
41
+ return {
42
+ baseUrl: args.url,
43
+ apiKey: typeof args.key === "string" ? args.key : void 0,
44
+ hub: args.hub === true,
45
+ profileName: "custom (via flags)"
46
+ };
47
+ }
48
+ const cliConfig = loadCliConfig();
49
+ const requestedProfileName = (typeof args.profile === "string" ? args.profile : null) ?? env.NOXAEAPI_PROFILE ?? cliConfig.current;
50
+ if (requestedProfileName && cliConfig.profiles[requestedProfileName]) {
51
+ const prof = cliConfig.profiles[requestedProfileName];
52
+ return {
53
+ baseUrl: prof.baseUrl,
54
+ apiKey: prof.apiKey,
55
+ hub: Boolean(prof.hub),
56
+ profileName: requestedProfileName
57
+ };
58
+ }
59
+ const envUrl = env.NOXAEAPI_HUB_BASE_URL ?? env.NOXAEAPI_BASE_URL;
60
+ if (envUrl) {
61
+ return {
62
+ baseUrl: envUrl,
63
+ apiKey: env.NOXAEAPI_HUB_KEY ?? env.NOXAEAPI_KEY,
64
+ hub: env.NOXAEAPI_HUB === "true" || Boolean(env.NOXAEAPI_HUB_BASE_URL),
65
+ profileName: "environment variables"
66
+ };
67
+ }
68
+ throw new Error(
69
+ "No NoxAeApi configuration found. Please run `noxae connect` in noxaeapi-cli, or provide --url <url>, or set NOXAEAPI_BASE_URL env variable."
70
+ );
71
+ }
72
+
73
+ // src/tools/network.ts
74
+ import { z } from "zod";
75
+
76
+ // src/client.ts
77
+ import {
78
+ NoxAeApiClient,
79
+ NoxAeApiNetworkHubClient
80
+ } from "@wumx-labs/noxaeapi-sdk";
81
+ var activeSession = null;
82
+ var cachedDefaultNode = null;
83
+ function getSession() {
84
+ if (activeSession) return activeSession;
85
+ const config = resolveConfig();
86
+ if (config.hub) {
87
+ const hubClient = new NoxAeApiNetworkHubClient({
88
+ baseUrl: config.baseUrl,
89
+ apiKey: config.apiKey
90
+ });
91
+ const singleClient = new NoxAeApiClient({
92
+ baseUrl: config.baseUrl,
93
+ apiKey: config.apiKey
94
+ });
95
+ activeSession = {
96
+ config,
97
+ hubClient,
98
+ singleClient,
99
+ isHub: true,
100
+ getClient(nodeId) {
101
+ if (nodeId && hubClient) {
102
+ return hubClient.node(nodeId);
103
+ }
104
+ return singleClient;
105
+ },
106
+ async getDefaultNodeId() {
107
+ if (cachedDefaultNode && cachedDefaultNode.expires > Date.now()) {
108
+ return cachedDefaultNode.id;
109
+ }
110
+ try {
111
+ const nodes = await hubClient.network.statusAll();
112
+ if (Array.isArray(nodes) && nodes.length > 0) {
113
+ const online = nodes.find((n) => n.online || n.status === "online") || nodes[0];
114
+ const id = online.name || online.id || online.nodeId;
115
+ if (id) {
116
+ cachedDefaultNode = { id: String(id), expires: Date.now() + 2e4 };
117
+ return String(id);
118
+ }
119
+ }
120
+ } catch {
121
+ }
122
+ return void 0;
123
+ },
124
+ async resolveTargetClient(nodeId) {
125
+ if (nodeId) {
126
+ return { client: hubClient.node(nodeId), effectiveNodeId: nodeId };
127
+ }
128
+ const defaultNode = await this.getDefaultNodeId();
129
+ if (defaultNode) {
130
+ return { client: hubClient.node(defaultNode), effectiveNodeId: defaultNode };
131
+ }
132
+ return { client: singleClient };
133
+ }
134
+ };
135
+ } else {
136
+ const singleClient = new NoxAeApiClient({
137
+ baseUrl: config.baseUrl,
138
+ apiKey: config.apiKey
139
+ });
140
+ activeSession = {
141
+ config,
142
+ hubClient: null,
143
+ singleClient,
144
+ isHub: false,
145
+ getClient() {
146
+ return singleClient;
147
+ },
148
+ async getDefaultNodeId() {
149
+ return void 0;
150
+ },
151
+ async resolveTargetClient() {
152
+ return { client: singleClient };
153
+ }
154
+ };
155
+ }
156
+ return activeSession;
157
+ }
158
+
159
+ // src/tools/network.ts
160
+ function registerNetworkTools(server) {
161
+ server.tool(
162
+ "network_status_all",
163
+ "Get live status, player count, and health/TPS of all Minecraft server nodes registered in the network hub or aggregator.",
164
+ {},
165
+ async () => {
166
+ const session = getSession();
167
+ try {
168
+ let result;
169
+ if (session.isHub && session.hubClient) {
170
+ result = await session.hubClient.network.statusAll();
171
+ } else if (session.singleClient) {
172
+ result = await session.singleClient.network.statusAll();
173
+ }
174
+ return {
175
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
176
+ };
177
+ } catch (err) {
178
+ return {
179
+ isError: true,
180
+ content: [
181
+ {
182
+ type: "text",
183
+ text: `Failed to fetch network status: ${err instanceof Error ? err.message : String(err)}`
184
+ }
185
+ ]
186
+ };
187
+ }
188
+ }
189
+ );
190
+ server.tool(
191
+ "network_node_status",
192
+ "Get detailed status and health of a specific server node in the network by its nodeId.",
193
+ {
194
+ nodeId: z.string().describe("The ID of the server node, e.g. 'lobby', 'survival-1'")
195
+ },
196
+ async ({ nodeId }) => {
197
+ const session = getSession();
198
+ try {
199
+ let result;
200
+ if (session.isHub && session.hubClient) {
201
+ result = await session.hubClient.network.status(nodeId);
202
+ } else if (session.singleClient) {
203
+ result = await session.singleClient.network.status(nodeId);
204
+ }
205
+ return {
206
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
207
+ };
208
+ } catch (err) {
209
+ return {
210
+ isError: true,
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: `Failed to get status for node '${nodeId}': ${err instanceof Error ? err.message : String(err)}`
215
+ }
216
+ ]
217
+ };
218
+ }
219
+ }
220
+ );
221
+ server.tool(
222
+ "network_broadcast",
223
+ "Broadcast a chat announcement across all servers in the network.",
224
+ {
225
+ message: z.string().describe("The message text to broadcast to all players in the network")
226
+ },
227
+ async ({ message }) => {
228
+ const session = getSession();
229
+ try {
230
+ let result;
231
+ if (session.isHub && session.hubClient) {
232
+ result = await session.hubClient.network.broadcast(message);
233
+ } else if (session.singleClient) {
234
+ result = await session.singleClient.network.broadcast(message);
235
+ }
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: `Network broadcast sent successfully: ${JSON.stringify(result ?? "OK")}`
241
+ }
242
+ ]
243
+ };
244
+ } catch (err) {
245
+ return {
246
+ isError: true,
247
+ content: [
248
+ {
249
+ type: "text",
250
+ text: `Failed to broadcast message: ${err instanceof Error ? err.message : String(err)}`
251
+ }
252
+ ]
253
+ };
254
+ }
255
+ }
256
+ );
257
+ server.tool(
258
+ "network_find_player",
259
+ "Find which server node a player is currently playing on in the proxy/network.",
260
+ {
261
+ player: z.string().describe("Username or UUID of the player")
262
+ },
263
+ async ({ player }) => {
264
+ const session = getSession();
265
+ try {
266
+ let result;
267
+ if (session.isHub && session.hubClient) {
268
+ result = await session.hubClient.network.find(player);
269
+ } else {
270
+ throw new Error("network_find_player requires a Velocity Network Hub connection.");
271
+ }
272
+ return {
273
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
274
+ };
275
+ } catch (err) {
276
+ return {
277
+ isError: true,
278
+ content: [
279
+ {
280
+ type: "text",
281
+ text: `Failed to find player '${player}': ${err instanceof Error ? err.message : String(err)}`
282
+ }
283
+ ]
284
+ };
285
+ }
286
+ }
287
+ );
288
+ server.tool(
289
+ "network_send_player",
290
+ "Send/transfer a player from their current server to a target server node.",
291
+ {
292
+ player: z.string().describe("Username or UUID of the player"),
293
+ destinationServer: z.string().describe("Destination server name, e.g. 'survival', 'lobby'")
294
+ },
295
+ async ({ player, destinationServer }) => {
296
+ const session = getSession();
297
+ try {
298
+ if (!session.isHub || !session.hubClient) {
299
+ throw new Error("network_send_player requires a Velocity Network Hub connection.");
300
+ }
301
+ const result = await session.hubClient.network.sendPlayer(player, destinationServer);
302
+ return {
303
+ content: [
304
+ {
305
+ type: "text",
306
+ text: `Player ${player} sent to ${destinationServer}: ${JSON.stringify(result ?? "OK")}`
307
+ }
308
+ ]
309
+ };
310
+ } catch (err) {
311
+ return {
312
+ isError: true,
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: `Failed to send player: ${err instanceof Error ? err.message : String(err)}`
317
+ }
318
+ ]
319
+ };
320
+ }
321
+ }
322
+ );
323
+ }
324
+
325
+ // src/tools/server.ts
326
+ import { z as z2 } from "zod";
327
+ async function resolveUuid(client, playerOrUuid) {
328
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(playerOrUuid)) {
329
+ return { uuid: playerOrUuid, name: playerOrUuid };
330
+ }
331
+ try {
332
+ const res = await client.players.resolve(playerOrUuid);
333
+ return { uuid: res.uuid, name: playerOrUuid };
334
+ } catch {
335
+ return { uuid: playerOrUuid, name: playerOrUuid };
336
+ }
337
+ }
338
+ function registerServerTools(server) {
339
+ server.tool(
340
+ "server_get_status",
341
+ "Get TPS (1m, 5m, 15m), CPU load, memory/RAM usage, uptime, and version of the Minecraft server.",
342
+ {
343
+ nodeId: z2.string().optional().describe("Target server node ID (if connected via Velocity hub, e.g. 'survival', 'lobby')")
344
+ },
345
+ async ({ nodeId }) => {
346
+ const session = getSession();
347
+ try {
348
+ if (session.isHub && session.hubClient && nodeId) {
349
+ const [info2, tps2, health2] = await Promise.allSettled([
350
+ session.hubClient.network.forward(nodeId, "GET", "server"),
351
+ session.hubClient.network.forward(nodeId, "GET", "server/tps"),
352
+ session.hubClient.network.forward(nodeId, "GET", "server/health")
353
+ ]);
354
+ return {
355
+ content: [
356
+ {
357
+ type: "text",
358
+ text: JSON.stringify(
359
+ {
360
+ nodeId,
361
+ info: info2.status === "fulfilled" ? info2.value : null,
362
+ tps: tps2.status === "fulfilled" ? tps2.value : null,
363
+ health: health2.status === "fulfilled" ? health2.value : null
364
+ },
365
+ null,
366
+ 2
367
+ )
368
+ }
369
+ ]
370
+ };
371
+ }
372
+ const client = session.getClient();
373
+ const [info, tps, health] = await Promise.allSettled([
374
+ client.server.info(),
375
+ client.server.tps(),
376
+ client.server.health()
377
+ ]);
378
+ return {
379
+ content: [
380
+ {
381
+ type: "text",
382
+ text: JSON.stringify(
383
+ {
384
+ info: info.status === "fulfilled" ? info.value : null,
385
+ tps: tps.status === "fulfilled" ? tps.value : null,
386
+ health: health.status === "fulfilled" ? health.value : null
387
+ },
388
+ null,
389
+ 2
390
+ )
391
+ }
392
+ ]
393
+ };
394
+ } catch (err) {
395
+ return {
396
+ isError: true,
397
+ content: [
398
+ {
399
+ type: "text",
400
+ text: `Failed to get server status: ${err instanceof Error ? err.message : String(err)}`
401
+ }
402
+ ]
403
+ };
404
+ }
405
+ }
406
+ );
407
+ server.tool(
408
+ "server_dispatch_command",
409
+ "Execute a Minecraft console command on the server (e.g. 'say Hello', 'whitelist on', 'gamemode creative Steve').",
410
+ {
411
+ command: z2.string().describe("The console command to execute, without leading slash"),
412
+ nodeId: z2.string().optional().describe("Target server node ID (if on Velocity hub, e.g. 'survival', 'lobby')")
413
+ },
414
+ async ({ command, nodeId }) => {
415
+ const session = getSession();
416
+ try {
417
+ if (session.isHub && session.hubClient && nodeId) {
418
+ const result2 = await session.hubClient.network.forward(nodeId, "POST", "server/exec", {
419
+ body: { command },
420
+ form: true
421
+ });
422
+ return {
423
+ content: [
424
+ {
425
+ type: "text",
426
+ text: `Command executed on [${nodeId}]: ${JSON.stringify(result2 ?? "Command dispatched successfully")}`
427
+ }
428
+ ]
429
+ };
430
+ }
431
+ const client = session.getClient();
432
+ const result = await client.server.dispatchCommand(command);
433
+ return {
434
+ content: [
435
+ {
436
+ type: "text",
437
+ text: `Command executed: ${JSON.stringify(result ?? "Command dispatched successfully")}`
438
+ }
439
+ ]
440
+ };
441
+ } catch (err) {
442
+ return {
443
+ isError: true,
444
+ content: [
445
+ {
446
+ type: "text",
447
+ text: `Failed to dispatch command: ${err instanceof Error ? err.message : String(err)}`
448
+ }
449
+ ]
450
+ };
451
+ }
452
+ }
453
+ );
454
+ server.tool(
455
+ "server_broadcast",
456
+ "Broadcast a chat message to all players on the Minecraft server.",
457
+ {
458
+ message: z2.string().describe("The message text to broadcast"),
459
+ nodeId: z2.string().optional().describe("Target server node ID (if on Velocity hub)")
460
+ },
461
+ async ({ message, nodeId }) => {
462
+ const session = getSession();
463
+ try {
464
+ if (session.isHub && session.hubClient && nodeId) {
465
+ const result2 = await session.hubClient.network.forward(nodeId, "POST", "chat/broadcast", {
466
+ body: { message },
467
+ form: true
468
+ });
469
+ return {
470
+ content: [{ type: "text", text: `Broadcast sent to [${nodeId}]: ${JSON.stringify(result2 ?? "OK")}` }]
471
+ };
472
+ }
473
+ const client = session.getClient();
474
+ const result = await client.server.broadcast(message);
475
+ return {
476
+ content: [{ type: "text", text: `Broadcast sent: ${JSON.stringify(result ?? "OK")}` }]
477
+ };
478
+ } catch (err) {
479
+ return {
480
+ isError: true,
481
+ content: [
482
+ {
483
+ type: "text",
484
+ text: `Failed to broadcast message: ${err instanceof Error ? err.message : String(err)}`
485
+ }
486
+ ]
487
+ };
488
+ }
489
+ }
490
+ );
491
+ server.tool(
492
+ "server_get_whitelist",
493
+ "Get the current list of whitelisted players.",
494
+ {
495
+ nodeId: z2.string().optional().describe("Target server node ID")
496
+ },
497
+ async ({ nodeId }) => {
498
+ const session = getSession();
499
+ try {
500
+ if (session.isHub && session.hubClient && nodeId) {
501
+ const result2 = await session.hubClient.network.forward(nodeId, "GET", "server/whitelist");
502
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
503
+ }
504
+ const client = session.getClient();
505
+ const result = await client.server.getWhitelist();
506
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
507
+ } catch (err) {
508
+ return {
509
+ isError: true,
510
+ content: [
511
+ {
512
+ type: "text",
513
+ text: `Failed to get whitelist: ${err instanceof Error ? err.message : String(err)}`
514
+ }
515
+ ]
516
+ };
517
+ }
518
+ }
519
+ );
520
+ server.tool(
521
+ "server_whitelist_add",
522
+ "Add a player to the server whitelist by username or UUID.",
523
+ {
524
+ player: z2.string().describe("Player username or UUID to whitelist"),
525
+ nodeId: z2.string().optional().describe("Target server node ID")
526
+ },
527
+ async ({ player, nodeId }) => {
528
+ const session = getSession();
529
+ try {
530
+ const client = session.getClient();
531
+ const { uuid, name } = await resolveUuid(client, player);
532
+ if (session.isHub && session.hubClient && nodeId) {
533
+ await session.hubClient.network.forward(nodeId, "POST", "server/whitelist", {
534
+ body: { uuid, name },
535
+ form: true
536
+ });
537
+ return {
538
+ content: [{ type: "text", text: `Successfully whitelisted player '${player}' on [${nodeId}].` }]
539
+ };
540
+ }
541
+ await client.server.addToWhitelist(uuid, name);
542
+ return {
543
+ content: [{ type: "text", text: `Successfully whitelisted player '${player}'.` }]
544
+ };
545
+ } catch (err) {
546
+ return {
547
+ isError: true,
548
+ content: [
549
+ {
550
+ type: "text",
551
+ text: `Failed to add player to whitelist: ${err instanceof Error ? err.message : String(err)}`
552
+ }
553
+ ]
554
+ };
555
+ }
556
+ }
557
+ );
558
+ server.tool(
559
+ "server_whitelist_remove",
560
+ "Remove a player from the server whitelist by username or UUID.",
561
+ {
562
+ player: z2.string().describe("Player username or UUID to remove from whitelist"),
563
+ nodeId: z2.string().optional().describe("Target server node ID")
564
+ },
565
+ async ({ player, nodeId }) => {
566
+ const session = getSession();
567
+ try {
568
+ const client = session.getClient();
569
+ const { uuid } = await resolveUuid(client, player);
570
+ if (session.isHub && session.hubClient && nodeId) {
571
+ await session.hubClient.network.forward(nodeId, "DELETE", "server/whitelist", {
572
+ query: { uuid }
573
+ });
574
+ return {
575
+ content: [{ type: "text", text: `Successfully removed player '${player}' from whitelist on [${nodeId}].` }]
576
+ };
577
+ }
578
+ await client.server.removeFromWhitelist(uuid);
579
+ return {
580
+ content: [{ type: "text", text: `Successfully removed player '${player}' from whitelist.` }]
581
+ };
582
+ } catch (err) {
583
+ return {
584
+ isError: true,
585
+ content: [
586
+ {
587
+ type: "text",
588
+ text: `Failed to remove player from whitelist: ${err instanceof Error ? err.message : String(err)}`
589
+ }
590
+ ]
591
+ };
592
+ }
593
+ }
594
+ );
595
+ server.tool(
596
+ "server_get_ops",
597
+ "List all server operators (OPs).",
598
+ {
599
+ nodeId: z2.string().optional().describe("Target server node ID")
600
+ },
601
+ async ({ nodeId }) => {
602
+ const session = getSession();
603
+ try {
604
+ if (session.isHub && session.hubClient && nodeId) {
605
+ const result2 = await session.hubClient.network.forward(nodeId, "GET", "server/ops");
606
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
607
+ }
608
+ const client = session.getClient();
609
+ const result = await client.server.getOps();
610
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
611
+ } catch (err) {
612
+ return {
613
+ isError: true,
614
+ content: [
615
+ {
616
+ type: "text",
617
+ text: `Failed to get operators: ${err instanceof Error ? err.message : String(err)}`
618
+ }
619
+ ]
620
+ };
621
+ }
622
+ }
623
+ );
624
+ server.tool(
625
+ "server_op_player",
626
+ "Grant operator (OP) status to a player.",
627
+ {
628
+ player: z2.string().describe("Player username or UUID to promote to operator"),
629
+ nodeId: z2.string().optional().describe("Target server node ID")
630
+ },
631
+ async ({ player, nodeId }) => {
632
+ const session = getSession();
633
+ try {
634
+ const client = session.getClient();
635
+ const { uuid } = await resolveUuid(client, player);
636
+ if (session.isHub && session.hubClient && nodeId) {
637
+ await session.hubClient.network.forward(nodeId, "POST", "server/ops", {
638
+ body: { playerUuid: uuid },
639
+ form: true
640
+ });
641
+ return {
642
+ content: [{ type: "text", text: `Successfully granted OP status to '${player}' on [${nodeId}].` }]
643
+ };
644
+ }
645
+ await client.server.opPlayer(uuid);
646
+ return {
647
+ content: [{ type: "text", text: `Successfully granted OP status to '${player}'.` }]
648
+ };
649
+ } catch (err) {
650
+ return {
651
+ isError: true,
652
+ content: [
653
+ {
654
+ type: "text",
655
+ text: `Failed to OP player: ${err instanceof Error ? err.message : String(err)}`
656
+ }
657
+ ]
658
+ };
659
+ }
660
+ }
661
+ );
662
+ server.tool(
663
+ "server_deop_player",
664
+ "Revoke operator (OP) status from a player.",
665
+ {
666
+ player: z2.string().describe("Player username or UUID to demote from operator"),
667
+ nodeId: z2.string().optional().describe("Target server node ID")
668
+ },
669
+ async ({ player, nodeId }) => {
670
+ const session = getSession();
671
+ try {
672
+ const client = session.getClient();
673
+ const { uuid } = await resolveUuid(client, player);
674
+ if (session.isHub && session.hubClient && nodeId) {
675
+ await session.hubClient.network.forward(nodeId, "DELETE", "server/ops", {
676
+ query: { playerUuid: uuid }
677
+ });
678
+ return {
679
+ content: [{ type: "text", text: `Successfully revoked OP status from '${player}' on [${nodeId}].` }]
680
+ };
681
+ }
682
+ await client.server.deopPlayer(uuid);
683
+ return {
684
+ content: [{ type: "text", text: `Successfully revoked OP status from '${player}'.` }]
685
+ };
686
+ } catch (err) {
687
+ return {
688
+ isError: true,
689
+ content: [
690
+ {
691
+ type: "text",
692
+ text: `Failed to de-OP player: ${err instanceof Error ? err.message : String(err)}`
693
+ }
694
+ ]
695
+ };
696
+ }
697
+ }
698
+ );
699
+ server.tool(
700
+ "server_get_logs",
701
+ "Tail recent console log lines from the server.",
702
+ {
703
+ lines: z2.number().optional().describe("Number of log lines to retrieve (default 50)"),
704
+ nodeId: z2.string().optional().describe("Target server node ID")
705
+ },
706
+ async ({ lines, nodeId }) => {
707
+ const session = getSession();
708
+ try {
709
+ if (session.isHub && session.hubClient && nodeId) {
710
+ const result2 = await session.hubClient.network.forward(nodeId, "GET", "server/logs", {
711
+ query: { lines: lines ?? 50 }
712
+ });
713
+ return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
714
+ }
715
+ const client = session.getClient();
716
+ const result = await client.server.getLogs(lines ?? 50);
717
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
718
+ } catch (err) {
719
+ return {
720
+ isError: true,
721
+ content: [
722
+ {
723
+ type: "text",
724
+ text: `Failed to get server logs: ${err instanceof Error ? err.message : String(err)}`
725
+ }
726
+ ]
727
+ };
728
+ }
729
+ }
730
+ );
731
+ server.tool(
732
+ "server_restart",
733
+ "Restart the Minecraft server process (requires privileged write key).",
734
+ {
735
+ nodeId: z2.string().optional().describe("Target server node ID")
736
+ },
737
+ async ({ nodeId }) => {
738
+ const session = getSession();
739
+ try {
740
+ if (session.isHub && session.hubClient && nodeId) {
741
+ await session.hubClient.network.forward(nodeId, "POST", "server/restart");
742
+ return { content: [{ type: "text", text: `Server [${nodeId}] restart initiated.` }] };
743
+ }
744
+ const client = session.getClient();
745
+ await client.server.restart();
746
+ return { content: [{ type: "text", text: "Server restart initiated." }] };
747
+ } catch (err) {
748
+ return {
749
+ isError: true,
750
+ content: [
751
+ {
752
+ type: "text",
753
+ text: `Failed to restart server: ${err instanceof Error ? err.message : String(err)}`
754
+ }
755
+ ]
756
+ };
757
+ }
758
+ }
759
+ );
760
+ server.tool(
761
+ "server_ban_ip",
762
+ "Ban an IP address from connecting to the server.",
763
+ {
764
+ ip: z2.string().describe("The IP address to ban"),
765
+ reason: z2.string().optional().describe("Reason for banning this IP"),
766
+ nodeId: z2.string().optional().describe("Target server node ID")
767
+ },
768
+ async ({ ip, reason, nodeId }) => {
769
+ const session = getSession();
770
+ try {
771
+ if (session.isHub && session.hubClient && nodeId) {
772
+ await session.hubClient.network.forward(nodeId, "POST", "server/ban-ip", {
773
+ body: { ip, reason },
774
+ form: true
775
+ });
776
+ return { content: [{ type: "text", text: `Successfully banned IP '${ip}' on [${nodeId}].` }] };
777
+ }
778
+ const client = session.getClient();
779
+ await client.server.banIp(ip, reason);
780
+ return { content: [{ type: "text", text: `Successfully banned IP '${ip}'.` }] };
781
+ } catch (err) {
782
+ return {
783
+ isError: true,
784
+ content: [
785
+ {
786
+ type: "text",
787
+ text: `Failed to ban IP: ${err instanceof Error ? err.message : String(err)}`
788
+ }
789
+ ]
790
+ };
791
+ }
792
+ }
793
+ );
794
+ server.tool(
795
+ "server_tell_player",
796
+ "Send a private whisper/message to a specific player.",
797
+ {
798
+ player: z2.string().describe("Player username or UUID to message"),
799
+ message: z2.string().describe("The private message content"),
800
+ nodeId: z2.string().optional().describe("Target server node ID")
801
+ },
802
+ async ({ player, message, nodeId }) => {
803
+ const session = getSession();
804
+ try {
805
+ const client = session.getClient();
806
+ const { uuid } = await resolveUuid(client, player);
807
+ if (session.isHub && session.hubClient && nodeId) {
808
+ await session.hubClient.network.forward(nodeId, "POST", "chat/tell", {
809
+ body: { playerUuid: uuid, message },
810
+ form: true
811
+ });
812
+ return { content: [{ type: "text", text: `Message sent to '${player}' on [${nodeId}].` }] };
813
+ }
814
+ await client.server.tell(uuid, message);
815
+ return { content: [{ type: "text", text: `Message sent to '${player}'.` }] };
816
+ } catch (err) {
817
+ return {
818
+ isError: true,
819
+ content: [
820
+ {
821
+ type: "text",
822
+ text: `Failed to send message: ${err instanceof Error ? err.message : String(err)}`
823
+ }
824
+ ]
825
+ };
826
+ }
827
+ }
828
+ );
829
+ }
830
+
831
+ // src/tools/players.ts
832
+ import { z as z3 } from "zod";
833
+ async function resolveUuid2(client, playerOrUuid) {
834
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(playerOrUuid)) {
835
+ return playerOrUuid;
836
+ }
837
+ try {
838
+ const res = await client.players.resolve(playerOrUuid);
839
+ return res.uuid;
840
+ } catch {
841
+ return playerOrUuid;
842
+ }
843
+ }
844
+ function registerPlayerTools(server) {
845
+ server.tool(
846
+ "players_list_online",
847
+ "List all players currently online on the Minecraft server (or proxy network), including usernames, UUIDs, ping, and health.",
848
+ {},
849
+ async () => {
850
+ const session = getSession();
851
+ try {
852
+ if (session.isHub && session.hubClient) {
853
+ const result = await session.hubClient.network.players();
854
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
855
+ }
856
+ const client = session.getClient();
857
+ const players = await client.players.list();
858
+ return {
859
+ content: [{ type: "text", text: JSON.stringify(players, null, 2) }]
860
+ };
861
+ } catch (err) {
862
+ return {
863
+ isError: true,
864
+ content: [
865
+ {
866
+ type: "text",
867
+ text: `Failed to list online players: ${err instanceof Error ? err.message : String(err)}`
868
+ }
869
+ ]
870
+ };
871
+ }
872
+ }
873
+ );
874
+ server.tool(
875
+ "players_get_info",
876
+ "Get detailed stats, health, and info for a player (online or offline) by username or UUID.",
877
+ {
878
+ player: z3.string().describe("Player username or UUID")
879
+ },
880
+ async ({ player }) => {
881
+ const session = getSession();
882
+ try {
883
+ const client = session.getClient();
884
+ const uuid = await resolveUuid2(client, player);
885
+ const [info, stats] = await Promise.allSettled([
886
+ client.players.get(uuid),
887
+ client.players.getStats(uuid)
888
+ ]);
889
+ return {
890
+ content: [
891
+ {
892
+ type: "text",
893
+ text: JSON.stringify(
894
+ {
895
+ playerInfo: info.status === "fulfilled" ? info.value : null,
896
+ stats: stats.status === "fulfilled" ? stats.value : null
897
+ },
898
+ null,
899
+ 2
900
+ )
901
+ }
902
+ ]
903
+ };
904
+ } catch (err) {
905
+ return {
906
+ isError: true,
907
+ content: [
908
+ {
909
+ type: "text",
910
+ text: `Failed to get player info for '${player}': ${err instanceof Error ? err.message : String(err)}`
911
+ }
912
+ ]
913
+ };
914
+ }
915
+ }
916
+ );
917
+ server.tool(
918
+ "players_kick",
919
+ "Kick an online player from the Minecraft server.",
920
+ {
921
+ player: z3.string().describe("Player username or UUID to kick"),
922
+ reason: z3.string().optional().describe("Kick reason displayed to the player")
923
+ },
924
+ async ({ player, reason }) => {
925
+ const session = getSession();
926
+ try {
927
+ const client = session.getClient();
928
+ const uuid = await resolveUuid2(client, player);
929
+ await client.players.kick(uuid, reason);
930
+ return {
931
+ content: [
932
+ {
933
+ type: "text",
934
+ text: `Successfully kicked player '${player}'${reason ? ` for reason: ${reason}` : ""}.`
935
+ }
936
+ ]
937
+ };
938
+ } catch (err) {
939
+ return {
940
+ isError: true,
941
+ content: [
942
+ {
943
+ type: "text",
944
+ text: `Failed to kick player '${player}': ${err instanceof Error ? err.message : String(err)}`
945
+ }
946
+ ]
947
+ };
948
+ }
949
+ }
950
+ );
951
+ server.tool(
952
+ "players_ban",
953
+ "Ban a player from the Minecraft server.",
954
+ {
955
+ player: z3.string().describe("Player username or UUID to ban"),
956
+ reason: z3.string().optional().describe("Ban reason"),
957
+ expiry: z3.string().optional().describe("ISO-8601 expiry date for temp bans (e.g. '2026-12-31T23:59:59Z'). Omit for permanent ban.")
958
+ },
959
+ async ({ player, reason, expiry }) => {
960
+ const session = getSession();
961
+ try {
962
+ const client = session.getClient();
963
+ const uuid = await resolveUuid2(client, player);
964
+ await client.players.ban(uuid, reason, expiry);
965
+ return {
966
+ content: [
967
+ {
968
+ type: "text",
969
+ text: `Successfully banned player '${player}'${expiry ? ` until ${expiry}` : " permanently"}.`
970
+ }
971
+ ]
972
+ };
973
+ } catch (err) {
974
+ return {
975
+ isError: true,
976
+ content: [
977
+ {
978
+ type: "text",
979
+ text: `Failed to ban player '${player}': ${err instanceof Error ? err.message : String(err)}`
980
+ }
981
+ ]
982
+ };
983
+ }
984
+ }
985
+ );
986
+ server.tool(
987
+ "players_unban",
988
+ "Unban a previously banned player.",
989
+ {
990
+ player: z3.string().describe("Player username or UUID to unban")
991
+ },
992
+ async ({ player }) => {
993
+ const session = getSession();
994
+ try {
995
+ const client = session.getClient();
996
+ const uuid = await resolveUuid2(client, player);
997
+ await client.players.unban(uuid);
998
+ return {
999
+ content: [{ type: "text", text: `Successfully unbanned player '${player}'.` }]
1000
+ };
1001
+ } catch (err) {
1002
+ return {
1003
+ isError: true,
1004
+ content: [
1005
+ {
1006
+ type: "text",
1007
+ text: `Failed to unban player '${player}': ${err instanceof Error ? err.message : String(err)}`
1008
+ }
1009
+ ]
1010
+ };
1011
+ }
1012
+ }
1013
+ );
1014
+ server.tool(
1015
+ "players_set_gamemode",
1016
+ "Change the gamemode of an online player (survival, creative, adventure, spectator).",
1017
+ {
1018
+ player: z3.string().describe("Player username or UUID"),
1019
+ gamemode: z3.enum(["survival", "creative", "adventure", "spectator"]).describe("Gamemode to set")
1020
+ },
1021
+ async ({ player, gamemode }) => {
1022
+ const session = getSession();
1023
+ try {
1024
+ const client = session.getClient();
1025
+ const uuid = await resolveUuid2(client, player);
1026
+ await client.players.setGamemode(uuid, gamemode);
1027
+ return {
1028
+ content: [{ type: "text", text: `Successfully set gamemode of '${player}' to ${gamemode}.` }]
1029
+ };
1030
+ } catch (err) {
1031
+ return {
1032
+ isError: true,
1033
+ content: [
1034
+ {
1035
+ type: "text",
1036
+ text: `Failed to set gamemode: ${err instanceof Error ? err.message : String(err)}`
1037
+ }
1038
+ ]
1039
+ };
1040
+ }
1041
+ }
1042
+ );
1043
+ }
1044
+
1045
+ // src/tools/luckperms.ts
1046
+ import { z as z4 } from "zod";
1047
+ async function resolveUuid3(client, playerOrUuid) {
1048
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(playerOrUuid)) {
1049
+ return playerOrUuid;
1050
+ }
1051
+ try {
1052
+ const res = await client.players.resolve(playerOrUuid);
1053
+ return res.uuid;
1054
+ } catch {
1055
+ return playerOrUuid;
1056
+ }
1057
+ }
1058
+ function registerLuckPermsTools(server) {
1059
+ server.tool(
1060
+ "luckperms_get_player_info",
1061
+ "Get all groups and permission nodes assigned to a player in LuckPerms.",
1062
+ {
1063
+ player: z4.string().describe("Player username or UUID")
1064
+ },
1065
+ async ({ player }) => {
1066
+ const session = getSession();
1067
+ try {
1068
+ const client = session.getClient();
1069
+ const uuid = await resolveUuid3(client, player);
1070
+ const [primaryGroup, groups, permissions] = await Promise.allSettled([
1071
+ client.luckperms.getPlayerPrimaryGroup(uuid),
1072
+ client.luckperms.getPlayerGroups(uuid),
1073
+ client.luckperms.getPlayerPermissions(uuid)
1074
+ ]);
1075
+ return {
1076
+ content: [
1077
+ {
1078
+ type: "text",
1079
+ text: JSON.stringify(
1080
+ {
1081
+ player,
1082
+ uuid,
1083
+ primaryGroup: primaryGroup.status === "fulfilled" ? primaryGroup.value : null,
1084
+ groups: groups.status === "fulfilled" ? groups.value : null,
1085
+ permissions: permissions.status === "fulfilled" ? permissions.value : null
1086
+ },
1087
+ null,
1088
+ 2
1089
+ )
1090
+ }
1091
+ ]
1092
+ };
1093
+ } catch (err) {
1094
+ return {
1095
+ isError: true,
1096
+ content: [
1097
+ {
1098
+ type: "text",
1099
+ text: `Failed to get LuckPerms info for '${player}': ${err instanceof Error ? err.message : String(err)}`
1100
+ }
1101
+ ]
1102
+ };
1103
+ }
1104
+ }
1105
+ );
1106
+ server.tool(
1107
+ "luckperms_add_player_permission",
1108
+ "Grant a permission node to a player in LuckPerms (permanent or temporary).",
1109
+ {
1110
+ player: z4.string().describe("Player username or UUID"),
1111
+ permission: z4.string().describe("Permission node (e.g. 'essentials.fly', 'worldedit.*')"),
1112
+ value: z4.boolean().optional().describe("Permission value: true to grant, false to negate (default: true)"),
1113
+ expiry: z4.string().optional().describe("Expiry in ISO-8601 format (e.g. '2026-12-31T00:00:00Z'). Omit for permanent.")
1114
+ },
1115
+ async ({ player, permission, value, expiry }) => {
1116
+ const session = getSession();
1117
+ try {
1118
+ const client = session.getClient();
1119
+ const uuid = await resolveUuid3(client, player);
1120
+ await client.luckperms.addPlayerPermission(uuid, permission, { value, expiry });
1121
+ return {
1122
+ content: [
1123
+ {
1124
+ type: "text",
1125
+ text: `Successfully granted permission '${permission}' to player '${player}' (value=${value ?? true}${expiry ? `, expires=${expiry}` : ""}).`
1126
+ }
1127
+ ]
1128
+ };
1129
+ } catch (err) {
1130
+ return {
1131
+ isError: true,
1132
+ content: [
1133
+ {
1134
+ type: "text",
1135
+ text: `Failed to add permission: ${err instanceof Error ? err.message : String(err)}`
1136
+ }
1137
+ ]
1138
+ };
1139
+ }
1140
+ }
1141
+ );
1142
+ server.tool(
1143
+ "luckperms_remove_player_permission",
1144
+ "Remove a permission node from a player in LuckPerms.",
1145
+ {
1146
+ player: z4.string().describe("Player username or UUID"),
1147
+ permission: z4.string().describe("Permission node to remove")
1148
+ },
1149
+ async ({ player, permission }) => {
1150
+ const session = getSession();
1151
+ try {
1152
+ const client = session.getClient();
1153
+ const uuid = await resolveUuid3(client, player);
1154
+ await client.luckperms.removePlayerPermission(uuid, permission);
1155
+ return {
1156
+ content: [
1157
+ {
1158
+ type: "text",
1159
+ text: `Successfully removed permission '${permission}' from player '${player}'.`
1160
+ }
1161
+ ]
1162
+ };
1163
+ } catch (err) {
1164
+ return {
1165
+ isError: true,
1166
+ content: [
1167
+ {
1168
+ type: "text",
1169
+ text: `Failed to remove permission: ${err instanceof Error ? err.message : String(err)}`
1170
+ }
1171
+ ]
1172
+ };
1173
+ }
1174
+ }
1175
+ );
1176
+ server.tool(
1177
+ "luckperms_set_player_primary_group",
1178
+ "Set the primary LuckPerms group for a player (e.g. 'default', 'vip', 'moderator', 'admin').",
1179
+ {
1180
+ player: z4.string().describe("Player username or UUID"),
1181
+ group: z4.string().describe("The name of the LuckPerms group")
1182
+ },
1183
+ async ({ player, group }) => {
1184
+ const session = getSession();
1185
+ try {
1186
+ const client = session.getClient();
1187
+ const uuid = await resolveUuid3(client, player);
1188
+ await client.luckperms.setPlayerPrimaryGroup(uuid, group);
1189
+ return {
1190
+ content: [
1191
+ {
1192
+ type: "text",
1193
+ text: `Successfully set primary group of '${player}' to '${group}'.`
1194
+ }
1195
+ ]
1196
+ };
1197
+ } catch (err) {
1198
+ return {
1199
+ isError: true,
1200
+ content: [
1201
+ {
1202
+ type: "text",
1203
+ text: `Failed to set primary group: ${err instanceof Error ? err.message : String(err)}`
1204
+ }
1205
+ ]
1206
+ };
1207
+ }
1208
+ }
1209
+ );
1210
+ server.tool(
1211
+ "luckperms_list_groups",
1212
+ "List all available permission groups configured in LuckPerms.",
1213
+ {},
1214
+ async () => {
1215
+ const session = getSession();
1216
+ try {
1217
+ const client = session.getClient();
1218
+ const groups = await client.luckperms.getGroups();
1219
+ return {
1220
+ content: [{ type: "text", text: JSON.stringify(groups, null, 2) }]
1221
+ };
1222
+ } catch (err) {
1223
+ return {
1224
+ isError: true,
1225
+ content: [
1226
+ {
1227
+ type: "text",
1228
+ text: `Failed to list groups: ${err instanceof Error ? err.message : String(err)}`
1229
+ }
1230
+ ]
1231
+ };
1232
+ }
1233
+ }
1234
+ );
1235
+ }
1236
+
1237
+ // src/tools/economy.ts
1238
+ import { z as z5 } from "zod";
1239
+ async function resolveUuid4(client, playerOrUuid) {
1240
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(playerOrUuid)) {
1241
+ return playerOrUuid;
1242
+ }
1243
+ try {
1244
+ if (client.players?.resolve) {
1245
+ const res = await client.players.resolve(playerOrUuid);
1246
+ return res.uuid;
1247
+ }
1248
+ } catch {
1249
+ }
1250
+ return playerOrUuid;
1251
+ }
1252
+ function registerEconomyTools(server) {
1253
+ server.tool(
1254
+ "economy_info",
1255
+ "Get information about the economy provider/plugin installed on the server.",
1256
+ {
1257
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1258
+ },
1259
+ async ({ nodeId }) => {
1260
+ const session = getSession();
1261
+ try {
1262
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1263
+ const result = await client.economy.info();
1264
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1265
+ ` : "";
1266
+ return {
1267
+ content: [{ type: "text", text: prefix + JSON.stringify(result, null, 2) }]
1268
+ };
1269
+ } catch (err) {
1270
+ return {
1271
+ isError: true,
1272
+ content: [
1273
+ {
1274
+ type: "text",
1275
+ text: `Failed to get economy info: ${err instanceof Error ? err.message : String(err)}`
1276
+ }
1277
+ ]
1278
+ };
1279
+ }
1280
+ }
1281
+ );
1282
+ server.tool(
1283
+ "economy_get_balance",
1284
+ "Get the economy balance for a player (supports Vault default money and custom currencies).",
1285
+ {
1286
+ player: z5.string().describe("Player username or UUID"),
1287
+ currency: z5.string().optional().describe("Currency ID (e.g. 'coins', 'candy'). Leave empty for primary Vault balance."),
1288
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1289
+ },
1290
+ async ({ player, currency, nodeId }) => {
1291
+ const session = getSession();
1292
+ try {
1293
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1294
+ const uuid = await resolveUuid4(client, player);
1295
+ let result;
1296
+ if (currency && client.economy.getCurrencyBalance) {
1297
+ result = await client.economy.getCurrencyBalance(currency, uuid);
1298
+ } else {
1299
+ result = await client.economy.getBalance(uuid);
1300
+ }
1301
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1302
+ ` : "";
1303
+ return {
1304
+ content: [{ type: "text", text: prefix + JSON.stringify(result, null, 2) }]
1305
+ };
1306
+ } catch (err) {
1307
+ return {
1308
+ isError: true,
1309
+ content: [
1310
+ {
1311
+ type: "text",
1312
+ text: `Failed to get balance for '${player}': ${err instanceof Error ? err.message : String(err)}`
1313
+ }
1314
+ ]
1315
+ };
1316
+ }
1317
+ }
1318
+ );
1319
+ server.tool(
1320
+ "economy_get_top",
1321
+ "Get the leaderboard of richest players by Vault/server default balance. Runs in < 0.1ms with zero lag.",
1322
+ {
1323
+ limit: z5.number().optional().describe("Maximum number of players to return (default 10, max 100)"),
1324
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1325
+ },
1326
+ async ({ limit = 10, nodeId }) => {
1327
+ const session = getSession();
1328
+ try {
1329
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1330
+ const result = await client.economy.getTopBalance(limit);
1331
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1332
+ ` : "";
1333
+ return {
1334
+ content: [{ type: "text", text: prefix + JSON.stringify(result, null, 2) }]
1335
+ };
1336
+ } catch (err) {
1337
+ return {
1338
+ isError: true,
1339
+ content: [
1340
+ {
1341
+ type: "text",
1342
+ text: `Failed to get top balances: ${err instanceof Error ? err.message : String(err)}`
1343
+ }
1344
+ ]
1345
+ };
1346
+ }
1347
+ }
1348
+ );
1349
+ server.tool(
1350
+ "economy_list_currencies",
1351
+ "List all custom currencies configured on the server (e.g. coins, candy, gems).",
1352
+ {
1353
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1354
+ },
1355
+ async ({ nodeId }) => {
1356
+ const session = getSession();
1357
+ try {
1358
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1359
+ if (!client.economy?.listCurrencies) {
1360
+ return { content: [{ type: "text", text: "Multi-currency module not available." }] };
1361
+ }
1362
+ const currencies = await client.economy.listCurrencies();
1363
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1364
+ ` : "";
1365
+ return {
1366
+ content: [{ type: "text", text: prefix + JSON.stringify(currencies, null, 2) }]
1367
+ };
1368
+ } catch (err) {
1369
+ return {
1370
+ isError: true,
1371
+ content: [
1372
+ {
1373
+ type: "text",
1374
+ text: `Failed to list currencies: ${err instanceof Error ? err.message : String(err)}`
1375
+ }
1376
+ ]
1377
+ };
1378
+ }
1379
+ }
1380
+ );
1381
+ server.tool(
1382
+ "economy_get_currency_top",
1383
+ "Get the leaderboard of richest players for a specific custom currency (e.g. coins, candy).",
1384
+ {
1385
+ currency: z5.string().describe("The currency ID (e.g. 'coins', 'candy')"),
1386
+ limit: z5.number().optional().describe("Maximum number of players to return (default 10, max 100)"),
1387
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1388
+ },
1389
+ async ({ currency, limit = 10, nodeId }) => {
1390
+ const session = getSession();
1391
+ try {
1392
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1393
+ if (!client.economy?.getCurrencyTop) {
1394
+ throw new Error("Currency top balances not supported on this economy provider.");
1395
+ }
1396
+ const result = await client.economy.getCurrencyTop(currency, limit);
1397
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1398
+ ` : "";
1399
+ return {
1400
+ content: [{ type: "text", text: prefix + JSON.stringify(result, null, 2) }]
1401
+ };
1402
+ } catch (err) {
1403
+ return {
1404
+ isError: true,
1405
+ content: [
1406
+ {
1407
+ type: "text",
1408
+ text: `Failed to get currency top for '${currency}': ${err instanceof Error ? err.message : String(err)}`
1409
+ }
1410
+ ]
1411
+ };
1412
+ }
1413
+ }
1414
+ );
1415
+ server.tool(
1416
+ "economy_pay",
1417
+ "Deposit / add money to a player's balance.",
1418
+ {
1419
+ player: z5.string().describe("Player username or UUID"),
1420
+ amount: z5.number().positive().describe("Amount to deposit"),
1421
+ currency: z5.string().optional().describe("Optional currency ID for multi-currency plugins (leave blank for Vault default)"),
1422
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1423
+ },
1424
+ async ({ player, amount, currency, nodeId }) => {
1425
+ const session = getSession();
1426
+ try {
1427
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1428
+ const uuid = await resolveUuid4(client, player);
1429
+ if (currency && client.economy.payCurrency) {
1430
+ await client.economy.payCurrency(currency, uuid, amount);
1431
+ } else {
1432
+ await client.economy.pay(uuid, amount);
1433
+ }
1434
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}] ` : "";
1435
+ return {
1436
+ content: [
1437
+ {
1438
+ type: "text",
1439
+ text: `${prefix}Successfully added ${amount} ${currency || "money"} to '${player}' balance.`
1440
+ }
1441
+ ]
1442
+ };
1443
+ } catch (err) {
1444
+ return {
1445
+ isError: true,
1446
+ content: [
1447
+ {
1448
+ type: "text",
1449
+ text: `Failed to pay '${player}': ${err instanceof Error ? err.message : String(err)}`
1450
+ }
1451
+ ]
1452
+ };
1453
+ }
1454
+ }
1455
+ );
1456
+ server.tool(
1457
+ "economy_debit",
1458
+ "Withdraw / deduct money from a player's balance.",
1459
+ {
1460
+ player: z5.string().describe("Player username or UUID"),
1461
+ amount: z5.number().positive().describe("Amount to deduct"),
1462
+ currency: z5.string().optional().describe("Optional currency ID for multi-currency plugins (leave blank for Vault default)"),
1463
+ nodeId: z5.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1464
+ },
1465
+ async ({ player, amount, currency, nodeId }) => {
1466
+ const session = getSession();
1467
+ try {
1468
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1469
+ const uuid = await resolveUuid4(client, player);
1470
+ if (currency && client.economy.debitCurrency) {
1471
+ await client.economy.debitCurrency(currency, uuid, amount);
1472
+ } else {
1473
+ await client.economy.debit(uuid, amount);
1474
+ }
1475
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}] ` : "";
1476
+ return {
1477
+ content: [
1478
+ {
1479
+ type: "text",
1480
+ text: `${prefix}Successfully deducted ${amount} ${currency || "money"} from '${player}' balance.`
1481
+ }
1482
+ ]
1483
+ };
1484
+ } catch (err) {
1485
+ return {
1486
+ isError: true,
1487
+ content: [
1488
+ {
1489
+ type: "text",
1490
+ text: `Failed to debit '${player}': ${err instanceof Error ? err.message : String(err)}`
1491
+ }
1492
+ ]
1493
+ };
1494
+ }
1495
+ }
1496
+ );
1497
+ }
1498
+
1499
+ // src/tools/worlds.ts
1500
+ import { z as z6 } from "zod";
1501
+ function registerWorldTools(server) {
1502
+ server.tool(
1503
+ "worlds_list",
1504
+ "List all worlds loaded on the Minecraft server (including name, environment, difficulty, time, and weather).",
1505
+ {},
1506
+ async () => {
1507
+ const session = getSession();
1508
+ try {
1509
+ const client = session.getClient();
1510
+ const worlds = await client.worlds.list();
1511
+ return {
1512
+ content: [{ type: "text", text: JSON.stringify(worlds, null, 2) }]
1513
+ };
1514
+ } catch (err) {
1515
+ return {
1516
+ isError: true,
1517
+ content: [
1518
+ {
1519
+ type: "text",
1520
+ text: `Failed to list worlds: ${err instanceof Error ? err.message : String(err)}`
1521
+ }
1522
+ ]
1523
+ };
1524
+ }
1525
+ }
1526
+ );
1527
+ server.tool(
1528
+ "worlds_set_time",
1529
+ "Set the in-game time of a world (0 = day, 6000 = noon, 13000 = night, 18000 = midnight).",
1530
+ {
1531
+ worldUuid: z6.string().describe("World UUID or name"),
1532
+ time: z6.number().min(0).max(24e3).describe("Time in ticks (0-24000)")
1533
+ },
1534
+ async ({ worldUuid, time }) => {
1535
+ const session = getSession();
1536
+ try {
1537
+ const client = session.getClient();
1538
+ await client.worlds.setTime(worldUuid, time);
1539
+ return {
1540
+ content: [{ type: "text", text: `Successfully set world '${worldUuid}' time to ${time} ticks.` }]
1541
+ };
1542
+ } catch (err) {
1543
+ return {
1544
+ isError: true,
1545
+ content: [
1546
+ {
1547
+ type: "text",
1548
+ text: `Failed to set world time: ${err instanceof Error ? err.message : String(err)}`
1549
+ }
1550
+ ]
1551
+ };
1552
+ }
1553
+ }
1554
+ );
1555
+ server.tool(
1556
+ "worlds_set_weather",
1557
+ "Set the weather condition in a world (clear, rain, thunder).",
1558
+ {
1559
+ worldUuid: z6.string().describe("World UUID or name"),
1560
+ weather: z6.enum(["clear", "rain", "thunder"]).describe("Weather state")
1561
+ },
1562
+ async ({ worldUuid, weather }) => {
1563
+ const session = getSession();
1564
+ try {
1565
+ const client = session.getClient();
1566
+ await client.worlds.setWeather(worldUuid, weather);
1567
+ return {
1568
+ content: [{ type: "text", text: `Successfully set world '${worldUuid}' weather to ${weather}.` }]
1569
+ };
1570
+ } catch (err) {
1571
+ return {
1572
+ isError: true,
1573
+ content: [
1574
+ {
1575
+ type: "text",
1576
+ text: `Failed to set weather: ${err instanceof Error ? err.message : String(err)}`
1577
+ }
1578
+ ]
1579
+ };
1580
+ }
1581
+ }
1582
+ );
1583
+ server.tool(
1584
+ "worlds_get_entities",
1585
+ "Get count and breakdown of entities (mobs, items, players) currently loaded in a world.",
1586
+ {
1587
+ worldUuid: z6.string().describe("World UUID or name")
1588
+ },
1589
+ async ({ worldUuid }) => {
1590
+ const session = getSession();
1591
+ try {
1592
+ const client = session.getClient();
1593
+ const result = await client.worlds.getEntities(worldUuid);
1594
+ return {
1595
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1596
+ };
1597
+ } catch (err) {
1598
+ return {
1599
+ isError: true,
1600
+ content: [
1601
+ {
1602
+ type: "text",
1603
+ text: `Failed to get world entities: ${err instanceof Error ? err.message : String(err)}`
1604
+ }
1605
+ ]
1606
+ };
1607
+ }
1608
+ }
1609
+ );
1610
+ }
1611
+
1612
+ // src/tools/plugins.ts
1613
+ import { z as z7 } from "zod";
1614
+ function registerPluginTools(server) {
1615
+ server.tool(
1616
+ "plugins_list",
1617
+ "List all plugins installed on the Minecraft server and their status (enabled/disabled, version).",
1618
+ {},
1619
+ async () => {
1620
+ const session = getSession();
1621
+ try {
1622
+ const client = session.getClient();
1623
+ const plugins = await client.plugins.list();
1624
+ return {
1625
+ content: [{ type: "text", text: JSON.stringify(plugins, null, 2) }]
1626
+ };
1627
+ } catch (err) {
1628
+ return {
1629
+ isError: true,
1630
+ content: [
1631
+ {
1632
+ type: "text",
1633
+ text: `Failed to list plugins: ${err instanceof Error ? err.message : String(err)}`
1634
+ }
1635
+ ]
1636
+ };
1637
+ }
1638
+ }
1639
+ );
1640
+ server.tool(
1641
+ "plugins_get_info",
1642
+ "Get detailed metadata and description for a specific plugin installed on the server.",
1643
+ {
1644
+ pluginName: z7.string().describe("Name of the plugin, e.g. 'LuckPerms', 'Essentials'")
1645
+ },
1646
+ async ({ pluginName }) => {
1647
+ const session = getSession();
1648
+ try {
1649
+ const client = session.getClient();
1650
+ const plugin = await client.plugins.get(pluginName);
1651
+ return {
1652
+ content: [{ type: "text", text: JSON.stringify(plugin, null, 2) }]
1653
+ };
1654
+ } catch (err) {
1655
+ return {
1656
+ isError: true,
1657
+ content: [
1658
+ {
1659
+ type: "text",
1660
+ text: `Failed to get info for plugin '${pluginName}': ${err instanceof Error ? err.message : String(err)}`
1661
+ }
1662
+ ]
1663
+ };
1664
+ }
1665
+ }
1666
+ );
1667
+ }
1668
+
1669
+ // src/tools/dispatch.ts
1670
+ import { z as z8 } from "zod";
1671
+ var SKIP_KEYS = /* @__PURE__ */ new Set(["connect"]);
1672
+ function buildResourceMap(client) {
1673
+ const map = {};
1674
+ for (const [key, value] of Object.entries(client)) {
1675
+ if (SKIP_KEYS.has(key)) continue;
1676
+ if (!value || typeof value !== "object") continue;
1677
+ const proto = Object.getPrototypeOf(value);
1678
+ if (!proto || proto === Object.prototype) continue;
1679
+ const actions = Object.getOwnPropertyNames(proto).filter(
1680
+ (name) => name !== "constructor" && typeof value[name] === "function"
1681
+ );
1682
+ if (actions.length === 0) continue;
1683
+ map[key] = actions;
1684
+ }
1685
+ return map;
1686
+ }
1687
+ function registerDispatchTools(server) {
1688
+ server.tool(
1689
+ "noxae_list_modules",
1690
+ "List all available SDK resource modules and their executable actions (e.g. essentials, worldguard, coreprotect, discordsrv, authme, shopgui, etc.).",
1691
+ {},
1692
+ async () => {
1693
+ const session = getSession();
1694
+ try {
1695
+ const client = session.getClient();
1696
+ const map = buildResourceMap(client);
1697
+ return {
1698
+ content: [
1699
+ {
1700
+ type: "text",
1701
+ text: JSON.stringify(map, null, 2)
1702
+ }
1703
+ ]
1704
+ };
1705
+ } catch (err) {
1706
+ return {
1707
+ isError: true,
1708
+ content: [
1709
+ {
1710
+ type: "text",
1711
+ text: `Failed to list modules: ${err instanceof Error ? err.message : String(err)}`
1712
+ }
1713
+ ]
1714
+ };
1715
+ }
1716
+ }
1717
+ );
1718
+ server.tool(
1719
+ "noxae_call_action",
1720
+ "Universal executor for any NoxAeApi module action. Allows calling all integration modules shown in CLI (e.g. worldguard, coreprotect, essentials, betterteams, discordsrv, authme, playerpoints, mythicmobs, shopgui, etc.).",
1721
+ {
1722
+ module: z8.string().describe("The module name (e.g. 'worldguard', 'essentials', 'coreprotect', 'betterteams', 'discordsrv', 'authme', 'shopgui')"),
1723
+ action: z8.string().describe("The method name to execute on the module (use noxae_list_modules to see available actions)"),
1724
+ args: z8.array(z8.unknown()).optional().describe("Arguments to pass to the method"),
1725
+ nodeId: z8.string().optional().describe("Target server node ID (if connected via Velocity hub, e.g. 'survival', 'lobby')")
1726
+ },
1727
+ async ({ module: modName, action, args = [], nodeId }) => {
1728
+ const session = getSession();
1729
+ try {
1730
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1731
+ const mod = client[modName];
1732
+ if (!mod) {
1733
+ throw new Error(`Module '${modName}' not found on client.`);
1734
+ }
1735
+ const fn = mod[action];
1736
+ if (typeof fn !== "function") {
1737
+ throw new Error(`Action '${action}' not found on module '${modName}'.`);
1738
+ }
1739
+ const result = await fn.apply(mod, args);
1740
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1741
+ ` : "";
1742
+ return {
1743
+ content: [
1744
+ {
1745
+ type: "text",
1746
+ text: prefix + JSON.stringify(result ?? "Success (void)", null, 2)
1747
+ }
1748
+ ]
1749
+ };
1750
+ } catch (err) {
1751
+ return {
1752
+ isError: true,
1753
+ content: [
1754
+ {
1755
+ type: "text",
1756
+ text: `Failed to execute ${modName}.${action}(): ${err instanceof Error ? err.message : String(err)}`
1757
+ }
1758
+ ]
1759
+ };
1760
+ }
1761
+ }
1762
+ );
1763
+ }
1764
+
1765
+ // src/tools/leaderboard.ts
1766
+ import { z as z9 } from "zod";
1767
+ var NOISY_BOARD_PATTERNS = [
1768
+ /^ajlb:.*prevent-type.*/i,
1769
+ /^ajlb:citizens\..*/i,
1770
+ /^ajlb:commands\..*/i,
1771
+ /^ajlb:loading\..*/i,
1772
+ /^ajlb:updater\..*/i,
1773
+ /^ajlb:formatted\..*/i,
1774
+ /^ajlb:no-data\..*/i,
1775
+ /^ajlb:signs\..*/i,
1776
+ /^ajlb:no-player-skin.*/i,
1777
+ /^ajlb:time\..*/i,
1778
+ /^ajlb:[0-9a-z]$/i,
1779
+ /^ajlb:java\..*/i,
1780
+ /^ajlb:essentials\..*/i
1781
+ ];
1782
+ function registerLeaderboardTools(server) {
1783
+ server.tool(
1784
+ "leaderboards_list",
1785
+ "List all available gameplay leaderboard rankings on the server (e.g. player:kills, player:deaths, player:playtime, mcmmo:power, auraskills:power). Automatically filters out internal plugin noise. Note: For economy and balances, prefer the dedicated economy_* tools.",
1786
+ {
1787
+ includeNoisy: z9.boolean().optional().describe("If true, includes internal plugin placeholder keys (default false)"),
1788
+ nodeId: z9.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1789
+ },
1790
+ async ({ includeNoisy = false, nodeId }) => {
1791
+ const session = getSession();
1792
+ try {
1793
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1794
+ if (!client?.leaderboards?.list) {
1795
+ throw new Error("Leaderboards module is not available on this connection.");
1796
+ }
1797
+ const rawList = await client.leaderboards.list();
1798
+ let filtered = rawList;
1799
+ if (!includeNoisy && Array.isArray(rawList)) {
1800
+ filtered = rawList.filter((entry) => {
1801
+ const id = entry?.id || "";
1802
+ return !NOISY_BOARD_PATTERNS.some((pattern) => pattern.test(id));
1803
+ });
1804
+ }
1805
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1806
+ ` : "";
1807
+ return {
1808
+ content: [
1809
+ {
1810
+ type: "text",
1811
+ text: prefix + JSON.stringify(filtered, null, 2)
1812
+ }
1813
+ ]
1814
+ };
1815
+ } catch (err) {
1816
+ return {
1817
+ isError: true,
1818
+ content: [
1819
+ {
1820
+ type: "text",
1821
+ text: `Failed to list leaderboards: ${err instanceof Error ? err.message : String(err)}`
1822
+ }
1823
+ ]
1824
+ };
1825
+ }
1826
+ }
1827
+ );
1828
+ server.tool(
1829
+ "leaderboards_get_top",
1830
+ "Get top ranking players for a specific gameplay leaderboard (e.g. 'player:kills', 'player:deaths', 'player:playtime', 'mcmmo:power', 'auraskills:power'). Note: For money/balances, always use economy_get_top or economy_get_currency_top.",
1831
+ {
1832
+ board: z9.string().describe("The leaderboard source ID (e.g. 'player:kills', 'player:deaths', 'player:playtime', 'mcmmo:power')"),
1833
+ limit: z9.number().optional().describe("Number of top players to return (default 10, max 100)"),
1834
+ nodeId: z9.string().optional().describe("Target server node ID (e.g. 'CSMP', 'LOBBY') when connected via Velocity hub")
1835
+ },
1836
+ async ({ board, limit = 10, nodeId }) => {
1837
+ const session = getSession();
1838
+ try {
1839
+ const { client, effectiveNodeId } = await session.resolveTargetClient(nodeId);
1840
+ const prefix = effectiveNodeId ? `[Target Node: @${effectiveNodeId}]
1841
+ ` : "";
1842
+ const cleanBoard = board.trim();
1843
+ if (cleanBoard === "economy:vault" || cleanBoard === "economy:money" || cleanBoard === "money" || cleanBoard === "vault" || cleanBoard === "balance" || cleanBoard === "ajlb:vault_eco_balance") {
1844
+ if (client.economy?.getTopBalance) {
1845
+ const entries2 = await client.economy.getTopBalance(limit);
1846
+ const formatted = (entries2 || []).map((e, idx) => ({
1847
+ rank: idx + 1,
1848
+ name: e.name || e.uuid,
1849
+ value: typeof e.balance === "number" ? e.balance : e.value,
1850
+ uuid: e.uuid
1851
+ }));
1852
+ return {
1853
+ content: [
1854
+ {
1855
+ type: "text",
1856
+ text: `${prefix}[Intercepted -> Native Vault Economy Top]
1857
+ ` + JSON.stringify(formatted, null, 2)
1858
+ }
1859
+ ]
1860
+ };
1861
+ }
1862
+ }
1863
+ const customCurMatch = cleanBoard.startsWith("economy:") ? cleanBoard.replace("economy:", "") : ["coins", "candy", "tokens", "point", "points"].includes(cleanBoard.toLowerCase()) ? cleanBoard.toLowerCase() : null;
1864
+ if (customCurMatch && customCurMatch !== "vault" && client.economy?.getCurrencyTop) {
1865
+ try {
1866
+ const res = await client.economy.getCurrencyTop(customCurMatch, limit);
1867
+ if (Array.isArray(res) && res.length > 0) {
1868
+ return {
1869
+ content: [
1870
+ {
1871
+ type: "text",
1872
+ text: `${prefix}[Native Currency Top: ${customCurMatch}]
1873
+ ` + JSON.stringify(res, null, 2)
1874
+ }
1875
+ ]
1876
+ };
1877
+ }
1878
+ } catch {
1879
+ }
1880
+ }
1881
+ if (!client.leaderboards?.getTop) {
1882
+ throw new Error("Leaderboard client module is not available on this connection.");
1883
+ }
1884
+ const entries = await client.leaderboards.getTop(cleanBoard, limit);
1885
+ return {
1886
+ content: [
1887
+ {
1888
+ type: "text",
1889
+ text: prefix + JSON.stringify(entries, null, 2)
1890
+ }
1891
+ ]
1892
+ };
1893
+ } catch (err) {
1894
+ return {
1895
+ isError: true,
1896
+ content: [
1897
+ {
1898
+ type: "text",
1899
+ text: `Failed to get leaderboard '${board}': ${err instanceof Error ? err.message : String(err)}`
1900
+ }
1901
+ ]
1902
+ };
1903
+ }
1904
+ }
1905
+ );
1906
+ }
1907
+
1908
+ // src/tools/auto-register.ts
1909
+ import { z as z10 } from "zod";
1910
+ var SKIP_MODULES = /* @__PURE__ */ new Set(["http", "connect"]);
1911
+ function registerAllSdkModules(server, registeredNames) {
1912
+ const session = getSession();
1913
+ const client = session.getClient();
1914
+ let totalModules = 0;
1915
+ let totalMethods = 0;
1916
+ let newRegistered = 0;
1917
+ for (const [modName, modInstance] of Object.entries(client)) {
1918
+ if (SKIP_MODULES.has(modName)) continue;
1919
+ if (!modInstance || typeof modInstance !== "object") continue;
1920
+ const proto = Object.getPrototypeOf(modInstance);
1921
+ if (!proto || proto === Object.prototype) continue;
1922
+ const actionNames = Object.getOwnPropertyNames(proto).filter(
1923
+ (name) => name !== "constructor" && typeof modInstance[name] === "function"
1924
+ );
1925
+ if (actionNames.length === 0) continue;
1926
+ totalModules++;
1927
+ for (const action of actionNames) {
1928
+ totalMethods++;
1929
+ const toolName = `${modName}_${action}`;
1930
+ if (registeredNames.has(toolName)) continue;
1931
+ registeredNames.add(toolName);
1932
+ newRegistered++;
1933
+ server.tool(
1934
+ toolName,
1935
+ `NoxAeApi ${modName}.${action}() action. Supports standalone server or Velocity node targeting via nodeId.`,
1936
+ {
1937
+ args: z10.array(z10.unknown()).optional().describe(`Arguments array for ${modName}.${action}()`),
1938
+ nodeId: z10.string().optional().describe("Target server node ID (e.g. 'survival', 'lobby') when connected via Velocity hub")
1939
+ },
1940
+ async ({ args = [], nodeId }) => {
1941
+ const currentSession = getSession();
1942
+ try {
1943
+ if (currentSession.isHub && currentSession.hubClient && nodeId) {
1944
+ const forwardPath = `${modName}/${action}`;
1945
+ try {
1946
+ const res = await currentSession.hubClient.network.forward(nodeId, "POST", forwardPath, {
1947
+ body: args.length === 1 && typeof args[0] === "object" ? args[0] : { args }
1948
+ });
1949
+ return {
1950
+ content: [{ type: "text", text: JSON.stringify(res ?? "OK", null, 2) }]
1951
+ };
1952
+ } catch {
1953
+ const res = await currentSession.hubClient.network.forward(nodeId, "GET", forwardPath);
1954
+ return {
1955
+ content: [{ type: "text", text: JSON.stringify(res ?? "OK", null, 2) }]
1956
+ };
1957
+ }
1958
+ }
1959
+ const activeClient = currentSession.getClient();
1960
+ const targetMod = activeClient[modName];
1961
+ if (!targetMod || typeof targetMod[action] !== "function") {
1962
+ throw new Error(`Method ${modName}.${action} not found on client.`);
1963
+ }
1964
+ const result = await targetMod[action].apply(targetMod, args);
1965
+ return {
1966
+ content: [
1967
+ {
1968
+ type: "text",
1969
+ text: JSON.stringify(result ?? "Success (void)", null, 2)
1970
+ }
1971
+ ]
1972
+ };
1973
+ } catch (err) {
1974
+ return {
1975
+ isError: true,
1976
+ content: [
1977
+ {
1978
+ type: "text",
1979
+ text: `Error in ${modName}.${action}(): ${err instanceof Error ? err.message : String(err)}`
1980
+ }
1981
+ ]
1982
+ };
1983
+ }
1984
+ }
1985
+ );
1986
+ }
1987
+ }
1988
+ return { totalModules, totalMethods, newRegistered };
1989
+ }
1990
+
1991
+ // src/index.ts
1992
+ async function main() {
1993
+ try {
1994
+ const config = resolveConfig();
1995
+ console.error(
1996
+ `[noxaeapi-mcp] Loaded profile: "${config.profileName}" (hub=${config.hub}, url=${config.baseUrl})`
1997
+ );
1998
+ } catch (err) {
1999
+ console.error(
2000
+ `[noxaeapi-mcp] Notice: ${err instanceof Error ? err.message : String(err)}`
2001
+ );
2002
+ }
2003
+ const server = new McpServer({
2004
+ name: "noxaeapi-mcp",
2005
+ version: "1.0.0"
2006
+ });
2007
+ const registeredNames = /* @__PURE__ */ new Set();
2008
+ const track = (names) => names.forEach((n) => registeredNames.add(n));
2009
+ registerNetworkTools(server);
2010
+ track([
2011
+ "network_status_all",
2012
+ "network_node_status",
2013
+ "network_broadcast",
2014
+ "network_find_player",
2015
+ "network_send_player"
2016
+ ]);
2017
+ registerServerTools(server);
2018
+ track([
2019
+ "server_get_status",
2020
+ "server_dispatch_command",
2021
+ "server_broadcast",
2022
+ "server_get_whitelist",
2023
+ "server_whitelist_add",
2024
+ "server_whitelist_remove",
2025
+ "server_get_ops",
2026
+ "server_op_player",
2027
+ "server_deop_player",
2028
+ "server_get_logs",
2029
+ "server_restart",
2030
+ "server_ban_ip",
2031
+ "server_tell_player"
2032
+ ]);
2033
+ registerPlayerTools(server);
2034
+ track([
2035
+ "players_list_online",
2036
+ "players_get_info",
2037
+ "players_kick",
2038
+ "players_ban",
2039
+ "players_unban",
2040
+ "players_set_gamemode"
2041
+ ]);
2042
+ registerLuckPermsTools(server);
2043
+ track([
2044
+ "luckperms_get_player_info",
2045
+ "luckperms_add_player_permission",
2046
+ "luckperms_remove_player_permission",
2047
+ "luckperms_set_player_primary_group",
2048
+ "luckperms_list_groups"
2049
+ ]);
2050
+ registerEconomyTools(server);
2051
+ track([
2052
+ "economy_info",
2053
+ "economy_get_balance",
2054
+ "economy_pay",
2055
+ "economy_debit",
2056
+ "economy_get_top",
2057
+ "economy_list_currencies",
2058
+ "economy_get_currency_top"
2059
+ ]);
2060
+ registerLeaderboardTools(server);
2061
+ track([
2062
+ "leaderboards_list",
2063
+ "leaderboards_get_top"
2064
+ ]);
2065
+ registerWorldTools(server);
2066
+ track([
2067
+ "worlds_list",
2068
+ "worlds_set_time",
2069
+ "worlds_set_weather",
2070
+ "worlds_get_entities"
2071
+ ]);
2072
+ registerPluginTools(server);
2073
+ track([
2074
+ "plugins_list",
2075
+ "plugins_get_info"
2076
+ ]);
2077
+ registerDispatchTools(server);
2078
+ track(["noxae_list_modules", "noxae_call_action"]);
2079
+ const stats = registerAllSdkModules(server, registeredNames);
2080
+ console.error(
2081
+ `[noxaeapi-mcp] Registered 100% of SDK: ${stats.totalModules} modules, ${registeredNames.size} total tools active.`
2082
+ );
2083
+ server.prompt(
2084
+ "minecraft_sysadmin",
2085
+ "Expert guidelines and decision trees for AI agents operating Minecraft servers and Velocity networks via NoxAeApi.",
2086
+ {},
2087
+ () => ({
2088
+ messages: [
2089
+ {
2090
+ role: "user",
2091
+ content: {
2092
+ type: "text",
2093
+ text: `You are an expert Minecraft DevOps and Server Administrator assistant operating with NoxAeApi.
2094
+ Follow these operational guidelines to make accurate, reliable decisions:
2095
+
2096
+ 1. ENVIRONMENT & TOPOLOGY:
2097
+ - When connected to a Velocity Network Hub proxy, multiple backend server nodes exist (e.g. CSMP, LOBBY).
2098
+ - Use 'network_status_all' to discover active nodes, player counts, and node health.
2099
+ - For node-specific actions (players, economy, leaderboards, commands, worlds), always supply the 'nodeId' parameter (e.g. 'CSMP').
2100
+
2101
+ 2. ECONOMY & BALANCE DECISION TREE:
2102
+ - For primary money/Vault balance: use 'economy_get_top' (richest players) and 'economy_get_balance' (individual balance).
2103
+ - For custom currencies (CoinsEngine, ExcellentEconomy): use 'economy_list_currencies' to find IDs, and 'economy_get_currency_top' for rankings.
2104
+ - DO NOT use generic placeholder boards for money. Always prefer the dedicated 'economy_*' tools.
2105
+
2106
+ 3. LEADERBOARD DECISION TREE:
2107
+ - For gameplay rankings (kills, deaths, playtime, mcmmo, auraskills): use 'leaderboards_get_top' with IDs like 'player:kills', 'player:deaths', 'player:playtime'.
2108
+ - Use 'leaderboards_list' to discover valid game stats (it automatically filters internal noise).
2109
+
2110
+ 4. PERFORMANCE & DIAGNOSTICS:
2111
+ - For lag or TPS inquiries: call 'server_get_status'. TPS: 20.0 is perfect, <18.0 is lagging.
2112
+ - For errors or console activity: call 'server_get_logs'.
2113
+
2114
+ 5. COMMANDS & SAFETY:
2115
+ - For executing commands: call 'server_dispatch_command' without the leading slash.
2116
+ - For player permissions: call 'luckperms_add_player_permission' or 'luckperms_remove_player_permission'.`
2117
+ }
2118
+ }
2119
+ ]
2120
+ })
2121
+ );
2122
+ const transport = new StdioServerTransport();
2123
+ await server.connect(transport);
2124
+ console.error("[noxaeapi-mcp] Server running on stdio ready for AI agents");
2125
+ }
2126
+ main().catch((err) => {
2127
+ console.error("[noxaeapi-mcp] Fatal error:", err);
2128
+ process.exit(1);
2129
+ });