@mahesvara/discord-mcpserver 1.0.8 → 1.1.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.
Files changed (54) hide show
  1. package/dist/index.js +4 -2833
  2. package/dist/schemas/channel.d.ts +176 -0
  3. package/dist/schemas/channel.js +146 -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 +572 -0
  32. package/dist/tools/community.d.ts +2 -0
  33. package/dist/tools/community.js +268 -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,572 @@
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, SortOrderType, ForumLayoutType } 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' | 'forum'): Channel type (default: 'text')
126
+ - topic (string, optional): Channel topic (text/forum 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/forum 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
+ - default_reaction_emoji (string, optional): Default emoji for forum posts (emoji char or custom ID)
132
+ - default_sort_order ('latest_activity' | 'creation_date', optional): Sort order for forum posts
133
+ - default_forum_layout ('not_set' | 'list_view' | 'gallery_view', optional): Forum layout
134
+
135
+ Returns:
136
+ The created channel's details`,
137
+ inputSchema: CreateChannelSchema,
138
+ annotations: {
139
+ readOnlyHint: false,
140
+ destructiveHint: false,
141
+ idempotentHint: false,
142
+ openWorldHint: true,
143
+ },
144
+ }, async (params) => {
145
+ try {
146
+ const client = await getClient();
147
+ const guild = client.guilds.cache.get(params.guild_id);
148
+ if (!guild) {
149
+ return {
150
+ isError: true,
151
+ content: [{ type: "text", text: `Guild not found: ${params.guild_id}` }],
152
+ };
153
+ }
154
+ let channelType;
155
+ switch (params.type) {
156
+ case "voice":
157
+ channelType = ChannelType.GuildVoice;
158
+ break;
159
+ case "category":
160
+ channelType = ChannelType.GuildCategory;
161
+ break;
162
+ case "forum":
163
+ channelType = ChannelType.GuildForum;
164
+ break;
165
+ default:
166
+ channelType = ChannelType.GuildText;
167
+ }
168
+ const channelOptions = {
169
+ name: params.name,
170
+ type: channelType,
171
+ };
172
+ // Only add these for non-category channels
173
+ if (params.type !== "category") {
174
+ if (params.parent_id)
175
+ channelOptions.parent = params.parent_id;
176
+ }
177
+ // Text channel specific options
178
+ if (params.type === "text" || !params.type) {
179
+ if (params.topic)
180
+ channelOptions.topic = params.topic;
181
+ if (params.nsfw !== undefined)
182
+ channelOptions.nsfw = params.nsfw;
183
+ }
184
+ // Voice channel specific options
185
+ if (params.type === "voice") {
186
+ if (params.bitrate)
187
+ channelOptions.bitrate = params.bitrate;
188
+ if (params.user_limit !== undefined)
189
+ channelOptions.userLimit = params.user_limit;
190
+ }
191
+ // Forum channel specific options
192
+ if (params.type === "forum") {
193
+ if (params.topic)
194
+ channelOptions.topic = params.topic;
195
+ if (params.nsfw !== undefined)
196
+ channelOptions.nsfw = params.nsfw;
197
+ if (params.default_reaction_emoji) {
198
+ // Check if it's a custom emoji ID (numeric) or unicode emoji
199
+ const isCustomEmoji = /^\d+$/.test(params.default_reaction_emoji);
200
+ channelOptions.defaultReactionEmoji = isCustomEmoji
201
+ ? { id: params.default_reaction_emoji }
202
+ : { name: params.default_reaction_emoji };
203
+ }
204
+ if (params.default_sort_order) {
205
+ channelOptions.defaultSortOrder = params.default_sort_order === "latest_activity"
206
+ ? SortOrderType.LatestActivity
207
+ : SortOrderType.CreationDate;
208
+ }
209
+ if (params.default_forum_layout) {
210
+ const layoutMap = {
211
+ not_set: ForumLayoutType.NotSet,
212
+ list_view: ForumLayoutType.ListView,
213
+ gallery_view: ForumLayoutType.GalleryView,
214
+ };
215
+ channelOptions.defaultForumLayout = layoutMap[params.default_forum_layout];
216
+ }
217
+ }
218
+ const channel = await guild.channels.create(channelOptions);
219
+ const typeLabel = params.type === "category" ? "category" : "channel";
220
+ const prefix = params.type === "category" ? "📁" : "#";
221
+ return {
222
+ content: [{ type: "text", text: `Created ${typeLabel} ${prefix}${channel.name} (ID: ${channel.id})` }],
223
+ };
224
+ }
225
+ catch (error) {
226
+ return {
227
+ isError: true,
228
+ content: [{ type: "text", text: `Error creating channel: ${error.message}` }],
229
+ };
230
+ }
231
+ });
232
+ server.registerTool("discord_delete_channel", {
233
+ title: "Delete Discord Channel",
234
+ description: `Delete a channel from a Discord server. This action is permanent!
235
+
236
+ Args:
237
+ - channel_id (string): Discord channel ID to delete
238
+
239
+ Returns:
240
+ Confirmation of deletion`,
241
+ inputSchema: DeleteChannelSchema,
242
+ annotations: {
243
+ readOnlyHint: false,
244
+ destructiveHint: true,
245
+ idempotentHint: false,
246
+ openWorldHint: true,
247
+ },
248
+ }, async (params) => {
249
+ try {
250
+ const client = await getClient();
251
+ const channel = client.channels.cache.get(params.channel_id);
252
+ if (!channel || !('delete' in channel)) {
253
+ return {
254
+ isError: true,
255
+ content: [{ type: "text", text: `Channel not found: ${params.channel_id}` }],
256
+ };
257
+ }
258
+ const name = 'name' in channel ? channel.name : params.channel_id;
259
+ await channel.delete();
260
+ return {
261
+ content: [{ type: "text", text: `Deleted channel: ${name}` }],
262
+ };
263
+ }
264
+ catch (error) {
265
+ return {
266
+ isError: true,
267
+ content: [{ type: "text", text: `Error deleting channel: ${error.message}` }],
268
+ };
269
+ }
270
+ });
271
+ server.registerTool("discord_edit_channel", {
272
+ title: "Edit Discord Channel",
273
+ description: `Edit a channel's properties including name, topic, category, position, and more.
274
+
275
+ Args:
276
+ - channel_id (string): Discord channel ID
277
+ - name (string, optional): New channel name
278
+ - topic (string, optional): New channel topic
279
+ - parent_id (string, optional): Move to a different category (or 'none' to remove from category)
280
+ - position (number, optional): New position of the channel
281
+ - nsfw (boolean, optional): Whether the channel is NSFW
282
+ - bitrate (number, optional): Bitrate for voice channels (8000-384000)
283
+ - user_limit (number, optional): User limit for voice channels
284
+ - default_reaction_emoji (string, optional): Default emoji for forum posts (emoji char or custom ID, 'none' to remove)
285
+ - default_sort_order ('latest_activity' | 'creation_date', optional): Sort order for forum posts
286
+ - default_forum_layout ('not_set' | 'list_view' | 'gallery_view', optional): Forum layout
287
+
288
+ Returns:
289
+ Updated channel details`,
290
+ inputSchema: EditChannelSchema,
291
+ annotations: {
292
+ readOnlyHint: false,
293
+ destructiveHint: false,
294
+ idempotentHint: true,
295
+ openWorldHint: true,
296
+ },
297
+ }, async (params) => {
298
+ try {
299
+ const client = await getClient();
300
+ const channel = client.channels.cache.get(params.channel_id);
301
+ if (!channel || !('edit' in channel)) {
302
+ return {
303
+ isError: true,
304
+ content: [{ type: "text", text: `Channel not found: ${params.channel_id}` }],
305
+ };
306
+ }
307
+ const updates = {};
308
+ if (params.name)
309
+ updates.name = params.name;
310
+ if (params.topic !== undefined)
311
+ updates.topic = params.topic;
312
+ if (params.position !== undefined)
313
+ updates.position = params.position;
314
+ if (params.nsfw !== undefined)
315
+ updates.nsfw = params.nsfw;
316
+ if (params.bitrate !== undefined)
317
+ updates.bitrate = params.bitrate;
318
+ if (params.user_limit !== undefined)
319
+ updates.userLimit = params.user_limit;
320
+ if (params.parent_id !== undefined) {
321
+ updates.parent = params.parent_id === 'none' ? null : params.parent_id;
322
+ }
323
+ // Forum channel specific options
324
+ if (params.default_reaction_emoji !== undefined) {
325
+ if (params.default_reaction_emoji === 'none') {
326
+ updates.defaultReactionEmoji = null;
327
+ }
328
+ else {
329
+ // Check if it's a custom emoji ID (numeric) or unicode emoji
330
+ const isCustomEmoji = /^\d+$/.test(params.default_reaction_emoji);
331
+ updates.defaultReactionEmoji = isCustomEmoji
332
+ ? { id: params.default_reaction_emoji }
333
+ : { name: params.default_reaction_emoji };
334
+ }
335
+ }
336
+ if (params.default_sort_order !== undefined) {
337
+ updates.defaultSortOrder = params.default_sort_order === "latest_activity"
338
+ ? SortOrderType.LatestActivity
339
+ : SortOrderType.CreationDate;
340
+ }
341
+ if (params.default_forum_layout !== undefined) {
342
+ const layoutMap = {
343
+ not_set: ForumLayoutType.NotSet,
344
+ list_view: ForumLayoutType.ListView,
345
+ gallery_view: ForumLayoutType.GalleryView,
346
+ };
347
+ updates.defaultForumLayout = layoutMap[params.default_forum_layout];
348
+ }
349
+ await channel.edit(updates);
350
+ return {
351
+ content: [{ type: "text", text: `Updated channel ${params.channel_id}` }],
352
+ };
353
+ }
354
+ catch (error) {
355
+ return {
356
+ isError: true,
357
+ content: [{ type: "text", text: `Error editing channel: ${error.message}` }],
358
+ };
359
+ }
360
+ });
361
+ // ============================================================================
362
+ // PERMISSION TOOLS
363
+ // ============================================================================
364
+ server.registerTool("discord_set_channel_permissions", {
365
+ title: "Set Channel Permissions",
366
+ description: `Set permission overrides for a role or member on a channel.
367
+
368
+ Common permission names:
369
+ - View: ViewChannel
370
+ - Messages: SendMessages, ReadMessageHistory, ManageMessages, EmbedLinks, AttachFiles, AddReactions
371
+ - Voice: Connect, Speak, Stream, MuteMembers, DeafenMembers, MoveMembers
372
+ - Threads: CreatePublicThreads, CreatePrivateThreads, SendMessagesInThreads
373
+ - Management: ManageChannels, ManageRoles, ManageWebhooks
374
+
375
+ Args:
376
+ - channel_id (string): Discord channel ID
377
+ - target_id (string): Role or User ID to set permissions for
378
+ - target_type ('role' | 'member'): Whether target is a role or member
379
+ - allow (string[], optional): Permissions to allow (e.g., ['ViewChannel', 'SendMessages'])
380
+ - deny (string[], optional): Permissions to deny (e.g., ['SendMessages'])
381
+
382
+ Returns:
383
+ Confirmation of permission changes`,
384
+ inputSchema: SetChannelPermissionsSchema,
385
+ annotations: {
386
+ readOnlyHint: false,
387
+ destructiveHint: false,
388
+ idempotentHint: true,
389
+ openWorldHint: true,
390
+ },
391
+ }, async (params) => {
392
+ try {
393
+ const client = await getClient();
394
+ const channel = client.channels.cache.get(params.channel_id);
395
+ if (!channel || !('permissionOverwrites' in channel)) {
396
+ return {
397
+ isError: true,
398
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
399
+ };
400
+ }
401
+ const guildChannel = channel;
402
+ // Build permission overwrite object with individual permission keys
403
+ const permissionOverwrite = {};
404
+ // Set allowed permissions to true
405
+ if (params.allow) {
406
+ for (const perm of params.allow) {
407
+ permissionOverwrite[perm] = true;
408
+ }
409
+ }
410
+ // Set denied permissions to false
411
+ if (params.deny) {
412
+ for (const perm of params.deny) {
413
+ permissionOverwrite[perm] = false;
414
+ }
415
+ }
416
+ await guildChannel.permissionOverwrites.edit(params.target_id, permissionOverwrite, {
417
+ type: params.target_type === 'role' ? OverwriteType.Role : OverwriteType.Member,
418
+ });
419
+ const allowList = params.allow?.join(', ') || 'none';
420
+ const denyList = params.deny?.join(', ') || 'none';
421
+ return {
422
+ content: [{ type: "text", text: `Updated permissions for ${params.target_type} ${params.target_id} on channel ${params.channel_id}\nAllowed: ${allowList}\nDenied: ${denyList}` }],
423
+ };
424
+ }
425
+ catch (error) {
426
+ return {
427
+ isError: true,
428
+ content: [{ type: "text", text: `Error setting permissions: ${error.message}` }],
429
+ };
430
+ }
431
+ });
432
+ server.registerTool("discord_remove_channel_permissions", {
433
+ title: "Remove Channel Permission Overrides",
434
+ description: `Remove all permission overrides for a role or member on a channel.
435
+
436
+ Args:
437
+ - channel_id (string): Discord channel ID
438
+ - target_id (string): Role or User ID to remove permission overrides for
439
+
440
+ Returns:
441
+ Confirmation of removal`,
442
+ inputSchema: RemoveChannelPermissionsSchema,
443
+ annotations: {
444
+ readOnlyHint: false,
445
+ destructiveHint: true,
446
+ idempotentHint: true,
447
+ openWorldHint: true,
448
+ },
449
+ }, async (params) => {
450
+ try {
451
+ const client = await getClient();
452
+ const channel = client.channels.cache.get(params.channel_id);
453
+ if (!channel || !('permissionOverwrites' in channel)) {
454
+ return {
455
+ isError: true,
456
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
457
+ };
458
+ }
459
+ const guildChannel = channel;
460
+ await guildChannel.permissionOverwrites.delete(params.target_id);
461
+ return {
462
+ content: [{ type: "text", text: `Removed permission overrides for ${params.target_id} on channel ${params.channel_id}` }],
463
+ };
464
+ }
465
+ catch (error) {
466
+ return {
467
+ isError: true,
468
+ content: [{ type: "text", text: `Error removing permissions: ${error.message}` }],
469
+ };
470
+ }
471
+ });
472
+ server.registerTool("discord_get_channel_permissions", {
473
+ title: "Get Channel Permissions",
474
+ description: `Get all permission overrides for a channel.
475
+
476
+ Args:
477
+ - channel_id (string): Discord channel ID
478
+ - response_format ('markdown' | 'json'): Output format (default: 'json')
479
+
480
+ Returns:
481
+ List of permission overrides with allow/deny for each role/member`,
482
+ inputSchema: GetChannelPermissionsSchema,
483
+ annotations: {
484
+ readOnlyHint: true,
485
+ destructiveHint: false,
486
+ idempotentHint: true,
487
+ openWorldHint: true,
488
+ },
489
+ }, async (params) => {
490
+ try {
491
+ const client = await getClient();
492
+ const channel = client.channels.cache.get(params.channel_id);
493
+ if (!channel || !('permissionOverwrites' in channel)) {
494
+ return {
495
+ isError: true,
496
+ content: [{ type: "text", text: `Channel not found or doesn't support permissions: ${params.channel_id}` }],
497
+ };
498
+ }
499
+ const guildChannel = channel;
500
+ const overwrites = guildChannel.permissionOverwrites.cache;
501
+ const formatted = overwrites.map(ow => {
502
+ const allowedPerms = new PermissionsBitField(ow.allow).toArray();
503
+ const deniedPerms = new PermissionsBitField(ow.deny).toArray();
504
+ return {
505
+ id: ow.id,
506
+ type: ow.type === OverwriteType.Role ? 'role' : 'member',
507
+ allow: allowedPerms,
508
+ deny: deniedPerms,
509
+ };
510
+ });
511
+ if (params.response_format === 'json') {
512
+ return {
513
+ content: [{ type: "text", text: JSON.stringify(Array.from(formatted.values()), null, 2) }],
514
+ };
515
+ }
516
+ // Markdown format
517
+ const lines = Array.from(formatted.values()).map(ow => {
518
+ const allowStr = ow.allow.length > 0 ? ow.allow.join(', ') : 'none';
519
+ const denyStr = ow.deny.length > 0 ? ow.deny.join(', ') : 'none';
520
+ return `### ${ow.type}: ${ow.id}\n- **Allow**: ${allowStr}\n- **Deny**: ${denyStr}`;
521
+ });
522
+ const text = lines.length > 0 ? lines.join('\n\n') : 'No permission overrides on this channel.';
523
+ return {
524
+ content: [{ type: "text", text }],
525
+ };
526
+ }
527
+ catch (error) {
528
+ return {
529
+ isError: true,
530
+ content: [{ type: "text", text: `Error getting permissions: ${error.message}` }],
531
+ };
532
+ }
533
+ });
534
+ server.registerTool("discord_sync_channel_permissions", {
535
+ title: "Sync Channel Permissions with Category",
536
+ description: `Sync a channel's permissions with its parent category.
537
+
538
+ Args:
539
+ - channel_id (string): Discord channel ID
540
+
541
+ Returns:
542
+ Confirmation of sync`,
543
+ inputSchema: SyncChannelPermissionsSchema,
544
+ annotations: {
545
+ readOnlyHint: false,
546
+ destructiveHint: false,
547
+ idempotentHint: true,
548
+ openWorldHint: true,
549
+ },
550
+ }, async (params) => {
551
+ try {
552
+ const client = await getClient();
553
+ const channel = client.channels.cache.get(params.channel_id);
554
+ if (!channel || !('lockPermissions' in channel)) {
555
+ return {
556
+ isError: true,
557
+ content: [{ type: "text", text: `Channel not found or doesn't support permission sync: ${params.channel_id}` }],
558
+ };
559
+ }
560
+ await channel.lockPermissions();
561
+ return {
562
+ content: [{ type: "text", text: `Synced permissions for channel ${params.channel_id} with its parent category` }],
563
+ };
564
+ }
565
+ catch (error) {
566
+ return {
567
+ isError: true,
568
+ content: [{ type: "text", text: `Error syncing permissions: ${error.message}` }],
569
+ };
570
+ }
571
+ });
572
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCommunityTools(server: McpServer): void;