import { WordPressClient } from "@/client/api.js"; import type { MCPToolSchema } from "@/types/mcp.js"; import { CreateUserRequest, UpdateUserRequest, UserQueryParams } from "@/types/wordpress.js"; import { preserveToolError } from "@/utils/error.js"; import { WordPressDataStreamer, StreamingUtils, StreamingResult } from "@/utils/streaming.js"; import { parseId, toolParams } from "./params.js"; /** * Provides tools for managing users on a WordPress site. * This class encapsulates tool definitions and their corresponding handlers. */ export class UserTools { /** * Retrieves the list of user management tools. * @returns An array of MCPTool definitions. */ public getTools(): Array<{ name: string; description: string; inputSchema?: MCPToolSchema; handler: (client: WordPressClient, params: Record) => Promise; }> { return [ { name: "wp_list_users", description: "Lists users from a WordPress site with comprehensive filtering and detailed user information including roles, registration dates, and activity status.\n\n" + "**Note:** Role, email, and registration date fields require **administrator** privileges. " + "Non-admin users will see limited metadata due to WordPress REST API restrictions.\n\n" + "**Usage Examples:**\n" + "• List all users: `wp_list_users`\n" + '• Search users: `wp_list_users --search="john"`\n' + '• Filter by role: `wp_list_users --roles=["editor","author"]`\n' + '• Find admins: `wp_list_users --roles=["administrator"]`\n' + '• Combined search: `wp_list_users --search="smith" --roles=["subscriber"]`', inputSchema: { type: "object", properties: { search: { type: "string", description: "Limit results to those matching a search term.", }, roles: { type: "array", items: { type: "string" }, description: "Limit results to users with specific roles.", }, }, }, handler: this.handleListUsers.bind(this), }, { name: "wp_get_user", description: "Retrieves a single user by their ID.", inputSchema: { type: "object", properties: { id: { type: "number", description: "The unique identifier for the user.", }, }, required: ["id"], }, handler: this.handleGetUser.bind(this), }, { name: "wp_get_current_user", description: "Retrieves the currently authenticated user with comprehensive profile information including roles, capabilities, and account details.\n\n" + "**Usage Examples:**\n" + "• Get current user: `wp_get_current_user`\n" + "• Check permissions: Use this to verify your current user's capabilities and roles\n" + "• Account verification: Confirm you're authenticated with the correct account\n" + "• Profile details: View registration date, email, and user metadata", inputSchema: { type: "object", properties: {}, }, handler: this.handleGetCurrentUser.bind(this), }, { name: "wp_create_user", description: "Creates a new user.", inputSchema: { type: "object", properties: { username: { type: "string", description: "The username for the new user.", }, email: { type: "string", description: "The email address for the new user.", }, password: { type: "string", description: "The password for the new user.", }, roles: { type: "array", items: { type: "string" }, description: "An array of roles to assign to the user.", }, }, required: ["username", "email", "password"], }, handler: this.handleCreateUser.bind(this), }, { name: "wp_update_user", description: "Updates an existing user.", inputSchema: { type: "object", properties: { id: { type: "number", description: "The ID of the user to update.", }, email: { type: "string", description: "The new email address for the user.", }, name: { type: "string", description: "The new display name for the user.", }, }, required: ["id"], }, handler: this.handleUpdateUser.bind(this), }, { name: "wp_delete_user", description: "Deletes a user.", inputSchema: { type: "object", properties: { id: { type: "number", description: "The ID of the user to delete.", }, reassign: { type: "number", description: "The ID of a user to reassign the deleted user's content to.", }, }, required: ["id"], }, handler: this.handleDeleteUser.bind(this), }, ]; } public async handleListUsers(client: WordPressClient, params: UserQueryParams): Promise { try { const users = await client.getUsers(params); if (users.length === 0) { return "No users found matching the criteria."; } // Use streaming for large user result sets (>30 users) if (users.length > 30) { const streamResults: StreamingResult[] = []; for await (const result of WordPressDataStreamer.streamUsers(users, { includeRoles: true, includeCapabilities: false, // Too verbose for large sets batchSize: 15, })) { streamResults.push(result); } return StreamingUtils.formatStreamingResponse(streamResults, "users"); } // Enhanced user information with comprehensive details const siteUrl = client.getSiteUrl ? client.getSiteUrl() : "Unknown site"; const userCount = users.length; const rolesSummary = users.reduce( (acc, u) => { const roles = u.roles || []; roles.forEach((role) => { acc[role] = (acc[role] || 0) + 1; }); return acc; }, {} as Record, ); const metadata = [ `👥 **Users Summary**: ${userCount} total users`, `🌐 **Source**: ${siteUrl}`, // WordPress omits `roles` unless the caller may list/edit users; say so rather than print nothing. `📊 **Roles Distribution**: ${ Object.keys(rolesSummary).length > 0 ? Object.entries(rolesSummary) .map(([role, count]) => `${role}: ${count}`) .join(", ") : "Restricted (requires admin)" }`, `📅 **Retrieved**: ${new Date().toLocaleString()}`, ]; const content = metadata.join("\n") + "\n\n" + users .map((u) => { const registrationDate = u.registered_date ? new Date(u.registered_date).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }) : "Restricted (requires admin)"; const roles = u.roles?.length ? u.roles.join(", ") : "Restricted (requires admin)"; const description = u.description || "No description"; const displayName = u.name || "No display name"; const userUrl = u.url || "No URL"; return ( `- **ID ${u.id}**: ${displayName} (@${u.slug})\n` + ` 📧 Email: ${u.email || "Restricted (requires admin)"}\n` + ` 🎭 Roles: ${roles}\n` + ` 📅 Registered: ${registrationDate}\n` + ` 🔗 URL: ${userUrl}\n` + ` 📝 Description: ${description}` ); }) .join("\n\n"); return content; } catch (_error) { preserveToolError("Failed to list users", _error); } } public async handleGetUser(client: WordPressClient, params: Record): Promise { const id = parseId(params); try { const user = await client.getUser(id); const content = `**User Details (ID: ${user.id})**\n\n` + `- **Name:** ${user.name}\n` + `- **Username:** ${user.slug}\n` + `- **Email:** ${user.email}\n` + `- **Roles:** ${user.roles?.join(", ") || "N/A"}`; return content; } catch (_error) { preserveToolError("Failed to get user", _error); } } public async handleGetCurrentUser( client: WordPressClient, params: Record, ): Promise> { try { const user = await client.getCurrentUser(); const siteUrl = client.getSiteUrl(); // Extract meaningful information from capabilities const capabilities = user.capabilities || {}; const keyCapabilities = [ "edit_posts", "edit_pages", "publish_posts", "publish_pages", "delete_posts", "delete_pages", "manage_categories", "manage_options", "moderate_comments", "upload_files", "edit_others_posts", "delete_others_posts", ]; const userCapabilities = keyCapabilities.filter((cap) => capabilities[cap]).join(", "); const totalCapabilities = Object.keys(capabilities).length; // Format registration date more clearly const registrationDate = user.registered_date ? new Date(user.registered_date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }) : "Not available"; // Extract role information const roles = user.roles || []; const primaryRole = roles[0] || "No role assigned"; const allRoles = roles.length > 1 ? roles.join(", ") : primaryRole; // Build comprehensive user information const content = `**Current User Details for ${siteUrl}**\n\n` + `- **ID:** ${user.id}\n` + `- **Display Name:** ${user.name || "Not set"}\n` + `- **Username:** ${user.slug || "Not set"}\n` + `- **Email:** ${user.email || "Not set"}\n` + `- **User URL:** ${user.url || "Not set"}\n` + `- **Nickname:** ${user.nickname || user.name || "Not set"}\n` + `- **Description:** ${user.description || "No description provided"}\n` + `- **Locale:** ${user.locale || "Default"}\n` + `- **Registration Date:** ${registrationDate}\n` + `- **Primary Role:** ${primaryRole}\n` + `- **All Roles:** ${allRoles}\n` + `- **Total Capabilities:** ${totalCapabilities} capabilities\n` + `- **Key Capabilities:** ${userCapabilities || "None"}\n` + `- **Profile Link:** ${user.link || `${siteUrl}/wp-admin/profile.php`}`; return { content }; } catch (_error) { preserveToolError("Failed to get current user", _error); } } public async handleCreateUser(client: WordPressClient, params: Record): Promise { const createParams = toolParams(params); try { const user = await client.createUser(createParams); return `✅ User "${user.name}" created successfully with ID: ${user.id}.`; } catch (_error) { preserveToolError("Failed to create user", _error); } } public async handleUpdateUser(client: WordPressClient, params: Record): Promise { const updateParams = toolParams(params); try { const user = await client.updateUser(updateParams); return `✅ User ${user.id} updated successfully.`; } catch (_error) { preserveToolError("Failed to update user", _error); } } public async handleDeleteUser(client: WordPressClient, params: Record): Promise { const id = parseId(params); const { reassign } = params as { reassign?: number }; try { await client.deleteUser(id, reassign); let content = `✅ User ${id} has been deleted.`; if (reassign) { content += ` Their content has been reassigned to user ID ${reassign}.`; } return content; } catch (_error) { preserveToolError("Failed to delete user", _error); } } } export default UserTools;