@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
JavaScript
// 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