import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import type { BetterNewsClient } from "../client.js"; import { handleTool } from "../utils.js"; export function registerModerationTools(server: McpServer, client: BetterNewsClient) { server.tool( "get_mod_queue", "Get the moderation queue of pending news items and source submissions. Requires Mod/Admin role and mod:queue scope.", { page: z.number().int().positive().optional().describe("Page number (default 1)"), limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20)"), }, handleTool(async (args) => { const data = await client.get("/api/mod/queue", args); return JSON.stringify(data, null, 2); }), ); server.tool( "get_mod_stats", "Get aggregate moderation statistics. Requires Mod/Admin role and mod:stats scope.", {}, handleTool(async () => { const data = await client.get("/api/mod/stats"); return JSON.stringify(data, null, 2); }), ); server.tool( "list_mod_users", "List all users with moderation details. Requires Admin role and mod:users scope.", { page: z.number().int().positive().optional().describe("Page number (default 1)"), limit: z.number().int().min(1).max(100).optional().describe("Items per page (default 20)"), search: z.string().optional().describe("Search by username or email"), role: z.enum(["User", "Moderator", "Admin"]).optional().describe("Filter by role"), banned: z.boolean().optional().describe("Filter by ban status"), }, handleTool(async (args) => { const data = await client.get("/api/mod/users", args); return JSON.stringify(data, null, 2); }), ); server.tool( "get_mod_user", "Get detailed moderation view of a specific user. Requires Admin role and mod:users scope.", { userId: z.string().uuid().describe("User UUID"), }, handleTool(async ({ userId }) => { const data = await client.get(`/api/mod/users/${userId}`); return JSON.stringify(data, null, 2); }), ); server.tool( "change_user_role", "Change a user's role. Requires Admin role and mod:users scope.", { userId: z.string().uuid().describe("User UUID"), role: z.enum(["User", "Moderator", "Admin"]).describe("New role"), }, handleTool(async ({ userId, role }) => { const data = await client.patch(`/api/mod/users/${userId}/role`, { role }); return JSON.stringify(data, null, 2); }), ); server.tool( "ban_user", "Ban a user. Requires Admin role and mod:users scope.", { userId: z.string().uuid().describe("User UUID"), reason: z.string().max(500).optional().describe("Ban reason"), bannedUntil: z.string().datetime().optional().describe("Temporary ban expiry (ISO 8601). Omit for permanent."), permanent: z.boolean().optional().describe("Whether the ban is permanent"), }, handleTool(async ({ userId, ...body }) => { const data = await client.post(`/api/mod/users/${userId}/ban`, body); return JSON.stringify(data, null, 2); }), ); server.tool( "unban_user", "Lift a ban from a user. Requires Admin role and mod:users scope.", { userId: z.string().uuid().describe("User UUID"), }, handleTool(async ({ userId }) => { const data = await client.delete(`/api/mod/users/${userId}/ban`); return JSON.stringify(data, null, 2); }), ); server.tool( "shadowban_user", "Toggle shadowban on a user. Shadowbanned users can post but their content is invisible to others. Requires Admin role and mod:users scope.", { userId: z.string().uuid().describe("User UUID"), shadowbanned: z.boolean().describe("True to shadowban, false to remove shadowban"), }, handleTool(async ({ userId, shadowbanned }) => { const data = await client.patch(`/api/mod/users/${userId}/shadowban`, { shadowbanned }); return JSON.stringify(data, null, 2); }), ); server.tool( "create_mod_user", "Create a new user account directly (bypasses email verification). Requires Admin role and mod:users scope.", { email: z.string().email().describe("User email address"), username: z.string().min(3).max(30).describe("Username"), displayName: z.string().min(1).max(25).describe("Display name"), password: z.string().min(8).describe("Initial password"), role: z.enum(["User", "Moderator", "Admin"]).optional().describe("Role (default: User)"), }, handleTool(async (body) => { const data = await client.post("/api/mod/users", body); return JSON.stringify(data, null, 2); }), ); server.tool( "list_all_tickets", "List all support tickets (mod view). Requires Mod/Admin role and mod:queue scope.", { page: z.number().int().positive().optional().describe("Page number (default 1)"), limit: z.number().int().min(1).max(100).optional().describe("Items per page (default 20)"), status: z.enum(["open", "in_progress", "resolved", "closed"]).optional().describe("Filter by status"), }, handleTool(async (args) => { const data = await client.get("/api/mod/tickets", args); return JSON.stringify(data, null, 2); }), ); server.tool( "list_mod_comments", "List all comments across the platform with moderation details (like/dislike counts, hidden status). Requires Mod/Admin role and mod:queue scope.", { page: z.number().int().positive().optional().describe("Page number (default 1)"), limit: z.number().int().min(1).max(50).optional().describe("Items per page (default 20, max 50)"), hidden: z.boolean().optional().describe("Filter by hidden status. Omit to see all."), sort: z .enum(["top", "most_disliked", "new", "oldest"]) .optional() .describe("Sort order: top = most liked, most_disliked, new (default), oldest"), }, handleTool(async (args) => { const data = await client.get("/api/mod/comments", args); return JSON.stringify(data, null, 2); }), ); }