@mahesvara/discord-mcpserver 1.0.7 → 1.0.9

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.
Files changed (54) hide show
  1. package/dist/index.js +7 -2835
  2. package/dist/schemas/channel.d.ts +158 -0
  3. package/dist/schemas/channel.js +128 -0
  4. package/dist/schemas/common.d.ts +23 -0
  5. package/dist/schemas/common.js +71 -0
  6. package/dist/schemas/community.d.ts +181 -0
  7. package/dist/schemas/community.js +60 -0
  8. package/dist/schemas/emoji.d.ts +59 -0
  9. package/dist/schemas/emoji.js +29 -0
  10. package/dist/schemas/event.d.ts +62 -0
  11. package/dist/schemas/event.js +33 -0
  12. package/dist/schemas/guild.d.ts +85 -0
  13. package/dist/schemas/guild.js +42 -0
  14. package/dist/schemas/index.d.ts +12 -855
  15. package/dist/schemas/index.js +13 -617
  16. package/dist/schemas/invite.d.ts +40 -0
  17. package/dist/schemas/invite.js +32 -0
  18. package/dist/schemas/member.d.ts +60 -0
  19. package/dist/schemas/member.js +33 -0
  20. package/dist/schemas/message.d.ts +121 -0
  21. package/dist/schemas/message.js +61 -0
  22. package/dist/schemas/moderation.d.ts +343 -0
  23. package/dist/schemas/moderation.js +152 -0
  24. package/dist/schemas/role.d.ts +126 -0
  25. package/dist/schemas/role.js +73 -0
  26. package/dist/schemas/webhook.d.ts +51 -0
  27. package/dist/schemas/webhook.js +33 -0
  28. package/dist/server.d.ts +2 -0
  29. package/dist/server.js +6 -0
  30. package/dist/tools/channel.d.ts +2 -0
  31. package/dist/tools/channel.js +510 -0
  32. package/dist/tools/community.d.ts +2 -0
  33. package/dist/tools/community.js +259 -0
  34. package/dist/tools/emoji.d.ts +2 -0
  35. package/dist/tools/emoji.js +247 -0
  36. package/dist/tools/event.d.ts +2 -0
  37. package/dist/tools/event.js +170 -0
  38. package/dist/tools/guild.d.ts +2 -0
  39. package/dist/tools/guild.js +198 -0
  40. package/dist/tools/index.d.ts +2 -0
  41. package/dist/tools/index.js +24 -0
  42. package/dist/tools/invite.d.ts +2 -0
  43. package/dist/tools/invite.js +143 -0
  44. package/dist/tools/member.d.ts +2 -0
  45. package/dist/tools/member.js +200 -0
  46. package/dist/tools/message.d.ts +2 -0
  47. package/dist/tools/message.js +386 -0
  48. package/dist/tools/moderation.d.ts +2 -0
  49. package/dist/tools/moderation.js +641 -0
  50. package/dist/tools/role.d.ts +2 -0
  51. package/dist/tools/role.js +420 -0
  52. package/dist/tools/webhook.d.ts +2 -0
  53. package/dist/tools/webhook.js +199 -0
  54. package/package.json +1 -1
