Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
46 changes: 44 additions & 2 deletions src/wordpress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(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<string, unknown> = {};
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
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
Expand Down Expand Up @@ -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:
Expand Down