Merge pull request #25 from btallman/feat/roles-and-permissions

feat: add role management, member tools, voice channels, and channel permissions
This commit is contained in:
Barry Yip 2026-03-17 22:20:52 +08:00 committed by GitHub
commit a2890ae1b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 787 additions and 22 deletions

View File

@ -59,7 +59,8 @@ const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers
]
});

View File

@ -49,12 +49,11 @@ export const ReplyToForumSchema = z.object({
});
export const CreateTextChannelSchema = z.object({
guildId: z.string({ description: "The ID of the server (guild) where the text channel will be created." }),
channelName: z.string({ description: "The name for the new text channel." }),
topic: z.string({ description: "The (optional) topic/description for the channel." }).optional(),
reason: z.string({ description: "Optional reason for audit logs when creating the channel." }).optional()
}, {
description: "Create a new text channel in a specified server (guild)."
guildId: z.string(),
channelName: z.string(),
topic: z.string().optional(),
categoryId: z.string().optional(),
reason: z.string().optional()
});
// Category schemas
@ -204,6 +203,88 @@ export const ListServersSchema = z.object({}, {
description: "List all servers (guilds) the bot is a member of."
});
// Role schemas
export const ListRolesSchema = z.object({
guildId: z.string()
});
export const CreateRoleSchema = z.object({
guildId: z.string(),
name: z.string(),
color: z.string().optional(),
hoist: z.boolean().optional(),
mentionable: z.boolean().optional(),
permissions: z.array(z.string()).optional(),
reason: z.string().optional()
});
export const EditRoleSchema = z.object({
guildId: z.string(),
roleId: z.string(),
name: z.string().optional(),
color: z.string().optional(),
hoist: z.boolean().optional(),
mentionable: z.boolean().optional(),
permissions: z.array(z.string()).optional(),
position: z.number().optional(),
reason: z.string().optional()
});
export const DeleteRoleSchema = z.object({
guildId: z.string(),
roleId: z.string(),
reason: z.string().optional()
});
export const AssignRoleSchema = z.object({
guildId: z.string(),
userId: z.string(),
roleId: z.string(),
reason: z.string().optional()
});
export const RemoveRoleSchema = z.object({
guildId: z.string(),
userId: z.string(),
roleId: z.string(),
reason: z.string().optional()
});
export const ListMembersSchema = z.object({
guildId: z.string(),
limit: z.number().min(1).max(1000).optional().default(100),
after: z.string().optional()
});
export const GetMemberSchema = z.object({
guildId: z.string(),
userId: z.string()
});
// Channel permission schemas
export const SetChannelPermissionsSchema = z.object({
channelId: z.string(),
roleId: z.string(),
allow: z.array(z.string()).optional(),
deny: z.array(z.string()).optional(),
reason: z.string().optional()
});
export const RemoveChannelPermissionsSchema = z.object({
channelId: z.string(),
roleId: z.string(),
reason: z.string().optional()
});
// Voice channel schema
export const CreateVoiceChannelSchema = z.object({
guildId: z.string(),
channelName: z.string(),
categoryId: z.string().optional(),
userLimit: z.number().min(0).max(99).optional(),
reason: z.string().optional()
});
export const SearchMessagesSchema = z.object({
guildId: z.string({ description: "The ID of the server (guild) to search in." }).min(1, "guildId is required"),
// Optional filters

View File

@ -34,6 +34,17 @@ import {
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler,
createVoiceChannelHandler,
setChannelPermissionsHandler,
removeChannelPermissionsHandler,
listRolesHandler,
createRoleHandler,
editRoleHandler,
deleteRoleHandler,
assignRoleHandler,
removeRoleHandler,
listMembersHandler,
getMemberHandler,
listServersHandler,
searchMessagesHandler
} from './tools/tools.js';
@ -214,6 +225,50 @@ export class DiscordMCPServer {
toolResponse = await searchMessagesHandler(args, this.toolContext);
return toolResponse;
case "discord_list_roles":
toolResponse = await listRolesHandler(args, this.toolContext);
return toolResponse;
case "discord_create_role":
toolResponse = await createRoleHandler(args, this.toolContext);
return toolResponse;
case "discord_edit_role":
toolResponse = await editRoleHandler(args, this.toolContext);
return toolResponse;
case "discord_delete_role":
toolResponse = await deleteRoleHandler(args, this.toolContext);
return toolResponse;
case "discord_assign_role":
toolResponse = await assignRoleHandler(args, this.toolContext);
return toolResponse;
case "discord_remove_role":
toolResponse = await removeRoleHandler(args, this.toolContext);
return toolResponse;
case "discord_list_members":
toolResponse = await listMembersHandler(args, this.toolContext);
return toolResponse;
case "discord_get_member":
toolResponse = await getMemberHandler(args, this.toolContext);
return toolResponse;
case "discord_create_voice_channel":
toolResponse = await createVoiceChannelHandler(args, this.toolContext);
return toolResponse;
case "discord_set_channel_permissions":
toolResponse = await setChannelPermissionsHandler(args, this.toolContext);
return toolResponse;
case "discord_remove_channel_permissions":
toolResponse = await removeChannelPermissionsHandler(args, this.toolContext);
return toolResponse;
default:
throw new Error(`Unknown tool: ${name}`);
}

View File

@ -129,13 +129,14 @@ export const toolList = [
},
{
name: "discord_create_text_channel",
description: "Creates a new text channel in a Discord server with an optional topic",
description: "Creates a new text channel in a Discord server with an optional topic and parent category",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
channelName: { type: "string" },
topic: { type: "string" }
topic: { type: "string" },
categoryId: { type: "string", description: "Parent category ID to place the channel under" }
},
required: ["guildId", "channelName"]
}
@ -378,5 +379,161 @@ export const toolList = [
},
required: ["guildId"]
}
},
{
name: "discord_list_roles",
description: "Lists all roles in a Discord server with their properties",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" }
},
required: ["guildId"]
}
},
{
name: "discord_create_role",
description: "Creates a new role in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
name: { type: "string" },
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
hoist: { type: "boolean", description: "Whether the role should be displayed separately in the sidebar" },
mentionable: { type: "boolean", description: "Whether the role can be mentioned by anyone" },
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names (e.g. ['SendMessages', 'ViewChannel'])" },
reason: { type: "string" }
},
required: ["guildId", "name"]
}
},
{
name: "discord_edit_role",
description: "Edits an existing role in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
roleId: { type: "string" },
name: { type: "string" },
color: { type: "string", description: "Hex color string (e.g. '#FF0000') or color name" },
hoist: { type: "boolean" },
mentionable: { type: "boolean" },
permissions: { type: "array", items: { type: "string" }, description: "Array of permission flag names" },
position: { type: "number", description: "New position in the role hierarchy" },
reason: { type: "string" }
},
required: ["guildId", "roleId"]
}
},
{
name: "discord_delete_role",
description: "Deletes a role from a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
roleId: { type: "string" },
reason: { type: "string" }
},
required: ["guildId", "roleId"]
}
},
{
name: "discord_assign_role",
description: "Assigns a role to a member in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
userId: { type: "string" },
roleId: { type: "string" },
reason: { type: "string" }
},
required: ["guildId", "userId", "roleId"]
}
},
{
name: "discord_remove_role",
description: "Removes a role from a member in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
userId: { type: "string" },
roleId: { type: "string" },
reason: { type: "string" }
},
required: ["guildId", "userId", "roleId"]
}
},
{
name: "discord_list_members",
description: "Lists members in a Discord server with their roles",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
limit: { type: "number", description: "Maximum number of members to return (default 100, max 1000)", minimum: 1, maximum: 1000, default: 100 },
after: { type: "string", description: "User ID to paginate after" }
},
required: ["guildId"]
}
},
{
name: "discord_get_member",
description: "Gets detailed information about a specific member in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
userId: { type: "string" }
},
required: ["guildId", "userId"]
}
},
{
name: "discord_create_voice_channel",
description: "Creates a new voice channel in a Discord server with an optional parent category",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
channelName: { type: "string" },
categoryId: { type: "string", description: "Parent category ID to place the channel under" },
userLimit: { type: "number", description: "Maximum number of users allowed (0 for unlimited)", minimum: 0, maximum: 99 },
reason: { type: "string" }
},
required: ["guildId", "channelName"]
}
},
{
name: "discord_set_channel_permissions",
description: "Sets permission overrides for a role or user on a channel or category",
inputSchema: {
type: "object",
properties: {
channelId: { type: "string", description: "Channel or category ID" },
roleId: { type: "string", description: "Role or user ID to set permissions for" },
allow: { type: "array", items: { type: "string" }, description: "Permission flags to allow (e.g. ['ViewChannel', 'SendMessages'])" },
deny: { type: "array", items: { type: "string" }, description: "Permission flags to deny (e.g. ['ViewChannel', 'SendMessages'])" },
reason: { type: "string" }
},
required: ["channelId", "roleId"]
}
},
{
name: "discord_remove_channel_permissions",
description: "Removes all permission overrides for a role or user on a channel or category",
inputSchema: {
type: "object",
properties: {
channelId: { type: "string", description: "Channel or category ID" },
roleId: { type: "string", description: "Role or user ID to remove overrides for" },
reason: { type: "string" }
},
required: ["channelId", "roleId"]
}
}
];

