UNPKG

@myea/wordpress-mcp-handler

Version:

Advanced WordPress MCP request handler with intelligent search, multi-locale support, and comprehensive content management capabilities

354 lines 18.5 kB
// Enhanced search utilities for WordPress MCP integration class WordPressSearchUtils { /** * Generate variations of search terms for better matching */ static generateSearchVariations(term) { const variations = new Set(); const cleanTerm = term.trim().toLowerCase(); variations.add(cleanTerm); variations.add(cleanTerm.replace(/\s+/g, '-')); variations.add(cleanTerm.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()); variations.add(cleanTerm.replace(/[-_]/g, ' ')); variations.add(cleanTerm.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase()); variations.add(cleanTerm.replace(/[-\s_]/g, '')); variations.add(cleanTerm.replace(/[-\s]/g, '_')); return Array.from(variations); } /** * Calculate similarity between strings for fuzzy matching */ static calculateSimilarity(str1, str2) { const s1 = str1.toLowerCase(); const s2 = str2.toLowerCase(); if (s1 === s2) return 1.0; if (s1.includes(s2) || s2.includes(s1)) return 0.8; const longer = s1.length > s2.length ? s1 : s2; const shorter = s1.length > s2.length ? s2 : s1; if (longer.length === 0) return 1.0; const distance = this.levenshteinDistance(longer, shorter); return (longer.length - distance) / longer.length; } static levenshteinDistance(str1, str2) { const matrix = []; for (let i = 0; i <= str2.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= str1.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= str2.length; i++) { for (let j = 1; j <= str1.length; j++) { if (str2.charAt(i - 1) === str1.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1); } } } return matrix[str2.length][str1.length]; } } class WordPressSearchFeedbackLoop { wpConnector; constructor(wpConnector) { this.wpConnector = wpConnector; } async comprehensiveSearch(searchTerm, options = {}) { const report = { strategiesUsed: [], totalAttempts: 0, confidence: 0, coverage: 'partial', }; let allResults = []; // Strategy 1: Direct WordPress search try { report.strategiesUsed.push('WordPress REST API Search'); report.totalAttempts++; const searchResponse = await this.wpConnector.searchContent(searchTerm); if (searchResponse.success && searchResponse.data.results) { allResults = allResults.concat(searchResponse.data.results); } } catch (error) { // WordPress search failed, continue with other strategies } // Strategy 2: Enhanced multi-content search try { report.strategiesUsed.push('Enhanced Multi-Content Search'); report.totalAttempts++; const enhancedResponse = await this.wpConnector.enhancedSearch(searchTerm, { includePosts: true, includePages: true, includeMedia: options.postTypes?.includes('attachment') || false, }); if (enhancedResponse.success) { if (enhancedResponse.data.posts) allResults = allResults.concat(enhancedResponse.data.posts); if (enhancedResponse.data.pages) allResults = allResults.concat(enhancedResponse.data.pages); if (enhancedResponse.data.media) allResults = allResults.concat(enhancedResponse.data.media); } } catch (error) { // Enhanced search failed, continue with other strategies } // Strategy 3: Fuzzy matching with search variations if (options.fuzzyMatching) { try { report.strategiesUsed.push('Fuzzy Matching with Variations'); report.totalAttempts++; const variations = WordPressSearchUtils.generateSearchVariations(searchTerm); for (const variation of variations.slice(0, 3)) { // Limit to top 3 variations if (variation !== searchTerm) { const response = await this.wpConnector.searchContent(variation); if (response.success && response.data.results) { allResults = allResults.concat(response.data.results); } } } } catch (error) { // Fuzzy search failed, continue with other strategies } } // Strategy 4: Category and tag-based search try { report.strategiesUsed.push('Taxonomy-Based Search'); report.totalAttempts++; const [categoriesResponse, tagsResponse] = await Promise.all([ this.wpConnector.getCategories({ search: searchTerm }), this.wpConnector.getTags({ search: searchTerm }), ]); if (categoriesResponse.success && categoriesResponse.data.length > 0) { for (const category of categoriesResponse.data.slice(0, 2)) { const postsResponse = await this.wpConnector.getPosts({ categories: [category.id] }); if (postsResponse.success && postsResponse.data.posts) { allResults = allResults.concat(postsResponse.data.posts); } } } if (tagsResponse.success && tagsResponse.data.length > 0) { for (const tag of tagsResponse.data.slice(0, 2)) { const postsResponse = await this.wpConnector.getPosts({ tags: [tag.id] }); if (postsResponse.success && postsResponse.data.posts) { allResults = allResults.concat(postsResponse.data.posts); } } } } catch (error) { // Taxonomy search failed, continue with other strategies } // Deduplicate results const uniqueResults = this.deduplicateResults(allResults); // Apply fuzzy matching and ranking const rankedResults = options.fuzzyMatching ? this.applyFuzzyMatching(uniqueResults, searchTerm) : uniqueResults; // Calculate confidence and coverage report.confidence = rankedResults.length > 0 ? Math.min(0.9, rankedResults.length / 10) : 0; report.coverage = rankedResults.length > 5 ? 'comprehensive' : rankedResults.length > 0 ? 'partial' : 'minimal'; return { results: rankedResults, searchReport: report, needsRetry: rankedResults.length === 0 && report.totalAttempts < 3, }; } deduplicateResults(results) { const seen = new Set(); return results.filter(result => { const id = result.id || result.slug || result.title?.rendered || JSON.stringify(result); if (seen.has(id)) { return false; } seen.add(id); return true; }); } applyFuzzyMatching(results, searchTerm) { return results .map(result => { const title = result.title?.rendered || result.name || ''; const content = result.content?.rendered || result.description || ''; const excerpt = result.excerpt?.rendered || ''; const titleSimilarity = WordPressSearchUtils.calculateSimilarity(searchTerm, title); const contentSimilarity = WordPressSearchUtils.calculateSimilarity(searchTerm, content); const excerptSimilarity = WordPressSearchUtils.calculateSimilarity(searchTerm, excerpt); const relevanceScore = Math.max(titleSimilarity * 2, contentSimilarity, excerptSimilarity); return { ...result, _relevanceScore: relevanceScore, }; }) .filter(result => result._relevanceScore > 0.1) .sort((a, b) => b._relevanceScore - a._relevanceScore); } } export class WordPressMCPRequestHandler { wpConnector; searchFeedbackLoop; constructor(wpConnector) { this.wpConnector = wpConnector; this.searchFeedbackLoop = new WordPressSearchFeedbackLoop(wpConnector); } async handleRequest(method, params) { console.log(`[WordPress MCP Handler] Processing: ${method}`); try { switch (method) { // Connection and Info case 'testConnection': return await this.wpConnector.testConnection(); case 'getSiteInfo': return await this.wpConnector.getSiteInfo(); case 'getContentStats': return await this.wpConnector.getContentStats(); // Posts Operations case 'getPosts': return await this.wpConnector.getPosts(params); case 'getPost': if (!params.id) throw new Error('Post ID is required'); return await this.wpConnector.getPost(params.id); case 'createPost': return await this.wpConnector.createPost(params); case 'updatePost': if (!params.id) throw new Error('Post ID is required'); return await this.wpConnector.updatePost(params); case 'deletePost': if (!params.id) throw new Error('Post ID is required'); return await this.wpConnector.deletePost(params.id, params.force); // Pages Operations case 'getPages': return await this.wpConnector.getPages(params); case 'getPage': if (!params.id) throw new Error('Page ID is required'); return await this.wpConnector.getPage(params.id); case 'createPage': return await this.wpConnector.createPage(params); case 'updatePage': if (!params.id) throw new Error('Page ID is required'); const { id: pageId, ...pageUpdateData } = params; return await this.wpConnector.updatePage(pageId, pageUpdateData); case 'deletePage': if (!params.id) throw new Error('Page ID is required'); return await this.wpConnector.deletePage(params.id, params.force); // Media Operations case 'getMedia': return await this.wpConnector.getMedia(params); case 'getMediaItem': if (!params.id) throw new Error('Media ID is required'); return await this.wpConnector.getMediaItem(params.id); case 'uploadMedia': return await this.wpConnector.uploadMedia(params); // Categories and Tags case 'getCategories': return await this.wpConnector.getCategories(params); case 'getTags': return await this.wpConnector.getTags(params); case 'createCategory': if (!params.name) throw new Error('Category name is required'); return await this.wpConnector.createCategory(params.name, params.description, params.parent); case 'createTag': if (!params.name) throw new Error('Tag name is required'); return await this.wpConnector.createTag(params.name, params.description); // Users case 'getUsers': return await this.wpConnector.getUsers(params); case 'getCurrentUser': return await this.wpConnector.getCurrentUser(); // Comments case 'getComments': return await this.wpConnector.getComments(params); case 'createComment': if (!params.post || !params.content) { throw new Error('Post ID and content are required'); } return await this.wpConnector.createComment(params.post, params.content, params.author_name, params.author_email); // Search Operations case 'searchContent': if (!params.query) throw new Error('Search query is required'); return await this.wpConnector.searchContent(params.query, params); case 'enhancedSearch': if (!params.query) throw new Error('Search query is required'); return await this.wpConnector.enhancedSearch(params.query, params.options || {}); case 'comprehensiveSearch': if (!params.searchTerm) throw new Error('Search term is required'); return await this.searchFeedbackLoop.comprehensiveSearch(params.searchTerm, params.options || {}); // Utility Operations case 'getContentBySlug': if (!params.slug) throw new Error('Slug is required'); return await this.wpConnector.getContentBySlug(params.slug, params.postType); case 'listMethods': return this.getAvailableMethods(); default: throw new Error(`Unknown method: ${method}`); } } catch (error) { console.error(`[WordPress MCP Handler] Error in ${method}:`, error); throw error; } } getAvailableMethods() { return [ // Connection and Info { name: 'testConnection', description: 'Test connection to WordPress site', parameters: [] }, { name: 'getSiteInfo', description: 'Get WordPress site information and settings', parameters: [] }, { name: 'getContentStats', description: 'Get content statistics (posts, pages, media counts)', parameters: [] }, // Posts Operations { name: 'getPosts', description: 'Get all posts with optional search parameters', parameters: ['search', 'author', 'per_page', 'page', 'status', 'categories', 'tags'] }, { name: 'getPost', description: 'Get a specific post by ID', parameters: ['id'] }, { name: 'createPost', description: 'Create a new post', parameters: ['title', 'content', 'status', 'excerpt', 'categories', 'tags', 'meta'] }, { name: 'updatePost', description: 'Update an existing post', parameters: ['id', 'title', 'content', 'status', 'excerpt', 'categories', 'tags', 'meta'] }, { name: 'deletePost', description: 'Delete a post', parameters: ['id', 'force'] }, // Pages Operations { name: 'getPages', description: 'Get all pages with optional search parameters', parameters: ['search', 'per_page', 'page', 'status', 'parent'] }, { name: 'getPage', description: 'Get a specific page by ID', parameters: ['id'] }, { name: 'createPage', description: 'Create a new page', parameters: ['title', 'content', 'status', 'parent', 'meta'] }, { name: 'updatePage', description: 'Update an existing page', parameters: ['id', 'title', 'content', 'status', 'parent', 'meta'] }, { name: 'deletePage', description: 'Delete a page', parameters: ['id', 'force'] }, // Media Operations { name: 'getMedia', description: 'Get media items with optional search parameters', parameters: ['search', 'per_page', 'page', 'media_type'] }, { name: 'getMediaItem', description: 'Get a specific media item by ID', parameters: ['id'] }, { name: 'uploadMedia', description: 'Upload a media file', parameters: ['file', 'filename', 'mimeType', 'title', 'alt_text', 'caption'] }, // Categories and Tags { name: 'getCategories', description: 'Get all categories', parameters: ['search', 'per_page', 'page', 'hide_empty'] }, { name: 'getTags', description: 'Get all tags', parameters: ['search', 'per_page', 'page', 'hide_empty'] }, { name: 'createCategory', description: 'Create a new category', parameters: ['name', 'description', 'parent'] }, { name: 'createTag', description: 'Create a new tag', parameters: ['name', 'description'] }, // Users { name: 'getUsers', description: 'Get all users', parameters: ['search', 'per_page', 'page', 'roles'] }, { name: 'getCurrentUser', description: 'Get current authenticated user', parameters: [] }, // Comments { name: 'getComments', description: 'Get comments with optional filters', parameters: ['post', 'per_page', 'page', 'status'] }, { name: 'createComment', description: 'Create a new comment', parameters: ['post', 'content', 'author_name', 'author_email'] }, // Search Operations { name: 'searchContent', description: 'Search content across WordPress using REST API', parameters: ['query', 'per_page', 'page', 'type'] }, { name: 'enhancedSearch', description: 'Enhanced search across multiple content types', parameters: ['query', 'options'] }, { name: 'comprehensiveSearch', description: 'Comprehensive search with fuzzy matching and multiple strategies', parameters: ['searchTerm', 'options'] }, // Utility Operations { name: 'getContentBySlug', description: 'Get content by slug', parameters: ['slug', 'postType'] }, { name: 'listMethods', description: 'List all available MCP methods', parameters: [] }, ]; } } //# sourceMappingURL=mcp-handler.js.map