import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { PermissionsBitField } from 'discord.js';
import { getDiscordClient } from '../utils/discord-client.js';
import { withErrorHandling } from '../utils/error-handler.js';
export function registerRoleTools(server: McpServer): void {
// List all roles in a server
server.tool(
'list_roles',
'List all roles in a Discord server',
{
guildId: z.string().describe('The ID of the server (guild)'),
},
async ({ guildId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const roles = await guild.roles.fetch();
return roles.map((role) => ({
id: role.id,
name: role.name,
color: role.hexColor,
position: role.position,
hoist: role.hoist,
mentionable: role.mentionable,
managed: role.managed,
permissions: role.permissions.toArray(),
memberCount: role.members.size,
})).sort((a, b) => b.position - a.position);
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Get role information
server.tool(
'get_role_info',
'Get detailed information about a specific role',
{
guildId: z.string().describe('The ID of the server (guild)'),
roleId: z.string().describe('The ID of the role'),
},
async ({ guildId, roleId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const role = await guild.roles.fetch(roleId);
if (!role) throw new Error('Role not found');
return {
id: role.id,
name: role.name,
color: role.hexColor,
position: role.position,
hoist: role.hoist,
mentionable: role.mentionable,
managed: role.managed,
permissions: role.permissions.toArray(),
createdAt: role.createdAt.toISOString(),
icon: role.iconURL(),
unicodeEmoji: role.unicodeEmoji,
members: role.members.map((m) => ({ id: m.id, username: m.user.username })),
editable: role.editable,
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Create a new role
server.tool(
'create_role',
'Create a new role in a Discord server',
{
guildId: z.string().describe('The ID of the server (guild)'),
name: z.string().describe('Name of the new role'),
color: z.string().optional().describe('Hex color code (e.g., #FF0000)'),
hoist: z.boolean().optional().describe('Whether to display the role separately'),
mentionable: z.boolean().optional().describe('Whether the role can be mentioned'),
permissions: z.array(z.string()).optional().describe('Array of permission names'),
position: z.number().optional().describe('Position of the role'),
reason: z.string().optional().describe('Reason for creating the role'),
},
async ({ guildId, name, color, hoist, mentionable, permissions, position, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const roleOptions: Record<string, unknown> = { name };
if (color !== undefined) roleOptions.color = color;
if (hoist !== undefined) roleOptions.hoist = hoist;
if (mentionable !== undefined) roleOptions.mentionable = mentionable;
if (permissions !== undefined) {
roleOptions.permissions = new PermissionsBitField(permissions as any);
}
if (position !== undefined) roleOptions.position = position;
if (reason !== undefined) roleOptions.reason = reason;
const newRole = await guild.roles.create(roleOptions);
return {
id: newRole.id,
name: newRole.name,
color: newRole.hexColor,
position: newRole.position,
message: 'Role created successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Delete a role
server.tool(
'delete_role',
'Delete a role from a Discord server',
{
guildId: z.string().describe('The ID of the server (guild)'),
roleId: z.string().describe('The ID of the role to delete'),
reason: z.string().optional().describe('Reason for deleting the role'),
},
async ({ guildId, roleId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const role = await guild.roles.fetch(roleId);
if (!role) throw new Error('Role not found');
const roleName = role.name;
await role.delete(reason);
return { roleId, roleName, message: 'Role deleted successfully' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Modify role properties
server.tool(
'modify_role',
'Modify role properties (name, color, permissions, position, etc.)',
{
guildId: z.string().describe('The ID of the server (guild)'),
roleId: z.string().describe('The ID of the role to modify'),
name: z.string().optional().describe('New role name'),
color: z.string().optional().describe('New hex color code'),
hoist: z.boolean().optional().describe('Whether to display separately'),
mentionable: z.boolean().optional().describe('Whether the role can be mentioned'),
permissions: z.array(z.string()).optional().describe('Array of permission names'),
position: z.number().optional().describe('New position'),
reason: z.string().optional().describe('Reason for the modification'),
},
async ({ guildId, roleId, name, color, hoist, mentionable, permissions, position, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const role = await guild.roles.fetch(roleId);
if (!role) throw new Error('Role not found');
const updateData: Record<string, unknown> = {};
if (name !== undefined) updateData.name = name;
if (color !== undefined) updateData.color = color;
if (hoist !== undefined) updateData.hoist = hoist;
if (mentionable !== undefined) updateData.mentionable = mentionable;
if (permissions !== undefined) {
updateData.permissions = new PermissionsBitField(permissions as any);
}
if (position !== undefined) updateData.position = position;
if (reason !== undefined) updateData.reason = reason;
const updatedRole = await role.edit(updateData);
return {
id: updatedRole.id,
name: updatedRole.name,
color: updatedRole.hexColor,
position: updatedRole.position,
message: 'Role updated successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Assign role to member
server.tool(
'assign_role',
'Assign a role to a member',
{
guildId: z.string().describe('The ID of the server (guild)'),
memberId: z.string().describe('The ID of the member'),
roleId: z.string().describe('The ID of the role to assign'),
reason: z.string().optional().describe('Reason for assigning the role'),
},
async ({ guildId, memberId, roleId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const member = await guild.members.fetch(memberId);
const role = await guild.roles.fetch(roleId);
if (!role) throw new Error('Role not found');
await member.roles.add(role, reason);
return {
memberId,
roleId,
roleName: role.name,
memberName: member.displayName,
message: 'Role assigned successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Remove role from member
server.tool(
'remove_role',
'Remove a role from a member',
{
guildId: z.string().describe('The ID of the server (guild)'),
memberId: z.string().describe('The ID of the member'),
roleId: z.string().describe('The ID of the role to remove'),
reason: z.string().optional().describe('Reason for removing the role'),
},
async ({ guildId, memberId, roleId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const member = await guild.members.fetch(memberId);
const role = await guild.roles.fetch(roleId);
if (!role) throw new Error('Role not found');
await member.roles.remove(role, reason);
return {
memberId,
roleId,
roleName: role.name,
memberName: member.displayName,
message: 'Role removed successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
}