View File

@ -1,13 +1,16 @@
import { z } from "zod";
import { ChannelType } from "discord.js";
import { ChannelType, PermissionsBitField } from "discord.js";
import { ToolContext, ToolResponse } from "./types.js";
import {
CreateTextChannelSchema,
DeleteChannelSchema,
import {
CreateTextChannelSchema,
DeleteChannelSchema,
ReadMessagesSchema,
CreateCategorySchema,
EditCategorySchema,
DeleteCategorySchema
DeleteCategorySchema,
SetChannelPermissionsSchema,
RemoveChannelPermissionsSchema,
CreateVoiceChannelSchema
} from "../schemas.js";
import { handleDiscordError } from "../errorHandler.js";
@ -110,7 +113,7 @@ export async function createTextChannelHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, channelName, topic, reason } = CreateTextChannelSchema.parse(args);
const { guildId, channelName, topic, categoryId, reason } = CreateTextChannelSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
@ -133,6 +136,7 @@ export async function createTextChannelHandler(
type: ChannelType.GuildText
};
if (topic) channelOptions.topic = topic;
if (categoryId) channelOptions.parent = categoryId;
if (reason) channelOptions.reason = reason;
const channel = await guild.channels.create(channelOptions);
@ -246,13 +250,146 @@ export async function readMessagesHandler(
})).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
return {
content: [{
type: "text",
content: [{
type: "text",
text: JSON.stringify({
channelId,
messageCount: formattedMessages.length,
messages: formattedMessages
}, null, 2)
}, null, 2)
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Helper to resolve permission strings to bitfield
function resolvePermissions(perms: string[]): bigint {
let bits = BigInt(0);
for (const perm of perms) {
const flag = PermissionsBitField.Flags[perm as keyof typeof PermissionsBitField.Flags];
if (flag !== undefined) {
bits |= flag;
}
}
return bits;
}
// Voice channel creation handler
export async function createVoiceChannelHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, channelName, categoryId, userLimit, reason } = CreateVoiceChannelSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
if (!guild) {
return {
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
isError: true
};
}
const channelOptions: any = {
name: channelName,
type: ChannelType.GuildVoice
};
if (categoryId) channelOptions.parent = categoryId;
if (typeof userLimit === "number") channelOptions.userLimit = userLimit;
if (reason) channelOptions.reason = reason;
const channel = await guild.channels.create(channelOptions);
return {
content: [{
type: "text",
text: `Successfully created voice channel "${channelName}" with ID: ${channel.id}`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Set channel permission overrides
export async function setChannelPermissionsHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { channelId, roleId, allow, deny, reason } = SetChannelPermissionsSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const channel = await context.client.channels.fetch(channelId);
if (!channel) {
return {
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
isError: true
};
}
if (!('permissionOverwrites' in channel) || !channel.permissionOverwrites) {
return {
content: [{ type: "text", text: `This channel type does not support permission overrides` }],
isError: true
};
}
const overwrite: any = { id: roleId };
if (allow && allow.length > 0) overwrite.allow = resolvePermissions(allow);
if (deny && deny.length > 0) overwrite.deny = resolvePermissions(deny);
await channel.permissionOverwrites.edit(roleId, {
...(allow && allow.length > 0 ? Object.fromEntries(allow.map(p => [p, true])) : {}),
...(deny && deny.length > 0 ? Object.fromEntries(deny.map(p => [p, false])) : {})
}, { reason: reason || "Permissions updated via API" });
return {
content: [{
type: "text",
text: `Successfully updated permissions for role/user ${roleId} on channel ${channelId}`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Remove channel permission overrides
export async function removeChannelPermissionsHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { channelId, roleId, reason } = RemoveChannelPermissionsSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const channel = await context.client.channels.fetch(channelId);
if (!channel) {
return {
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
isError: true
};
}
if (!('permissionOverwrites' in channel) || !channel.permissionOverwrites) {
return {
content: [{ type: "text", text: `This channel type does not support permission overrides` }],
isError: true
};
}
await channel.permissionOverwrites.delete(roleId, reason || "Permission override removed via API");
return {
content: [{
type: "text",
text: `Successfully removed permission overrides for role/user ${roleId} on channel ${channelId}`
}]
};
} catch (error) {

310
src/tools/roles.ts Normal file
View File

@ -0,0 +1,310 @@
import { ChannelType, PermissionsBitField } from "discord.js";
import { ToolContext, ToolResponse } from "./types.js";
import {
ListRolesSchema,
CreateRoleSchema,
EditRoleSchema,
DeleteRoleSchema,
AssignRoleSchema,
RemoveRoleSchema,
ListMembersSchema,
GetMemberSchema
} from "../schemas.js";
import { handleDiscordError } from "../errorHandler.js";
// Helper to resolve permission strings to bitfield
function resolvePermissions(perms: string[]): bigint {
let bits = BigInt(0);
for (const perm of perms) {
const flag = PermissionsBitField.Flags[perm as keyof typeof PermissionsBitField.Flags];
if (flag !== undefined) {
bits |= flag;
}
}
return bits;
}
export async function listRolesHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId } = ListRolesSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const roles = await guild.roles.fetch();
const formatted = roles
.sort((a, b) => b.position - a.position)
.map(role => ({
id: role.id,
name: role.name,
color: role.hexColor,
position: role.position,
hoist: role.hoist,
mentionable: role.mentionable,
managed: role.managed,
memberCount: role.members.size
}));
return {
content: [{
type: "text",
text: JSON.stringify({ guildId, roleCount: formatted.length, roles: formatted }, null, 2)
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function createRoleHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, name, color, hoist, mentionable, permissions, reason } = CreateRoleSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const options: any = { name };
if (color) options.color = color;
if (typeof hoist === "boolean") options.hoist = hoist;
if (typeof mentionable === "boolean") options.mentionable = mentionable;
if (permissions) options.permissions = resolvePermissions(permissions);
if (reason) options.reason = reason;
const role = await guild.roles.create(options);
return {
content: [{
type: "text",
text: `Successfully created role "${name}" with ID: ${role.id}`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function editRoleHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, roleId, name, color, hoist, mentionable, permissions, position, reason } = EditRoleSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const role = await guild.roles.fetch(roleId);
if (!role) {
return {
content: [{ type: "text", text: `Cannot find role with ID: ${roleId}` }],
isError: true
};
}
const update: any = {};
if (name) update.name = name;
if (color) update.color = color;
if (typeof hoist === "boolean") update.hoist = hoist;
if (typeof mentionable === "boolean") update.mentionable = mentionable;
if (permissions) update.permissions = resolvePermissions(permissions);
if (typeof position === "number") update.position = position;
if (reason) update.reason = reason;
await role.edit(update);
return {
content: [{
type: "text",
text: `Successfully edited role "${role.name}" (ID: ${roleId})`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function deleteRoleHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, roleId, reason } = DeleteRoleSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const role = await guild.roles.fetch(roleId);
if (!role) {
return {
content: [{ type: "text", text: `Cannot find role with ID: ${roleId}` }],
isError: true
};
}
const roleName = role.name;
await role.delete(reason || "Role deleted via API");
return {
content: [{
type: "text",
text: `Successfully deleted role "${roleName}" (ID: ${roleId})`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function assignRoleHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, userId, roleId, reason } = AssignRoleSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const member = await guild.members.fetch(userId);
if (!member) {
return {
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
isError: true
};
}
await member.roles.add(roleId, reason || "Role assigned via API");
return {
content: [{
type: "text",
text: `Successfully assigned role ${roleId} to member ${member.user.tag} (${userId})`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function removeRoleHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, userId, roleId, reason } = RemoveRoleSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const member = await guild.members.fetch(userId);
if (!member) {
return {
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
isError: true
};
}
await member.roles.remove(roleId, reason || "Role removed via API");
return {
content: [{
type: "text",
text: `Successfully removed role ${roleId} from member ${member.user.tag} (${userId})`
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function listMembersHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, limit, after } = ListMembersSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const fetchOptions: { limit: number; after?: string } = { limit };
if (after) fetchOptions.after = after;
const members = await guild.members.list(fetchOptions);
const formatted = members.map((member) => ({
id: member.id,
username: member.user.username,
displayName: member.displayName,
bot: member.user.bot,
roles: member.roles.cache
.filter((r) => r.name !== "@everyone")
.map((r) => ({ id: r.id, name: r.name })),
joinedAt: member.joinedAt
}));
return {
content: [{
type: "text",
text: JSON.stringify({ guildId, memberCount: formatted.length, members: formatted }, null, 2)
}]
};
} catch (error) {
return handleDiscordError(error);
}
}
export async function getMemberHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, userId } = GetMemberSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in." }],
isError: true
};
}
const guild = await context.client.guilds.fetch(guildId);
const member = await guild.members.fetch(userId);
if (!member) {
return {
content: [{ type: "text", text: `Cannot find member with ID: ${userId}` }],
isError: true
};
}
const formatted = {
id: member.id,
username: member.user.username,
displayName: member.displayName,
bot: member.user.bot,
roles: member.roles.cache
.filter(r => r.name !== "@everyone")
.map(r => ({ id: r.id, name: r.name })),
joinedAt: member.joinedAt,
nickname: member.nickname,
avatar: member.displayAvatarURL()
};
return {
content: [{
type: "text",
text: JSON.stringify(formatted, null, 2)
}]
};
} catch (error) {
return handleDiscordError(error);
}
}

View File

@ -19,10 +19,13 @@ import {
readMessagesHandler,
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler
deleteCategoryHandler,
createVoiceChannelHandler,
setChannelPermissionsHandler,
removeChannelPermissionsHandler
} from './channel.js';
import {
getServerInfoHandler,
import {
getServerInfoHandler,
listServersHandler,
searchMessagesHandler
} from "./server.js";
@ -38,6 +41,16 @@ import {
editWebhookHandler,
deleteWebhookHandler
} from './webhooks.js';
import {
listRolesHandler,
createRoleHandler,
editRoleHandler,
deleteRoleHandler,
assignRoleHandler,
removeRoleHandler,
listMembersHandler,
getMemberHandler
} from './roles.js';
// Export tool handlers
export {
@ -67,6 +80,17 @@ export {
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler,
createVoiceChannelHandler,
setChannelPermissionsHandler,
removeChannelPermissionsHandler,
listRolesHandler,
createRoleHandler,
editRoleHandler,
deleteRoleHandler,
assignRoleHandler,
removeRoleHandler,
listMembersHandler,
getMemberHandler,
listServersHandler,
searchMessagesHandler
};
@ -77,4 +101,4 @@ export { ToolResponse, ToolContext, ToolHandler };
// Create tool context
export function createToolContext(client: Client): ToolContext {
return { client };
}
}