import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { getDiscordClient } from '../utils/discord-client.js';
import { withErrorHandling } from '../utils/error-handler.js';
export function registerMemberTools(server: McpServer): void {
// List members in a server
server.tool(
'list_members',
'List members in a Discord server (fetches up to 1000 members)',
{
guildId: z.string().describe('The ID of the server (guild)'),
limit: z.number().optional().describe('Maximum number of members to fetch (default: 100, max: 1000)'),
},
async ({ guildId, limit = 100 }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const members = await guild.members.fetch({ limit: Math.min(limit, 1000) });
return members.map((member) => ({
id: member.id,
username: member.user.username,
displayName: member.displayName,
nickname: member.nickname,
joinedAt: member.joinedAt?.toISOString(),
roles: member.roles.cache.map((r) => ({ id: r.id, name: r.name })),
isBot: member.user.bot,
avatar: member.displayAvatarURL(),
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Get member information
server.tool(
'get_member_info',
'Get detailed information about a specific member',
{
guildId: z.string().describe('The ID of the server (guild)'),
memberId: z.string().describe('The ID of the member (user ID)'),
},
async ({ guildId, memberId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const member = await guild.members.fetch(memberId);
return {
id: member.id,
username: member.user.username,
displayName: member.displayName,
nickname: member.nickname,
discriminator: member.user.discriminator,
joinedAt: member.joinedAt?.toISOString(),
premiumSince: member.premiumSince?.toISOString(),
communicationDisabledUntil: member.communicationDisabledUntil?.toISOString(),
pending: member.pending,
isBot: member.user.bot,
avatar: member.displayAvatarURL(),
banner: member.user.bannerURL(),
roles: member.roles.cache.map((r) => ({
id: r.id,
name: r.name,
color: r.hexColor,
position: r.position,
})),
permissions: member.permissions.toArray(),
highestRole: {
id: member.roles.highest.id,
name: member.roles.highest.name,
},
isOwner: member.id === guild.ownerId,
manageable: member.manageable,
kickable: member.kickable,
bannable: member.bannable,
moderatable: member.moderatable,
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Modify member properties
server.tool(
'modify_member',
'Modify member properties (nickname, roles, timeout, etc.)',
{
guildId: z.string().describe('The ID of the server (guild)'),
memberId: z.string().describe('The ID of the member'),
nickname: z.string().optional().describe('New nickname (null to remove)'),
roles: z.array(z.string()).optional().describe('Array of role IDs to set'),
mute: z.boolean().optional().describe('Whether to mute in voice'),
deaf: z.boolean().optional().describe('Whether to deafen in voice'),
channelId: z.string().optional().describe('Voice channel ID to move to'),
communicationDisabledUntil: z.string().optional().describe('ISO timestamp for timeout end'),
reason: z.string().optional().describe('Reason for the modification'),
},
async ({ guildId, memberId, nickname, roles, mute, deaf, channelId, communicationDisabledUntil, 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 updateData: Record<string, unknown> = {};
if (nickname !== undefined) updateData.nick = nickname;
if (roles !== undefined) updateData.roles = roles;
if (mute !== undefined) updateData.mute = mute;
if (deaf !== undefined) updateData.deaf = deaf;
if (channelId !== undefined) updateData.channel = channelId;
if (communicationDisabledUntil !== undefined) {
updateData.communicationDisabledUntil = communicationDisabledUntil ? new Date(communicationDisabledUntil) : null;
}
if (reason !== undefined) updateData.reason = reason;
const updatedMember = await member.edit(updateData);
return {
id: updatedMember.id,
displayName: updatedMember.displayName,
nickname: updatedMember.nickname,
message: 'Member 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) }] };
}
);
// Kick a member
server.tool(
'kick_member',
'Kick a member from the server',
{
guildId: z.string().describe('The ID of the server (guild)'),
memberId: z.string().describe('The ID of the member to kick'),
reason: z.string().optional().describe('Reason for the kick'),
},
async ({ guildId, memberId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const member = await guild.members.fetch(memberId);
if (!member.kickable) {
throw new Error('Cannot kick this member: insufficient permissions or role hierarchy');
}
const username = member.user.username;
await member.kick(reason);
return { memberId, username, message: 'Member kicked successfully' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Ban a member
server.tool(
'ban_member',
'Ban a member from the server',
{
guildId: z.string().describe('The ID of the server (guild)'),
userId: z.string().describe('The ID of the user to ban'),
deleteMessageSeconds: z.number().optional().describe('Number of seconds of messages to delete (0-604800)'),
reason: z.string().optional().describe('Reason for the ban'),
},
async ({ guildId, userId, deleteMessageSeconds, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const banOptions: { deleteMessageSeconds?: number; reason?: string } = {};
if (deleteMessageSeconds !== undefined) {
banOptions.deleteMessageSeconds = Math.min(Math.max(0, deleteMessageSeconds), 604800);
}
if (reason) banOptions.reason = reason;
await guild.members.ban(userId, banOptions);
return { userId, message: 'User banned successfully' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Unban a user
server.tool(
'unban_member',
'Unban a user from the server',
{
guildId: z.string().describe('The ID of the server (guild)'),
userId: z.string().describe('The ID of the user to unban'),
reason: z.string().optional().describe('Reason for the unban'),
},
async ({ guildId, userId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
await guild.members.unban(userId, reason);
return { userId, message: 'User unbanned successfully' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// List bans
server.tool(
'list_bans',
'List all banned users in a 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 bans = await guild.bans.fetch();
return bans.map((ban) => ({
userId: ban.user.id,
username: ban.user.username,
reason: ban.reason,
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
}