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 <noreply@anthropic.com>
This commit is contained in:
parent
6558290613
commit
f5e6859ff8
|
|
@ -59,7 +59,8 @@ const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMessages,
|
GatewayIntentBits.GuildMessages,
|
||||||
GatewayIntentBits.MessageContent
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildMembers
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export const CreateTextChannelSchema = z.object({
|
||||||
guildId: z.string(),
|
guildId: z.string(),
|
||||||
channelName: z.string(),
|
channelName: z.string(),
|
||||||
topic: z.string().optional(),
|
topic: z.string().optional(),
|
||||||
|
categoryId: z.string().optional(),
|
||||||
reason: z.string().optional()
|
reason: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -140,6 +141,88 @@ export const DeleteWebhookSchema = z.object({
|
||||||
|
|
||||||
export const ListServersSchema = 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({
|
export const SearchMessagesSchema = z.object({
|
||||||
guildId: z.string().min(1, "guildId is required"),
|
guildId: z.string().min(1, "guildId is required"),
|
||||||
// Optional filters
|
// Optional filters
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,17 @@ import {
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
|
createVoiceChannelHandler,
|
||||||
|
setChannelPermissionsHandler,
|
||||||
|
removeChannelPermissionsHandler,
|
||||||
|
listRolesHandler,
|
||||||
|
createRoleHandler,
|
||||||
|
editRoleHandler,
|
||||||
|
deleteRoleHandler,
|
||||||
|
assignRoleHandler,
|
||||||
|
removeRoleHandler,
|
||||||
|
listMembersHandler,
|
||||||
|
getMemberHandler,
|
||||||
listServersHandler,
|
listServersHandler,
|
||||||
searchMessagesHandler
|
searchMessagesHandler
|
||||||
} from './tools/tools.js';
|
} from './tools/tools.js';
|
||||||
|
|
@ -196,6 +207,50 @@ export class DiscordMCPServer {
|
||||||
toolResponse = await searchMessagesHandler(args, this.toolContext);
|
toolResponse = await searchMessagesHandler(args, this.toolContext);
|
||||||
return toolResponse;
|
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:
|
default:
|
||||||
throw new Error(`Unknown tool: ${name}`);
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
161
src/toolList.ts
161
src/toolList.ts
|
|
@ -129,13 +129,14 @@ export const toolList = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "discord_create_text_channel",
|
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: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
guildId: { type: "string" },
|
guildId: { type: "string" },
|
||||||
channelName: { 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"]
|
required: ["guildId", "channelName"]
|
||||||
}
|
}
|
||||||
|
|
@ -339,5 +340,161 @@ export const toolList = [
|
||||||
},
|
},
|
||||||
required: ["guildId"]
|
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"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ChannelType } from "discord.js";
|
import { ChannelType, PermissionsBitField } from "discord.js";
|
||||||
import { ToolContext, ToolResponse } from "./types.js";
|
import { ToolContext, ToolResponse } from "./types.js";
|
||||||
import {
|
import {
|
||||||
CreateTextChannelSchema,
|
CreateTextChannelSchema,
|
||||||
|
|
@ -7,7 +7,10 @@ import {
|
||||||
ReadMessagesSchema,
|
ReadMessagesSchema,
|
||||||
CreateCategorySchema,
|
CreateCategorySchema,
|
||||||
EditCategorySchema,
|
EditCategorySchema,
|
||||||
DeleteCategorySchema
|
DeleteCategorySchema,
|
||||||
|
SetChannelPermissionsSchema,
|
||||||
|
RemoveChannelPermissionsSchema,
|
||||||
|
CreateVoiceChannelSchema
|
||||||
} from "../schemas.js";
|
} from "../schemas.js";
|
||||||
import { handleDiscordError } from "../errorHandler.js";
|
import { handleDiscordError } from "../errorHandler.js";
|
||||||
|
|
||||||
|
|
@ -110,7 +113,7 @@ export async function createTextChannelHandler(
|
||||||
args: unknown,
|
args: unknown,
|
||||||
context: ToolContext
|
context: ToolContext
|
||||||
): Promise<ToolResponse> {
|
): Promise<ToolResponse> {
|
||||||
const { guildId, channelName, topic, reason } = CreateTextChannelSchema.parse(args);
|
const { guildId, channelName, topic, categoryId, reason } = CreateTextChannelSchema.parse(args);
|
||||||
try {
|
try {
|
||||||
if (!context.client.isReady()) {
|
if (!context.client.isReady()) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -133,6 +136,7 @@ export async function createTextChannelHandler(
|
||||||
type: ChannelType.GuildText
|
type: ChannelType.GuildText
|
||||||
};
|
};
|
||||||
if (topic) channelOptions.topic = topic;
|
if (topic) channelOptions.topic = topic;
|
||||||
|
if (categoryId) channelOptions.parent = categoryId;
|
||||||
if (reason) channelOptions.reason = reason;
|
if (reason) channelOptions.reason = reason;
|
||||||
const channel = await guild.channels.create(channelOptions);
|
const channel = await guild.channels.create(channelOptions);
|
||||||
|
|
||||||
|
|
@ -259,3 +263,136 @@ export async function readMessagesHandler(
|
||||||
return handleDiscordError(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) {
|
||||||
|
return handleDiscordError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,10 @@ import {
|
||||||
readMessagesHandler,
|
readMessagesHandler,
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
deleteCategoryHandler
|
deleteCategoryHandler,
|
||||||
|
createVoiceChannelHandler,
|
||||||
|
setChannelPermissionsHandler,
|
||||||
|
removeChannelPermissionsHandler
|
||||||
} from './channel.js';
|
} from './channel.js';
|
||||||
import {
|
import {
|
||||||
getServerInfoHandler,
|
getServerInfoHandler,
|
||||||
|
|
@ -36,6 +39,16 @@ import {
|
||||||
editWebhookHandler,
|
editWebhookHandler,
|
||||||
deleteWebhookHandler
|
deleteWebhookHandler
|
||||||
} from './webhooks.js';
|
} from './webhooks.js';
|
||||||
|
import {
|
||||||
|
listRolesHandler,
|
||||||
|
createRoleHandler,
|
||||||
|
editRoleHandler,
|
||||||
|
deleteRoleHandler,
|
||||||
|
assignRoleHandler,
|
||||||
|
removeRoleHandler,
|
||||||
|
listMembersHandler,
|
||||||
|
getMemberHandler
|
||||||
|
} from './roles.js';
|
||||||
|
|
||||||
// Export tool handlers
|
// Export tool handlers
|
||||||
export {
|
export {
|
||||||
|
|
@ -62,6 +75,17 @@ export {
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
|
createVoiceChannelHandler,
|
||||||
|
setChannelPermissionsHandler,
|
||||||
|
removeChannelPermissionsHandler,
|
||||||
|
listRolesHandler,
|
||||||
|
createRoleHandler,
|
||||||
|
editRoleHandler,
|
||||||
|
deleteRoleHandler,
|
||||||
|
assignRoleHandler,
|
||||||
|
removeRoleHandler,
|
||||||
|
listMembersHandler,
|
||||||
|
getMemberHandler,
|
||||||
listServersHandler,
|
listServersHandler,
|
||||||
searchMessagesHandler
|
searchMessagesHandler
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue