Skip to main content
Glama
index.ts20.9 kB
/** * Taxonomy Tools Registration * * Handles: Categories, Tags, Taxonomies (CRUD operations) * * @package WP_Navigator_Pro * @since 1.3.0 */ import { toolRegistry, ToolCategory } from '../../tool-registry/index.js'; import { validateRequired, validatePagination, validateId, buildQueryString } from '../../tool-registry/utils.js'; /** * Register taxonomy management tools (categories, tags, taxonomies) */ export function registerTaxonomyTools() { // ============================================================================ // CATEGORIES // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_list_categories', description: 'List all WordPress categories with optional filtering. Returns category ID, name, slug, count, and parent.', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination (default: 1)' }, per_page: { type: 'number', description: 'Number of categories to return (default: 10, max: 100)' }, search: { type: 'string', description: 'Search term to filter categories by name' }, parent: { type: 'number', description: 'Filter by parent category ID' }, }, required: [], }, }, handler: async (args, context) => { const { page, per_page } = validatePagination(args); const qs = buildQueryString({ page, per_page, search: args.search, parent: args.parent }); const categories = await context.wpRequest(`/wp/v2/categories?${qs}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(categories, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_get_category', description: 'Get a single WordPress category by ID. Returns full category details including description and post count.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress category ID' }, }, required: ['id'], }, }, handler: async (args, context) => { validateRequired(args, ['id']); const id = validateId(args.id, 'Category'); const category = await context.wpRequest(`/wp/v2/categories/${id}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(category, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_create_category', description: 'Create a new WordPress category. Requires name. Changes are logged in audit trail.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Category name' }, description: { type: 'string', description: 'Category description (optional)' }, slug: { type: 'string', description: 'Category slug (optional, auto-generated from name if not provided)' }, parent: { type: 'number', description: 'Parent category ID (optional, for hierarchical categories)' }, }, required: ['name'], }, }, handler: async (args, context) => { try { validateRequired(args, ['name']); const createData: any = { name: args.name }; if (args.description) createData.description = args.description; if (args.slug) createData.slug = args.slug; if (args.parent) createData.parent = args.parent; const result = await context.wpRequest('/wp/v2/categories', { method: 'POST', body: JSON.stringify(createData), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, name: result.name, slug: result.slug, link: result.link, message: 'Category created successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'CREATE_FAILED', message: errorMessage, context: { resource_type: 'category', name: args.name, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check category name is unique', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_update_category', description: 'Update a WordPress category. Requires category ID and at least one field to update. Changes are logged in audit trail.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress category ID' }, name: { type: 'string', description: 'New category name' }, description: { type: 'string', description: 'New category description' }, slug: { type: 'string', description: 'New category slug' }, parent: { type: 'number', description: 'New parent category ID' }, }, required: ['id'], }, }, handler: async (args, context) => { try { validateRequired(args, ['id']); const id = validateId(args.id, 'Category'); const updateData: any = {}; if (args.name) updateData.name = args.name; if (args.description) updateData.description = args.description; if (args.slug) updateData.slug = args.slug; if (args.parent !== undefined) updateData.parent = args.parent; if (Object.keys(updateData).length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'validation_failed', code: 'VALIDATION_FAILED', message: 'At least one field (name, description, slug, or parent) must be provided', context: { resource_type: 'category', resource_id: args.id }, }, null, 2), }], isError: true, }; } const result = await context.wpRequest(`/wp/v2/categories/${id}`, { method: 'POST', body: JSON.stringify(updateData), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, name: result.name, slug: result.slug, message: 'Category updated successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'UPDATE_FAILED', message: errorMessage, context: { resource_type: 'category', resource_id: args.id, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check category ID exists with wpnav_get_category', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_delete_category', description: 'Delete a WordPress category by ID. Posts in this category will be reassigned to Uncategorized. WARNING: This action cannot be undone.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress category ID' }, force: { type: 'boolean', description: 'Force permanent deletion. Default: true', default: true }, }, required: ['id'], }, }, handler: async (args, context) => { try { validateRequired(args, ['id']); const id = validateId(args.id, 'Category'); const params = new URLSearchParams({ force: String(args.force !== false) }); const result = await context.wpRequest(`/wp/v2/categories/${id}?${params.toString()}`, { method: 'DELETE', }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, message: 'Category deleted successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'DELETE_FAILED', message: errorMessage, context: { resource_type: 'category', resource_id: args.id, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check category ID exists with wpnav_get_category', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); // ============================================================================ // TAGS // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_list_tags', description: 'List all WordPress tags with optional filtering. Returns tag ID, name, slug, and count.', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination (default: 1)' }, per_page: { type: 'number', description: 'Number of tags to return (default: 10, max: 100)' }, search: { type: 'string', description: 'Search term to filter tags by name' }, }, required: [], }, }, handler: async (args, context) => { const { page, per_page } = validatePagination(args); const qs = buildQueryString({ page, per_page, search: args.search }); const tags = await context.wpRequest(`/wp/v2/tags?${qs}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(tags, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_get_tag', description: 'Get a single WordPress tag by ID. Returns full tag details including description and post count.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress tag ID' }, }, required: ['id'], }, }, handler: async (args, context) => { validateRequired(args, ['id']); const id = validateId(args.id, 'Tag'); const tag = await context.wpRequest(`/wp/v2/tags/${id}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(tag, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_create_tag', description: 'Create a new WordPress tag. Requires name. Changes are logged in audit trail.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Tag name' }, description: { type: 'string', description: 'Tag description (optional)' }, slug: { type: 'string', description: 'Tag slug (optional, auto-generated from name if not provided)' }, }, required: ['name'], }, }, handler: async (args, context) => { try { validateRequired(args, ['name']); const createData: any = { name: args.name }; if (args.description) createData.description = args.description; if (args.slug) createData.slug = args.slug; const result = await context.wpRequest('/wp/v2/tags', { method: 'POST', body: JSON.stringify(createData), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, name: result.name, slug: result.slug, link: result.link, message: 'Tag created successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'CREATE_FAILED', message: errorMessage, context: { resource_type: 'tag', name: args.name, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check tag name is unique', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_update_tag', description: 'Update a WordPress tag. Requires tag ID and at least one field to update. Changes are logged in audit trail.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress tag ID' }, name: { type: 'string', description: 'New tag name' }, description: { type: 'string', description: 'New tag description' }, slug: { type: 'string', description: 'New tag slug' }, }, required: ['id'], }, }, handler: async (args, context) => { try { validateRequired(args, ['id']); const id = validateId(args.id, 'Tag'); const updateData: any = {}; if (args.name) updateData.name = args.name; if (args.description) updateData.description = args.description; if (args.slug) updateData.slug = args.slug; if (Object.keys(updateData).length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'validation_failed', code: 'VALIDATION_FAILED', message: 'At least one field (name, description, or slug) must be provided', context: { resource_type: 'tag', resource_id: args.id }, }, null, 2), }], isError: true, }; } const result = await context.wpRequest(`/wp/v2/tags/${id}`, { method: 'POST', body: JSON.stringify(updateData), }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, name: result.name, slug: result.slug, message: 'Tag updated successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'UPDATE_FAILED', message: errorMessage, context: { resource_type: 'tag', resource_id: args.id, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check tag ID exists with wpnav_get_tag', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_delete_tag', description: 'Delete a WordPress tag by ID. Posts with this tag will have it removed. WARNING: This action cannot be undone.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'WordPress tag ID' }, force: { type: 'boolean', description: 'Force permanent deletion. Default: true', default: true }, }, required: ['id'], }, }, handler: async (args, context) => { try { validateRequired(args, ['id']); const id = validateId(args.id, 'Tag'); const params = new URLSearchParams({ force: String(args.force !== false) }); const result = await context.wpRequest(`/wp/v2/tags/${id}?${params.toString()}`, { method: 'DELETE', }); return { content: [{ type: 'text', text: context.clampText(JSON.stringify({ id: result.id, message: 'Tag deleted successfully', }, null, 2)), }], }; } catch (error: any) { const errorMessage = error.message || 'Unknown error'; const isWritesDisabled = errorMessage.includes('WRITES_DISABLED'); return { content: [{ type: 'text', text: JSON.stringify({ error: isWritesDisabled ? 'writes_disabled' : 'operation_failed', code: isWritesDisabled ? 'WRITES_DISABLED' : 'DELETE_FAILED', message: errorMessage, context: { resource_type: 'tag', resource_id: args.id, suggestion: isWritesDisabled ? 'Set WPNAV_ENABLE_WRITES=1 in MCP server config (.mcp.json env section)' : 'Check tag ID exists with wpnav_get_tag', }, }, null, 2), }], isError: true, }; } }, category: ToolCategory.TAXONOMY, }); // ============================================================================ // TAXONOMIES (Read-Only Discovery) // ============================================================================ toolRegistry.register({ definition: { name: 'wpnav_list_taxonomies', description: 'List all registered WordPress taxonomies (categories, tags, custom). Returns taxonomy name, labels, and capabilities. Always available for site structure discovery.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Filter by post type (e.g., "post", "page")' }, }, required: [], }, }, handler: async (args, context) => { const qs = buildQueryString({ type: args.type }); const taxonomies = await context.wpRequest(`/wp/v2/taxonomies?${qs}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(taxonomies, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); toolRegistry.register({ definition: { name: 'wpnav_get_taxonomy', description: 'Get details about a specific taxonomy by name. Returns full taxonomy configuration including hierarchical status, REST base, and labels. Always available for site structure discovery.', inputSchema: { type: 'object', properties: { taxonomy: { type: 'string', description: 'Taxonomy name (e.g., "category", "post_tag", or custom taxonomy)' }, }, required: ['taxonomy'], }, }, handler: async (args, context) => { validateRequired(args, ['taxonomy']); const taxonomy = await context.wpRequest(`/wp/v2/taxonomies/${args.taxonomy}`); return { content: [{ type: 'text', text: context.clampText(JSON.stringify(taxonomy, null, 2)) }], }; }, category: ToolCategory.TAXONOMY, }); }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server