From 700f9baed17f507217bb29aeacd3c92ec8e83552 Mon Sep 17 00:00:00 2001 From: Jon Klein Date: Wed, 13 May 2026 19:11:24 -0400 Subject: [PATCH] feat(wordpress): strip yoast_head fields from REST responses Adds a pure trimResponseFields() helper applied inside makeWordPressRequest right before the response is returned to the MCP client. By default it strips the top-level yoast_head and yoast_head_json fields, which Yoast SEO adds to every post/page response and which contribute ~10KB of pre-rendered schema markup the LLM never needs. The strip list is overridable via the MCP_WP_STRIP_FIELDS environment variable (comma-separated). Set it to an empty string to disable trimming. Only top-level fields are stripped; nested objects are left intact. Both single-object responses and arrays of objects are handled. The escape hatch on makeWordPressRequest (rawResponse: true) bypasses trimming. Co-Authored-By: Claude Opus 4.7 --- README.md | 19 +++++++++++++++++++ src/wordpress.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9ae91d..41dd1f9 100644 --- a/README.md +++ b/README.md @@ -224,8 +224,27 @@ WORDPRESS_PASSWORD=wp_app_password # Optional: Custom SQL query endpoint (default: /mcp/v1/query) WORDPRESS_SQL_ENDPOINT=/mcp/v1/query + +# Optional: Comma-separated list of top-level fields to strip from +# WordPress REST API responses before they are returned to the MCP +# client. Defaults to "yoast_head,yoast_head_json" — read-only schema +# markup that adds ~10KB to every response but is rarely useful to the +# LLM. Set to an empty string to disable trimming. +MCP_WP_STRIP_FIELDS=yoast_head,yoast_head_json ``` +## Response Trimming + +By default the server strips the top-level `yoast_head` and `yoast_head_json` +fields from every WordPress REST API response before returning it to the MCP +client. These fields contain Yoast SEO's pre-rendered schema markup, which the +LLM almost never needs but pays tokens for on every request. + +- The trim applies to both single-object responses and arrays of objects. +- Only **top-level** fields are stripped; nested objects are left untouched. +- Override the list with the `MCP_WP_STRIP_FIELDS` environment variable + (comma-separated). Set it to an empty string to disable trimming entirely. + ## Enabling SQL Query Tool (Optional) The `execute_sql_query` tool allows you to run read-only SQL queries against your WordPress database. This is an optional feature that requires adding a custom REST API endpoint to your WordPress site. diff --git a/src/wordpress.ts b/src/wordpress.ts index 92564bc..a8363f6 100644 --- a/src/wordpress.ts +++ b/src/wordpress.ts @@ -6,6 +6,46 @@ import { siteManager } from './config/site-manager.js'; // Legacy global WordPress API client instance for backward compatibility let wpClient: AxiosInstance; +const DEFAULT_STRIP_FIELDS = ['yoast_head', 'yoast_head_json']; + +/** + * Resolve the list of top-level fields to strip from WP REST responses. + * Reads MCP_WP_STRIP_FIELDS (comma-separated) and falls back to the default list. + * An empty string disables trimming. + */ +export function resolveStripFields(envValue?: string): string[] { + if (envValue === undefined) return DEFAULT_STRIP_FIELDS; + return envValue + .split(',') + .map(f => f.trim()) + .filter(f => f.length > 0); +} + +/** + * Pure function: return a shallow copy of `data` with `fields` removed at the + * top level. Arrays of objects are mapped; nested objects are left untouched. + * Non-object/non-array inputs (null, primitives) are returned as-is. + */ +export function trimResponseFields(data: T, fields: string[]): T { + if (fields.length === 0) return data; + if (data === null || data === undefined) return data; + + if (Array.isArray(data)) { + return data.map(item => trimResponseFields(item, fields)) as unknown as T; + } + + if (typeof data === 'object') { + const fieldSet = new Set(fields); + const out: Record = {}; + for (const [key, value] of Object.entries(data as Record)) { + if (!fieldSet.has(key)) out[key] = value; + } + return out as T; + } + + return data; +} + /** * Initialize the WordPress API client with authentication * Now uses SiteManager for multi-site support @@ -106,8 +146,10 @@ Status: ${response.status} Data: ${JSON.stringify(response.data, null, 2)} `; logToFile(responseLog, 'debug'); - - return options?.rawResponse ? response : response.data; + + if (options?.rawResponse) return response; + const stripFields = resolveStripFields(process.env.MCP_WP_STRIP_FIELDS); + return trimResponseFields(response.data, stripFields); } catch (error: any) { const errorLog = ` ERROR: