159 lines
6.7 KiB
TypeScript
159 lines
6.7 KiB
TypeScript
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<unknown>("/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<unknown>("/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<unknown>("/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<unknown>(`/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<unknown>(`/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<unknown>(`/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<unknown>(`/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<unknown>(`/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<unknown>("/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<unknown>("/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<unknown>("/api/mod/comments", args);
|
|
return JSON.stringify(data, null, 2);
|
|
}),
|
|
);
|
|
}
|