Skip to content

Commit a2890ae

Browse files
authored
Merge pull request #25 from btallman/feat/roles-and-permissions
feat: add role management, member tools, voice channels, and channel permissions
2 parents 53ed667 + 1338f99 commit a2890ae

File tree

7 files changed

+787
-22
lines changed

7 files changed

+787
-22
lines changed

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ const client = new Client({
5959
intents: [
6060
GatewayIntentBits.Guilds,
6161
GatewayIntentBits.GuildMessages,
62-
GatewayIntentBits.MessageContent
62+
GatewayIntentBits.MessageContent,
63+
GatewayIntentBits.GuildMembers
6364
]
6465
});
6566

src/schemas.ts

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,11 @@ export const ReplyToForumSchema = z.object({
4949
});
5050

5151
export const CreateTextChannelSchema = z.object({
52-
guildId: z.string({ description: "The ID of the server (guild) where the text channel will be created." }),
53-
channelName: z.string({ description: "The name for the new text channel." }),
54-
topic: z.string({ description: "The (optional) topic/description for the channel." }).optional(),
55-
reason: z.string({ description: "Optional reason for audit logs when creating the channel." }).optional()
56-
}, {
57-
description: "Create a new text channel in a specified server (guild)."
52+
guildId: z.string(),
53+
channelName: z.string(),
54+
topic: z.string().optional(),
55+
categoryId: z.string().optional(),
56+
reason: z.string().optional()
5857
});
5958

6059
// Category schemas
@@ -204,6 +203,88 @@ export const ListServersSchema = z.object({}, {
204203
description: "List all servers (guilds) the bot is a member of."
205204
});
206205

