Skip to main content
Glama
unified-taxonomies.ts18 kB
// src/tools/unified-taxonomies.ts import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { makeWordPressRequest, logToFile } from '../wordpress.js'; import { z } from 'zod'; // Cache for taxonomies to reduce API calls let taxonomiesCache: any = null; let taxonomyCacheTimestamp: number = 0; const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes // Helper function to get all taxonomies with caching async function getTaxonomies(forceRefresh = false) { const now = Date.now(); if (!forceRefresh && taxonomiesCache && (now - taxonomyCacheTimestamp) < CACHE_DURATION) { logToFile('Using cached taxonomies'); return taxonomiesCache; } try { logToFile('Fetching taxonomies from API'); const response = await makeWordPressRequest('GET', 'wp/v2/taxonomies'); taxonomiesCache = response; taxonomyCacheTimestamp = now; return response; } catch (error: any) { logToFile(`Error fetching taxonomies: ${error.message}`); throw error; } } // Helper function to get the correct endpoint for a taxonomy function getTaxonomyEndpoint(taxonomy: string): string { const endpointMap: Record<string, string> = { 'category': 'wp/v2/categories', 'post_tag': 'wp/v2/tags', 'nav_menu': 'wp/v2/menus', 'link_category': 'wp/v2/link_categories' }; return endpointMap[taxonomy] || `wp/v2/${taxonomy}`; } // Helper function to get the correct content endpoint function getContentEndpoint(contentType: string): string { const endpointMap: Record<string, string> = { 'post': 'wp/v2/posts', 'page': 'wp/v2/pages' }; return endpointMap[contentType] || `wp/v2/${contentType}`; } // Schema definitions const discoverTaxonomiesSchema = z.object({ content_type: z.string().optional().describe("Limit results to taxonomies associated with a specific content type"), refresh_cache: z.boolean().optional().describe("Force refresh the taxonomies cache") }); const listTermsSchema = z.object({ taxonomy: z.string().describe("The taxonomy slug (e.g., 'category', 'post_tag', or custom taxonomies)"), page: z.number().optional().describe("Page number (default 1)"), per_page: z.number().min(1).max(100).optional().describe("Items per page (default 10, max 100)"), search: z.string().optional().describe("Search term for term name"), parent: z.number().optional().describe("Parent term ID to retrieve direct children"), slug: z.string().optional().describe("Limit result to terms with a specific slug"), hide_empty: z.boolean().optional().describe("Whether to hide terms not assigned to any content"), orderby: z.enum(['id', 'include', 'name', 'slug', 'term_group', 'description', 'count']).optional().describe("Sort terms by parameter"), order: z.enum(['asc', 'desc']).optional().describe("Order sort attribute") }); const getTermSchema = z.object({ taxonomy: z.string().describe("The taxonomy slug"), id: z.number().describe("Term ID") }); const createTermSchema = z.object({ taxonomy: z.string().describe("The taxonomy slug"), name: z.string().describe("Term name"), slug: z.string().optional().describe("Term slug"), parent: z.number().optional().describe("Parent term ID"), description: z.string().optional().describe("Term description"), meta: z.record(z.any()).optional().describe("Term meta fields") }); const updateTermSchema = z.object({ taxonomy: z.string().describe("The taxonomy slug"), id: z.number().describe("Term ID"), name: z.string().optional().describe("Term name"), slug: z.string().optional().describe("Term slug"), parent: z.number().optional().describe("Parent term ID"), description: z.string().optional().describe("Term description"), meta: z.record(z.any()).optional().describe("Term meta fields") }); const deleteTermSchema = z.object({ taxonomy: z.string().describe("The taxonomy slug"), id: z.number().describe("Term ID"), force: z.boolean().optional().describe("Required to be true, as terms do not support trashing") }); const assignTermsToContentSchema = z.object({ content_id: z.number().describe("The content ID"), content_type: z.string().describe("The content type slug"), taxonomy: z.string().describe("The taxonomy slug"), terms: z.array(z.union([z.number(), z.string()])).describe("Array of term IDs or slugs to assign"), append: z.boolean().optional().describe("If true, append terms to existing ones. If false, replace all terms") }); const getContentTermsSchema = z.object({ content_id: z.number().describe("The content ID"), content_type: z.string().describe("The content type slug"), taxonomy: z.string().optional().describe("Specific taxonomy to retrieve terms from (if not specified, returns all)") }); // Type definitions type DiscoverTaxonomiesParams = z.infer<typeof discoverTaxonomiesSchema>; type ListTermsParams = z.infer<typeof listTermsSchema>; type GetTermParams = z.infer<typeof getTermSchema>; type CreateTermParams = z.infer<typeof createTermSchema>; type UpdateTermParams = z.infer<typeof updateTermSchema>; type DeleteTermParams = z.infer<typeof deleteTermSchema>; type AssignTermsToContentParams = z.infer<typeof assignTermsToContentSchema>; type GetContentTermsParams = z.infer<typeof getContentTermsSchema>; export const unifiedTaxonomyTools: Tool[] = [ { name: "discover_taxonomies", description: "Discovers all available taxonomies (built-in and custom) in the WordPress site", inputSchema: { type: "object", properties: discoverTaxonomiesSchema.shape } }, { name: "list_terms", description: "Lists terms in any taxonomy (categories, tags, or custom taxonomies) with filtering and pagination", inputSchema: { type: "object", properties: listTermsSchema.shape } }, { name: "get_term", description: "Gets a specific term by ID from any taxonomy", inputSchema: { type: "object", properties: getTermSchema.shape } }, { name: "create_term", description: "Creates a new term in any taxonomy", inputSchema: { type: "object", properties: createTermSchema.shape } }, { name: "update_term", description: "Updates an existing term in any taxonomy", inputSchema: { type: "object", properties: updateTermSchema.shape } }, { name: "delete_term", description: "Deletes a term from any taxonomy", inputSchema: { type: "object", properties: deleteTermSchema.shape } }, { name: "assign_terms_to_content", description: "Assigns taxonomy terms to content of any type", inputSchema: { type: "object", properties: assignTermsToContentSchema.shape } }, { name: "get_content_terms", description: "Gets all taxonomy terms assigned to content of any type", inputSchema: { type: "object", properties: getContentTermsSchema.shape } } ]; export const unifiedTaxonomyHandlers = { discover_taxonomies: async (params: DiscoverTaxonomiesParams) => { try { const taxonomies = await getTaxonomies(params.refresh_cache || false); // Filter by content type if specified let filteredTaxonomies = taxonomies; if (params.content_type) { filteredTaxonomies = Object.fromEntries( Object.entries(taxonomies).filter(([_, tax]: [string, any]) => tax.types && tax.types.includes(params.content_type) ) ); } // Format the response to be more readable const formattedTaxonomies = Object.entries(filteredTaxonomies).map(([slug, tax]: [string, any]) => ({ slug, name: tax.name, description: tax.description, types: tax.types, hierarchical: tax.hierarchical, rest_base: tax.rest_base, labels: tax.labels })); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(formattedTaxonomies, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error discovering taxonomies: ${error.message}` }], isError: true } }; } }, list_terms: async (params: ListTermsParams) => { try { const endpoint = getTaxonomyEndpoint(params.taxonomy); const { taxonomy, ...queryParams } = params; const response = await makeWordPressRequest('GET', endpoint, queryParams); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error listing terms: ${error.message}` }], isError: true } }; } }, get_term: async (params: GetTermParams) => { try { const endpoint = getTaxonomyEndpoint(params.taxonomy); const response = await makeWordPressRequest('GET', `${endpoint}/${params.id}`); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error getting term: ${error.message}` }], isError: true } }; } }, create_term: async (params: CreateTermParams) => { try { const endpoint = getTaxonomyEndpoint(params.taxonomy); const termData: any = { name: params.name }; if (params.slug !== undefined) termData.slug = params.slug; if (params.parent !== undefined) termData.parent = params.parent; if (params.description !== undefined) termData.description = params.description; if (params.meta !== undefined) termData.meta = params.meta; const response = await makeWordPressRequest('POST', endpoint, termData); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error creating term: ${error.message}` }], isError: true } }; } }, update_term: async (params: UpdateTermParams) => { try { const endpoint = getTaxonomyEndpoint(params.taxonomy); const updateData: any = {}; if (params.name !== undefined) updateData.name = params.name; if (params.slug !== undefined) updateData.slug = params.slug; if (params.parent !== undefined) updateData.parent = params.parent; if (params.description !== undefined) updateData.description = params.description; if (params.meta !== undefined) updateData.meta = params.meta; const response = await makeWordPressRequest('POST', `${endpoint}/${params.id}`, updateData); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error updating term: ${error.message}` }], isError: true } }; } }, delete_term: async (params: DeleteTermParams) => { try { const endpoint = getTaxonomyEndpoint(params.taxonomy); const response = await makeWordPressRequest('DELETE', `${endpoint}/${params.id}`, { force: true // Terms require force to be true }); return { toolResult: { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error deleting term: ${error.message}` }], isError: true } }; } }, assign_terms_to_content: async (params: AssignTermsToContentParams) => { try { // Determine the content endpoint const contentEndpoint = getContentEndpoint(params.content_type); // Prepare the update data const updateData: any = {}; // The field name depends on the taxonomy if (params.taxonomy === 'category') { updateData.categories = params.terms; } else if (params.taxonomy === 'post_tag') { updateData.tags = params.terms; } else { // For custom taxonomies, use the taxonomy slug as the field name updateData[params.taxonomy] = params.terms; } // If appending, we need to get current terms first if (params.append) { try { const currentContent = await makeWordPressRequest('GET', `${contentEndpoint}/${params.content_id}`); const currentTerms = currentContent[params.taxonomy === 'category' ? 'categories' : params.taxonomy === 'post_tag' ? 'tags' : params.taxonomy] || []; // Merge current terms with new terms (remove duplicates) const allTerms = [...new Set([...currentTerms, ...params.terms])]; updateData[params.taxonomy === 'category' ? 'categories' : params.taxonomy === 'post_tag' ? 'tags' : params.taxonomy] = allTerms; } catch (error) { // If we can't get current terms, just set the new ones logToFile(`Warning: Could not get current terms for append operation: ${error}`); } } const response = await makeWordPressRequest('POST', `${contentEndpoint}/${params.content_id}`, updateData); return { toolResult: { content: [{ type: 'text', text: JSON.stringify({ success: true, content_id: params.content_id, content_type: params.content_type, taxonomy: params.taxonomy, assigned_terms: params.terms, appended: params.append || false, content: response }, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error assigning terms to content: ${error.message}` }], isError: true } }; } }, get_content_terms: async (params: GetContentTermsParams) => { try { // First, get the content to see what taxonomies are assigned const contentEndpoint = getContentEndpoint(params.content_type); const content = await makeWordPressRequest('GET', `${contentEndpoint}/${params.content_id}`); // Get all available taxonomies const taxonomies = await getTaxonomies(); const terms: any = {}; // If specific taxonomy requested if (params.taxonomy) { const taxonomyField = params.taxonomy === 'category' ? 'categories' : params.taxonomy === 'post_tag' ? 'tags' : params.taxonomy; if (content[taxonomyField]) { // Get full term details const endpoint = getTaxonomyEndpoint(params.taxonomy); const termDetails = await Promise.all( content[taxonomyField].map(async (termId: number) => { try { return await makeWordPressRequest('GET', `${endpoint}/${termId}`); } catch { return { id: termId, error: 'Could not fetch term details' }; } }) ); terms[params.taxonomy] = termDetails; } } else { // Get all taxonomy terms for this content for (const [taxonomySlug, taxonomyInfo] of Object.entries(taxonomies)) { const tax = taxonomyInfo as any; // Check if this taxonomy applies to this content type if (tax.types && tax.types.includes(params.content_type)) { const taxonomyField = taxonomySlug === 'category' ? 'categories' : taxonomySlug === 'post_tag' ? 'tags' : taxonomySlug; if (content[taxonomyField] && Array.isArray(content[taxonomyField]) && content[taxonomyField].length > 0) { const endpoint = getTaxonomyEndpoint(taxonomySlug); const termDetails = await Promise.all( content[taxonomyField].map(async (termId: number) => { try { return await makeWordPressRequest('GET', `${endpoint}/${termId}`); } catch { return { id: termId, error: 'Could not fetch term details' }; } }) ); terms[taxonomySlug] = termDetails; } } } } return { toolResult: { content: [{ type: 'text', text: JSON.stringify({ content_id: params.content_id, content_type: params.content_type, terms: terms }, null, 2) }], isError: false } }; } catch (error: any) { return { toolResult: { content: [{ type: 'text', text: `Error getting content terms: ${error.message}` }], isError: true } }; } } };

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/wplaunchify/fluent-community-mcp'

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