From f5e6859ff833396da045b4645883b704ac13a6ee Mon Sep 17 00:00:00 2001 From: Ben Tallman Date: Mon, 9 Mar 2026 15:20:51 -0700 Subject: [PATCH] feat: add role management, member tools, voice channels, and channel permissions Add 12 new tools for Discord server management: Role management: - discord_list_roles: list all roles with properties - discord_create_role: create roles with color, hoist, permissions - discord_edit_role: modify existing role properties - discord_delete_role: remove a role Member management: - discord_assign_role: add a role to a member - discord_remove_role: remove a role from a member - discord_list_members: list guild members with roles - discord_get_member: get detailed member info Channel enhancements: - discord_create_voice_channel: create voice channels with optional category - discord_set_channel_permissions: set permission overrides on channels/categories - discord_remove_channel_permissions: remove permission overrides - Add categoryId parameter to discord_create_text_channel Also adds GuildMembers intent for member/role operations. Co-Authored-By: Claude Opus 4.6 --- src/index.ts | 3 +- src/schemas.ts | 83 ++++++++++++ src/server.ts | 55 ++++++++ src/toolList.ts | 161 +++++++++++++++++++++- src/tools/channel.ts | 155 ++++++++++++++++++++-- src/tools/roles.ts | 310 +++++++++++++++++++++++++++++++++++++++++++ src/tools/tools.ts | 32 ++++- 7 files changed, 783 insertions(+), 16 deletions(-) create mode 100644 src/tools/roles.ts diff --git a/src/index.ts b/src/index.ts index c4ef47a..4fb1838 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,7 +59,8 @@ const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers ] }); diff --git a/src/schemas.ts b/src/schemas.ts index 08f6a52..b0f7e80 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -40,6 +40,7 @@ export const CreateTextChannelSchema = z.object({ guildId: z.string(), channelName: z.string(), topic: z.string().optional(), + categoryId: z.string().optional(), reason: z.string().optional() }); @@ -140,6 +141,88 @@ export const DeleteWebhookSchema = z.object({ export const ListServersSchema = z.object({}); +// 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().min(1, "guildId is required"), // Optional filters diff --git a/src/server.ts b/src/server.ts index 535349b..8d50ec5 100644 --- a/src/server.ts +++ b/src/server.ts @@ -31,6 +31,17 @@ import { createCategoryHandler, editCategoryHandler, deleteCategoryHandler, + createVoiceChannelHandler, + setChannelPermissionsHandler, + removeChannelPermissionsHandler, + listRolesHandler, + createRoleHandler, + editRoleHandler, + deleteRoleHandler, + assignRoleHandler, + removeRoleHandler, + listMembersHandler, + getMemberHandler, listServersHandler, searchMessagesHandler } from './tools/tools.js'; @@ -196,6 +207,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}`); } diff --git a/src/toolList.ts b/src/toolList.ts index 2fc7864..1ad76f7 100644 --- a/src/toolList.ts +++ b/src/toolList.ts @@ -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"] } @@ -339,5 +340,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"] + } } ]; \ No newline at end of file diff --git a/src/tools/channel.ts b/src/tools/channel.ts index 75d3e62..231cbe0 100644 --- a/src/tools/channel.ts +++ b/src/tools/channel.ts @@ -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 { - 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 { + 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 { + 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 { + 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) { diff --git a/src/tools/roles.ts b/src/tools/roles.ts new file mode 100644 index 0000000..8b37542 --- /dev/null +++ b/src/tools/roles.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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); + } +} diff --git a/src/tools/tools.ts b/src/tools/tools.ts index a35a785..89d6a3e 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -17,10 +17,13 @@ import { readMessagesHandler, createCategoryHandler, editCategoryHandler, - deleteCategoryHandler + deleteCategoryHandler, + createVoiceChannelHandler, + setChannelPermissionsHandler, + removeChannelPermissionsHandler } from './channel.js'; -import { - getServerInfoHandler, +import { + getServerInfoHandler, listServersHandler, searchMessagesHandler } from "./server.js"; @@ -36,6 +39,16 @@ import { editWebhookHandler, deleteWebhookHandler } from './webhooks.js'; +import { + listRolesHandler, + createRoleHandler, + editRoleHandler, + deleteRoleHandler, + assignRoleHandler, + removeRoleHandler, + listMembersHandler, + getMemberHandler +} from './roles.js'; // Export tool handlers export { @@ -62,6 +75,17 @@ export { createCategoryHandler, editCategoryHandler, deleteCategoryHandler, + createVoiceChannelHandler, + setChannelPermissionsHandler, + removeChannelPermissionsHandler, + listRolesHandler, + createRoleHandler, + editRoleHandler, + deleteRoleHandler, + assignRoleHandler, + removeRoleHandler, + listMembersHandler, + getMemberHandler, listServersHandler, searchMessagesHandler }; @@ -72,4 +96,4 @@ export { ToolResponse, ToolContext, ToolHandler }; // Create tool context export function createToolContext(client: Client): ToolContext { return { client }; -} \ No newline at end of file +}