206+
// Role schemas
207+
export const ListRolesSchema = z.object({
208+
guildId: z.string()
209+
});
210+
211+
export const CreateRoleSchema = z.object({
212+
guildId: z.string(),
213+
name: z.string(),
214+
color: z.string().optional(),
215+
hoist: z.boolean().optional(),
216+
mentionable: z.boolean().optional(),
217+
permissions: z.array(z.string()).optional(),
218+
reason: z.string().optional()
219+
});
220+
221+
export const EditRoleSchema = z.object({
222+
guildId: z.string(),
223+
roleId: z.string(),
224+
name: z.string().optional(),
225+
color: z.string().optional(),
226+
hoist: z.boolean().optional(),
227+
mentionable: z.boolean().optional(),
228+
permissions: z.array(z.string()).optional(),
229+
position: z.number().optional(),
230+
reason: z.string().optional()
231+
});
232+
233+
export const DeleteRoleSchema = z.object({
234+
guildId: z.string(),
235+
roleId: z.string(),
236+
reason: z.string().optional()
237+
});
238+
239+
export const AssignRoleSchema = z.object({
240+
guildId: z.string(),
241+
userId: z.string(),
242+
roleId: z.string(),
243+
reason: z.string().optional()
244+
});
245+
246+
export const RemoveRoleSchema = z.object({
247+
guildId: z.string(),
248+
userId: z.string(),
249+
roleId: z.string(),
250+
reason: z.string().optional()
251+
});
252+
253+
export const ListMembersSchema = z.object({
254+
guildId: z.string(),
255+
limit: z.number().min(1).max(1000).optional().default(100),
256+
after: z.string().optional()
257+
});
258+
259+
export const GetMemberSchema = z.object({
260+
guildId: z.string(),
261+
userId: z.string()
262+
});
263+
264+
// Channel permission schemas
265+
export const SetChannelPermissionsSchema = z.object({
266+
channelId: z.string(),
267+
roleId: z.string(),
268+
allow: z.array(z.string()).optional(),
269+
deny: z.array(z.string()).optional(),
270+
reason: z.string().optional()
271+
});
272+
273+
export const RemoveChannelPermissionsSchema = z.object({
274+
channelId: z.string(),
275+
roleId: z.string(),
276+
reason: z.string().optional()
277+
});
278+
279+
// Voice channel schema
280+
export const CreateVoiceChannelSchema = z.object({
281+
guildId: z.string(),
282+
channelName: z.string(),
283+
categoryId: z.string().optional(),
284+
userLimit: z.number().min(0).max(99).optional(),
285+
reason: z.string().optional()
286+
});
287+
207288
export const SearchMessagesSchema = z.object({
208289
guildId: z.string({ description: "The ID of the server (guild) to search in." }).min(1, "guildId is required"),
209290
// Optional filters

src/server.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ import {
3434
createCategoryHandler,
3535
editCategoryHandler,
3636
deleteCategoryHandler,
37+
createVoiceChannelHandler,
38+
setChannelPermissionsHandler,
39+
removeChannelPermissionsHandler,
40+
listRolesHandler,
41+
createRoleHandler,
42+
editRoleHandler,
43+
deleteRoleHandler,
44+
assignRoleHandler,
45+
removeRoleHandler,
46+
listMembersHandler,
47+
getMemberHandler,
3748
listServersHandler,
3849
searchMessagesHandler
3950
} from './tools/tools.js';
@@ -214,6 +225,50 @@ export class DiscordMCPServer {
214225
toolResponse = await searchMessagesHandler(args, this.toolContext);
215226
return toolResponse;
216227

228+
case "discord_list_roles":
229+
toolResponse = await listRolesHandler(args, this.toolContext);
230+
return toolResponse;
231+
232+
case "discord_create_role":
233+
toolResponse = await createRoleHandler(args, this.toolContext);
234+
return toolResponse;
235+
236+
case "discord_edit_role":
237+
toolResponse = await editRoleHandler(args, this.toolContext);
238+
return toolResponse;
239+
240+
case "discord_delete_role":
241+
toolResponse = await deleteRoleHandler(args, this.toolContext);
242+
return toolResponse;
243+
244+
case "discord_assign_role":
245+
toolResponse = await assignRoleHandler(args, this.toolContext);
246+
return toolResponse;
247+
248+
case "discord_remove_role":
249+
toolResponse = await removeRoleHandler(args, this.toolContext);
250+
return toolResponse;
251+
252+
case "discord_list_members":
253+
toolResponse = await listMembersHandler(args, this.toolContext);
254+
return toolResponse;
255+
256+
case "discord_get_member":
257+
toolResponse = await getMemberHandler(args, this.toolContext);
258+
return toolResponse;
259+
260+
case "discord_create_voice_channel":
261+
toolResponse = await createVoiceChannelHandler(args, this.toolContext);
262+
return toolResponse;
263+
264+
case "discord_set_channel_permissions":
265+
toolResponse = await setChannelPermissionsHandler(args, this.toolContext);
266+
return toolResponse;
267+
268+
case "discord_remove_channel_permissions":
269+
toolResponse = await removeChannelPermissionsHandler(args, this.toolContext);
270+
return toolResponse;
271+
217272
default:
218273
throw new Error(`Unknown tool: ${name}`);
219274
}

src/toolList.ts

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,14 @@ export const toolList = [
129129
},
130130
{
131131
name: "discord_create_text_channel",
132-
description: "Creates a new text channel in a Discord server with an optional topic",
132+
description: "Creates a new text channel in a Discord server with an optional topic and parent category",
133133
inputSchema: {
134134
type: "object",
135135
properties: {
136136
guildId: { type: "string" },
137137
channelName: { type: "string" },
138-
topic: { type: "string" }
138+
topic: { type: "string" },
139+
categoryId: { type: "string", description: "Parent category ID to place the channel under" }
139140
},
140141
required: ["guildId", "channelName"]
141142
}
@@ -378,5 +379,161 @@ export const toolList = [
378379
},
379380
required: ["guildId"]
380381
}
382+
},
383+
{
384+
name: "discord_list_roles",
385+
description: "Lists all roles in a Discord server with their properties",
386+
inputSchema: {
387+
type: "object",
388+
properties: {
389+
guildId: { type: "string" }
390+
},
391+
required: ["guildId"]
392+
}
393+
},
394+
{
395+
name: "discord_create_role",
396+
description: "Creates a new role in a Discord server",
397+
inputSchema: {
398+
type: "object",
399+
properties: {
400+
guildId: { type: "string" },
401+
name: { type: "string" },
402+
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
403+
hoist: { type: "boolean", description: "Whether the role should be displayed separately in the sidebar" },
404+
mentionable: { type: "boolean", description: "Whether the role can be mentioned by anyone" },
405+
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names (e.g. ['SendMessages', 'ViewChannel'])" },
406+
reason: { type: "string" }
407+
},
408+
required: ["guildId", "name"]
409+
}
410+
},
411+
{
412+
name: "discord_edit_role",
413+
description: "Edits an existing role in a Discord server",
414+
inputSchema: {
415+
type: "object",
416+
properties: {
417+
guildId: { type: "string" },
418+
roleId: { type: "string" },
419+
name: { type: "string" },
420+
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
421+
hoist: { type: "boolean" },
422+
mentionable: { type: "boolean" },
423+
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names" },
424+
position: { type: "number", description: "New position in the role hierarchy" },
425+
reason: { type: "string" }
426+
},
427+
required: ["guildId", "roleId"]
428+
}
429+
},
430+
{
431+
name: "discord_delete_role",
432+
description: "Deletes a role from a Discord server",
433+
inputSchema: {
434+
type: "object",
435+
properties: {
436+
guildId: { type: "string" },
437+
roleId: { type: "string" },
438+
reason: { type: "string" }
439+
},
440+
required: ["guildId", "roleId"]
441+
}
442+
},
443+
{
444+
name: "discord_assign_role",
445+
description: "Assigns a role to a member in a Discord server",
446+
inputSchema: {
447+
type: "object",
448+
properties: {
449+
guildId: { type: "string" },
450+
userId: { type: "string" },
451+
roleId: { type: "string" },
452+
reason: { type: "string" }
453+
},
454+
required: ["guildId", "userId", "roleId"]
455+
}
456+
},
457+
{
458+
name: "discord_remove_role",
459+
description: "Removes a role from a member in a Discord server",
460+
inputSchema: {
461+
type: "object",
462+
properties: {
463+
guildId: { type: "string" },
464+
userId: { type: "string" },
465+
roleId: { type: "string" },
466+
reason: { type: "string" }
467+
},
468+
required: ["guildId", "userId", "roleId"]
469+
}
470+
},
471+
{
472+
name: "discord_list_members",
473+
description: "Lists members in a Discord server with their roles",
474+
inputSchema: {
475+
type: "object",
476+
properties: {
477+
guildId: { type: "string" },
478+
limit: { type: "number", description: "Maximum number of members to return (default 100, max 1000)", minimum: 1, maximum: 1000, default: 100 },
479+
after: { type: "string", description: "User ID to paginate after" }
480+
},
481+
required: ["guildId"]
482+
}
483+
},
484+
{
485+
name: "discord_get_member",
486+
description: "Gets detailed information about a specific member in a Discord server",
487+
inputSchema: {
488+
type: "object",
489+
properties: {
490+
guildId: { type: "string" },
491+
userId: { type: "string" }
492+
},
493+
required: ["guildId", "userId"]
494+
}
495+
},
496+
{
497+
name: "discord_create_voice_channel",
498+
description: "Creates a new voice channel in a Discord server with an optional parent category",
499+
inputSchema: {
500+
type: "object",
501+
properties: {
502+
guildId: { type: "string" },
503+
channelName: { type: "string" },
504+
categoryId: { type: "string", description: "Parent category ID to place the channel under" },
505+
userLimit: { type: "number", description: "Maximum number of users allowed (0 for unlimited)", minimum: 0, maximum: 99 },
506+
reason: { type: "string" }
507+
},
508+
required: ["guildId", "channelName"]
509+
}
510+
},
511+
{
512+
name: "discord_set_channel_permissions",
513+
description: "Sets permission overrides for a role or user on a channel or category",
514+
inputSchema: {
515+
type: "object",
516+
properties: {
517+
channelId: { type: "string", description: "Channel or category ID" },
518+
roleId: { type: "string", description: "Role or user ID to set permissions for" },
519+
allow: { type: "array", items: { type: "string" }, description: "Permission flags to allow (e.g. ['ViewChannel', 'SendMessages'])" },
520+
deny: { type: "array", items: { type: "string" }, description: "Permission flags to deny (e.g. ['ViewChannel', 'SendMessages'])" },
521+
reason: { type: "string" }
522+
},
523+
required: ["channelId", "roleId"]
524+
}
525+
},
526+
{
527+
name: "discord_remove_channel_permissions",
528+
description: "Removes all permission overrides for a role or user on a channel or category",
529+
inputSchema: {
530+
type: "object",
531+
properties: {
532+
channelId: { type: "string", description: "Channel or category ID" },
533+
roleId: { type: "string", description: "Role or user ID to remove overrides for" },
534+
reason: { type: "string" }
535+
},
536+
required: ["channelId", "roleId"]
537+
}
381538
}
382539
];

0 commit comments

Comments
 (0)