@@ -0,0 +1,510 @@
1
+ import { ListChannelsSchema, GetChannelSchema, CreateChannelSchema, DeleteChannelSchema, EditChannelSchema, SetChannelPermissionsSchema, RemoveChannelPermissionsSchema, GetChannelPermissionsSchema, SyncChannelPermissionsSchema, } from "../schemas/index.js";
2
+ import { getClient, formatChannel, channelToMarkdown, formatResponse, truncateIfNeeded, getChannelType, } from "../services/discord.js";
3
+ import { ChannelType, GuildChannel, PermissionsBitField, OverwriteType } from "discord.js";
4
+ // Helper function to convert permission strings to bitfield
5
+ function parsePermissions(permissions) {
6
+ let bits = BigInt(0);
7
+ for (const perm of permissions) {
8
+ const flag = PermissionsBitField.Flags[perm];
9
+ if (flag) {
10
+ bits |= flag;
11
+ }
12
+ }
13
+ return bits;
14
+ }
15
+ // List of common permission names for reference
16
+ const PERMISSION_LIST = [
17
+ 'ViewChannel', 'ManageChannels', 'ManageRoles', 'CreateInstantInvite',
18
+ 'SendMessages', 'SendMessagesInThreads', 'CreatePublicThreads', 'CreatePrivateThreads',
19
+ 'EmbedLinks', 'AttachFiles', 'AddReactions', 'UseExternalEmojis', 'UseExternalStickers',
20
+ 'MentionEveryone', 'ManageMessages', 'ManageThreads', 'ReadMessageHistory',
21
+ 'SendTTSMessages', 'UseApplicationCommands',
22
+ 'Connect', 'Speak', 'Stream', 'UseEmbeddedActivities', 'UseSoundboard',
23
+ 'UseExternalSounds', 'UseVAD', 'PrioritySpeaker', 'MuteMembers', 'DeafenMembers', 'MoveMembers',
24
+ 'ManageEvents', 'ManageWebhooks'
25
+ ];
26
+ export function registerChannelTools(server) {
27
+ server.registerTool("discord_list_channels", {
28
+ title: "List Discord Channels",
29
+ description: `List channels in a Discord server.
30
+
31
+ Args:
32
+ - guild_id (string): Discord server/guild ID
33
+ - type ('text' | 'voice' | 'category' | 'all'): Filter by channel type (default: 'all')
34
+ - response_format ('markdown' | 'json'): Output format (default: 'json')
35
+
36
+ Returns:
37
+ List of channels with id, name, type, topic, and category info`,
38
+ inputSchema: ListChannelsSchema,
39
+ annotations: {
40
+ readOnlyHint: true,
41
+ destructiveHint: false,
42
+ idempotentHint: true,
43
+ openWorldHint: true,
44
+ },
45
+ }, async (params) => {
46
+ try {
47
+ const client = await getClient();
48
+ const guild = client.guilds.cache.get(params.guild_id);
49
+ if (!guild) {
50
+ return {
51
+ isError: true,
52
+ content: [{ type: "text", text: `Guild not found: ${params.guild_id}` }],
53
+ };
54
+ }
55
+ // Get all channels as array to avoid type predicate issues
56
+ const allChannels = Array.from(guild.channels.cache.values());
57
+ // Filter out threads and apply type filter
58
+ let filteredChannels = allChannels.filter(c => !c.isThread());
59
+ if (params.type !== "all") {
60
+ const targetType = getChannelType(params.type);
61
+ if (targetType !== null) {
62
+ filteredChannels = filteredChannels.filter(c => c.type === targetType);
63
+ }
64
+ }
65
+ const formatted = filteredChannels.map(c => formatChannel(c));
66
+ const text = formatResponse(formatted, params.response_format, (items) => items.map(channelToMarkdown).join("\n\n"));
67
+ return {
68
+ content: [{ type: "text", text: truncateIfNeeded(text) }],
69
+ };
70
+ }
71
+ catch (error) {
72
+ return {
73
+ isError: true,
74
+ content: [{ type: "text", text: `Error listing channels: ${error.message}` }],
75
+ };
76
+ }
77
+ });
78
+ server.registerTool("discord_get_channel", {
79
+ title: "Get Discord Channel Info",
80
+ description: `Get detailed information about a specific channel.
81
+
82
+ Args:
83
+ - channel_id (string): Discord channel ID
84
+ - response_format ('markdown' | 'json'): Output format (default: 'json')
85
+
86
+ Returns:
87
+ Channel details including name, type, topic, and category`,
88
+ inputSchema: GetChannelSchema,
89
+ annotations: {
90
+ readOnlyHint: true,
91
+ destructiveHint: false,
92
+ idempotentHint: true,
93
+ openWorldHint: true,
94
+ },
95
+ }, async (params) => {
96
+ try {
97
+ const client = await getClient();
98
+ const channel = client.channels.cache.get(params.channel_id);
99
+ if (!channel || !(channel instanceof GuildChannel)) {
100
+ return {
101
+ isError: true,
102
+ content: [{ type: "text", text: `Channel not found: ${params.channel_id}` }],
103
+ };
104
+ }
105
+ const formatted = formatChannel(channel);
106
+ const text = formatResponse(formatted, params.response_format, channelToMarkdown);
107
+ return {
108
+ content: [{ type: "text", text }],
109
+ };
110
+ }
111
+ catch (error) {
112
+ return {
113
+ isError: true,
114
+ content: [{ type: "text", text: `Error getting channel: ${error.message}` }],
115
+ };
116
+ }
117
+ });
118
+ server.registerTool("discord_create_channel", {
119
+ title: "Create Discord Channel",
120
+ description: `Create a new channel or category in a Discord server.
121
+
122
+ Args:
123
+ - guild_id (string): Discord server/guild ID
124
+ - name (string): Channel name
125
+ - type ('text' | 'voice' | 'category'): Channel type (default: 'text')
126
+ - topic (string, optional): Channel topic (text channels only)
127
+ - parent_id (string, optional): Category ID to create channel under (not for categories)
128
+ - nsfw (boolean, optional): Whether the channel is NSFW (text channels only)
129
+ - bitrate (number, optional): Bitrate for voice channels (8000-384000)
130
+ - user_limit (number, optional): User limit for voice channels (0 = unlimited)
131
+
132
+ Returns:
133
+ The created channel's details`,
134
+ inputSchema: CreateChannelSchema,
135
+ annotations: {
136
+ readOnlyHint: false,
137
+ destructiveHint: false,
138
+ idempotentHint: false,
139
+ openWorldHint: true,
140
+ },
141
+ }, async (params) => {
142
+ try {
143
+ const client = await getClient();
144
+ const guild = client.guilds.cache.get(params.guild_id);
145
+ if (!guild) {
146
+ return {
147
+ isError: true,
148
+ content: [{ type: "text", text: `Guild not found: ${params.guild_id}` }],
149
+ };
150
+ }
151
+ let channelType;
152
+ switch (params.type) {
153
+ case "voice":
154
+ channelType = ChannelType.GuildVoice;
155
+ break;
156
+ case "category":
157
+ channelType = ChannelType.GuildCategory;
158
+ break;
159
+ default:
160
+ channelType = ChannelType.GuildText;
161
+ }
162
+ const channelOptions = {
163
+ name: params.name,
164
+ type: channelType,
165
+ };
166
+ // Only add these for non-category channels
167
+ if (params.type !== "category") {
168
+ if (params.parent_id)
169
+ channelOptions.parent = params.parent_id;
170
+ }
171
+ // Text channel specific options
172
+ if (params.type === "text" || !params.type) {
173
+ if (params.topic)
174
+ channelOptions.topic = params.topic;
175
+ if (params.nsfw !== undefined)
176
+ channelOptions.nsfw = params.nsfw;
177
+ }
178
+ // Voice channel specific options
179
+ if (params.type === "voice") {
180
+ if (params.bitrate)
181
+ channelOptions.bitrate = params.bitrate;
182
+ if (params.user_limit !== undefined)
183
+ channelOptions.userLimit = params.user_limit;
184
+ }
185
+ const channel = await guild.channels.create(channelOptions);
186
+ const typeLabel = params.type === "category" ? "category" : "channel";
187
+ const prefix = params.type === "category" ? "📁" : "#";
188
+ return {
189
+ content: [{ type: "text", text: `Created ${typeLabel} ${prefix}${channel.name} (ID: ${channel.id})` }],
190
+ };
191
+ }
192
+ catch (error) {
193
+ return {
194
+ isError: true,
195
+ content: [{ type: "text", text: `Error creating channel: ${error.message}` }],
196
+ };
197
+ }
198
+ });
199
+ server.registerTool("discord_delete_channel", {
200
+ title: "Delete Discord Channel",
201
+ description: `Delete a channel from a Discord server. This action is permanent!
202
+
203
+ Args:
204
+ - channel_id (string): Discord channel ID to delete
205
+
206
+ Returns:
207
+ Confirmation of deletion`,
208
+ inputSchema: DeleteChannelSchema,
209
+ annotations: {
210
+ readOnlyHint: false,
211
+ destructiveHint: true,
212
+ idempotentHint: false,
213
+ openWorldHint: true,
214
+ },
215
+ }, async (params) => {
216
+ try {
217
+ const client = await getClient();
218
+ const channel = client.channels.cache.get(params.channel_id);
219
+ if (!channel || !('delete' in channel)) {
220
+ return {
221
+ isError: true,
222
+ content: [{ type: "text", text: `Channel not found: ${params.channel_id}` }],
223
+ };
224
+ }
225
+ const name = 'name' in channel ? channel.name : params.channel_id;
226
+ await channel.delete();
227
+ return {
228
+ content: [{ type: "text", text: `Deleted channel: ${name}` }],
229
+ };
230
+ }
231
+ catch (error) {
232
+ return {
233
+ isError: true,
234
+ content: [{ type: "text", text: `Error deleting channel: ${error.message}` }],
235
+ };
236
+ }
237
+ });
238
+ server.registerTool("discord_edit_channel", {
239
+ title: "Edit Discord Channel",
240
+ description: `Edit a channel's properties including name, topic, category, position, and more.
241
+
242
+ Args:
243
+ - channel_id (string): Discord channel ID
244
+ - name (string, optional): New channel name
245
+ - topic (string, optional): New channel topic
246
+ - parent_id (string, optional): Move to a different category (or 'none' to remove from category)
247
+ - position (number, optional): New position of the channel
248
+ - nsfw (boolean, optional): Whether the channel is NSFW
249
+ - bitrate (number, optional): Bitrate for voice channels (8000-384000)
250
+ - user_limit (number, optional): User limit for voice channels
251
+
252
+ Returns:
253
+ Updated channel details`,
254
+ inputSchema: EditChannelSchema,
255
+ annotations: {
256
+ readOnlyHint: false,
257
+ destructiveHint: false,
258
+ idempotentHint: true,
259
+ openWorldHint: true,
260
+ },
261
+ }, async (params) => {
262
+ try {
263
+ const client = await getClient();
264
+ const channel = client.channels.cache.get(params.channel_id);
265
+ if (!channel || !('edit' in channel)) {
266
+ return {
267
+ isError: true,
268
+ content: [{ type: "text", text: `Channel not found: ${params.channel_id}` }],
269
+ };
270
+ }
271
+ const updates = {};
272
+ if (params.name)
273
+ updates.name = params.name;
274
+ if (params.topic !== undefined)
275
+ updates.topic = params.topic;
276
+ if (params.position !== undefined)
277
+ updates.position = params.position;
278
+ if (params.nsfw !== undefined)
279
+ updates.nsfw = params.nsfw;
280
+ if (params.bitrate !== undefined)
281
+ updates.bitrate = params.bitrate;
282
+ if (params.user_limit !== undefined)
283
+ updates.userLimit = params.user_limit;
284
+ if (params.parent_id !== undefined) {
285
+ updates.parent = params.parent_id === 'none' ? null : params.parent_id;
286
+ }
287
+ await channel.edit(updates);
288
+ return {
289
+ content: [{ type: "text", text: `Updated channel ${params.channel_id}` }],
290
+ };
291
+ }
292
+ catch (error) {
293
+ return {
294
+ isError: true,
295
+ content: [{ type: "text", text: `Error editing channel: ${error.message}` }],
296
+ };
297
+ }
298
+ });
299
+ // ============================================================================
300
+ // PERMISSION TOOLS
301
+ // ============================================================================
302
+ server.registerTool("discord_set_channel_permissions", {
303
+ title: "Set Channel Permissions",
304
+ description: `Set permission overrides for a role or member on a channel.
305
+
306
+ Common permission names:
307
+ - View: ViewChannel
308
+ - Messages: SendMessages, ReadMessageHistory, ManageMessages, EmbedLinks, AttachFiles, AddReactions
309
+ - Voice: Connect, Speak, Stream, MuteMembers, DeafenMembers, MoveMembers
310
+ - Threads: CreatePublicThreads, CreatePrivateThreads, SendMessagesInThreads
311
+ - Management: ManageChannels, ManageRoles, ManageWebhooks
312
+
313
+ Args:
314
+ - channel_id (string): Discord channel ID
315
+ - target_id (string): Role or User ID to set permissions for
316
+ - target_type ('role' | 'member'): Whether target is a role or member
317
+ - allow (string[], optional): Permissions to allow (e.g., ['ViewChannel', 'SendMessages'])
318
+ - deny (string[], optional): Permissions to deny (e.g., ['SendMessages'])
319
+
320
+ Returns:
321
+ Confirmation of permission changes`,
322
+ inputSchema: SetChannelPermissionsSchema,
323
+ annotations: {
324
+ readOnlyHint: false,
325
+ destructiveHint: false,
326
+ idempotentHint: true,
327
+ openWorldHint: true,
328
+ },
329
+ }, async (params) => {
330
+ try {
331
+ const client = await getClient();
332
+ const channel = client.channels.cache.get(params.channel_id);
333
+ if (!channel || !('permissionOverwrites' in channel)) {
334
+ return {
335
+ isError: true,
336
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
337
+ };
338
+ }
339
+ const guildChannel = channel;
340
+ // Build permission overwrite object with individual permission keys
341
+ const permissionOverwrite = {};
342
+ // Set allowed permissions to true
343
+ if (params.allow) {
344
+ for (const perm of params.allow) {
345
+ permissionOverwrite[perm] = true;
346
+ }
347
+ }
348
+ // Set denied permissions to false
349
+ if (params.deny) {
350
+ for (const perm of params.deny) {
351
+ permissionOverwrite[perm] = false;
352
+ }
353
+ }
354
+ await guildChannel.permissionOverwrites.edit(params.target_id, permissionOverwrite, {
355
+ type: params.target_type === 'role' ? OverwriteType.Role : OverwriteType.Member,
356
+ });
357
+ const allowList = params.allow?.join(', ') || 'none';
358
+ const denyList = params.deny?.join(', ') || 'none';
359
+ return {
360
+ content: [{ type: "text", text: `Updated permissions for ${params.target_type} ${params.target_id} on channel ${params.channel_id}\nAllowed: ${allowList}\nDenied: ${denyList}` }],
361
+ };
362
+ }
363
+ catch (error) {
364
+ return {
365
+ isError: true,
366
+ content: [{ type: "text", text: `Error setting permissions: ${error.message}` }],
367
+ };
368
+ }
369
+ });
370
+ server.registerTool("discord_remove_channel_permissions", {
371
+ title: "Remove Channel Permission Overrides",
372
+ description: `Remove all permission overrides for a role or member on a channel.
373
+
374
+ Args:
375
+ - channel_id (string): Discord channel ID
376
+ - target_id (string): Role or User ID to remove permission overrides for
377
+
378
+ Returns:
379
+ Confirmation of removal`,
380
+ inputSchema: RemoveChannelPermissionsSchema,
381
+ annotations: {
382
+ readOnlyHint: false,
383
+ destructiveHint: true,
384
+ idempotentHint: true,
385
+ openWorldHint: true,
386
+ },
387
+ }, async (params) => {
388
+ try {
389
+ const client = await getClient();
390
+ const channel = client.channels.cache.get(params.channel_id);
391
+ if (!channel || !('permissionOverwrites' in channel)) {
392
+ return {
393
+ isError: true,
394
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
395
+ };
396
+ }
397
+ const guildChannel = channel;
398
+ await guildChannel.permissionOverwrites.delete(params.target_id);
399
+ return {
400
+ content: [{ type: "text", text: `Removed permission overrides for ${params.target_id} on channel ${params.channel_id}` }],
401
+ };
402
+ }
403
+ catch (error) {
404
+ return {
405
+ isError: true,
406
+ content: [{ type: "text", text: `Error removing permissions: ${error.message}` }],
407
+ };
408
+ }
409
+ });
410
+ server.registerTool("discord_get_channel_permissions", {
411
+ title: "Get Channel Permissions",
412
+ description: `Get all permission overrides for a channel.
413
+
414
+ Args:
415
+ - channel_id (string): Discord channel ID
416
+ - response_format ('markdown' | 'json'): Output format (default: 'json')
417
+
418
+ Returns:
419
+ List of permission overrides with allow/deny for each role/member`,
420
+ inputSchema: GetChannelPermissionsSchema,
421
+ annotations: {
422
+ readOnlyHint: true,
423
+ destructiveHint: false,
424
+ idempotentHint: true,
425
+ openWorldHint: true,
426
+ },
427
+ }, async (params) => {
428
+ try {
429
+ const client = await getClient();
430
+ const channel = client.channels.cache.get(params.channel_id);
431
+ if (!channel || !('permissionOverwrites' in channel)) {
432
+ return {
433
+ isError: true,
434
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
435
+ };
436
+ }
437
+ const guildChannel = channel;
438
+ const overwrites = guildChannel.permissionOverwrites.cache;
439
+ const formatted = overwrites.map(ow => {
440
+ const allowedPerms = new PermissionsBitField(ow.allow).toArray();
441
+ const deniedPerms = new PermissionsBitField(ow.deny).toArray();
442
+ return {
443
+ id: ow.id,
444
+ type: ow.type === OverwriteType.Role ? 'role' : 'member',
445
+ allow: allowedPerms,
446
+ deny: deniedPerms,
447
+ };
448
+ });
449
+ if (params.response_format === 'json') {
450
+ return {
451
+ content: [{ type: "text", text: JSON.stringify(Array.from(formatted.values()), null, 2) }],
452
+ };
453
+ }
454
+ // Markdown format
455
+ const lines = Array.from(formatted.values()).map(ow => {
456
+ const allowStr = ow.allow.length > 0 ? ow.allow.join(', ') : 'none';
457
+ const denyStr = ow.deny.length > 0 ? ow.deny.join(', ') : 'none';
458
+ return `### ${ow.type}: ${ow.id}\n- **Allow**: ${allowStr}\n- **Deny**: ${denyStr}`;
459
+ });
460
+ const text = lines.length > 0 ? lines.join('\n\n') : 'No permission overrides on this channel.';
461
+ return {
462
+ content: [{ type: "text", text }],
463
+ };
464
+ }
465
+ catch (error) {
466
+ return {
467
+ isError: true,
468
+ content: [{ type: "text", text: `Error getting permissions: ${error.message}` }],
469
+ };
470
+ }
471
+ });
472
+ server.registerTool("discord_sync_channel_permissions", {
473
+ title: "Sync Channel Permissions with Category",
474
+ description: `Sync a channel's permissions with its parent category.
475
+
476
+ Args:
477
+ - channel_id (string): Discord channel ID
478
+
479
+ Returns:
480
+ Confirmation of sync`,
481
+ inputSchema: SyncChannelPermissionsSchema,
482
+ annotations: {
483
+ readOnlyHint: false,
484
+ destructiveHint: false,
485
+ idempotentHint: true,
486
+ openWorldHint: true,
487
+ },
488
+ }, async (params) => {
489
+ try {
490
+ const client = await getClient();
491
+ const channel = client.channels.cache.get(params.channel_id);
492
+ if (!channel || !('lockPermissions' in channel)) {
493
+ return {
494
+ isError: true,
495
+ content: [{ type: "text", text: `Channel not found or doesn't support permission sync: ${params.channel_id}` }],
496
+ };
497
+ }
498
+ await channel.lockPermissions();
499
+ return {
500
+ content: [{ type: "text", text: `Synced permissions for channel ${params.channel_id} with its parent category` }],
501
+ };
502
+ }
503
+ catch (error) {
504
+ return {
505
+ isError: true,
506
+ content: [{ type: "text", text: `Error syncing permissions: ${error.message}` }],
507
+ };
508
+ }
509
+ });
510
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCommunityTools(server: McpServer): void;