@respira/wordpress-mcp-server 6.6.0 → 6.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAEzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAW;IAErD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAuPvE,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,oBAAoB;IAQ5B,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAuIP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAk5DtB;;;;;;OAMG;IACH,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;YAEpC,oBAAoB;YAqDpB,2BAA2B;IAazC;;;;OAIG;YACW,cAAc;IAY5B,OAAO,CAAC,mBAAmB;YAwTb,cAAc;YAoBd,gBAAgB;IAkkB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuQzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAKV"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAuBzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAW;IAErD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAuPvE,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,oBAAoB;IAQ5B,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YA0LP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAoiEtB;;;;;;OAMG;IACH,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;YAEpC,oBAAoB;YAqDpB,2BAA2B;IAazC;;;;OAIG;YACW,cAAc;IAY5B,OAAO,CAAC,mBAAmB;YAwTb,cAAc;YAoBd,gBAAgB;IAkkB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuQzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAKV"}
package/dist/server.js CHANGED
@@ -11,6 +11,27 @@ import { RespiraVersionChecker } from './version-checker.js';
11
11
  import { getBricksTools, dispatchBricksTool } from './bricks-tools.js';
12
12
  import { getElementorTools, dispatchElementorTool } from './elementor-tools.js';
13
13
  import { getAcfTools, ACF_TOOL_NAMES } from './acf-tools.js';
14
+ /**
15
+ * Thrown by the per-call watchdog when a tool handler exceeds its deadline.
16
+ * Surfaces as a structured `tool_timeout` response so the stdio loop stays
17
+ * responsive and every subsequent tool call keeps working.
18
+ */
19
+ class ToolTimeoutError extends Error {
20
+ toolName;
21
+ timeoutMs;
22
+ constructor(toolName, timeoutMs) {
23
+ super(`Tool "${toolName}" did not complete within ${timeoutMs}ms. ` +
24
+ `The WordPress server may be unresponsive, the operation may legitimately take longer than configured, ` +
25
+ `or the handler may be stuck. Other tools should continue to work. ` +
26
+ `Raise RESPIRA_MAX_TOOL_TIMEOUT_MS if the operation genuinely needs more time.`);
27
+ this.toolName = toolName;
28
+ this.timeoutMs = timeoutMs;
29
+ this.name = 'ToolTimeoutError';
30
+ }
31
+ }
32
+ function getMaxToolTimeoutMs() {
33
+ return Math.max(5000, parseInt(process.env.RESPIRA_MAX_TOOL_TIMEOUT_MS || '120000', 10));
34
+ }
14
35
  export class RespiraWordPressServer {
15
36
  server;
16
37
  currentSite = null;
@@ -22,7 +43,7 @@ export class RespiraWordPressServer {
22
43
  allowedSites = null;
23
44
  /** Whether the plugin version warning has already been shown this session. */
24
45
  versionWarningShown = false;
25
- static MCP_SERVER_VERSION = '6.3.0';
46
+ static MCP_SERVER_VERSION = '6.6.4';
26
47
  /**
27
48
  * Normalize a tool name: respira_* → wordpress_* for switch dispatch.
28
49
  * Tracks whether the deprecated wordpress_* name was used.
@@ -351,9 +372,25 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
351
372
  if (!this.currentSite) {
352
373
  throw new Error('No WordPress site configured');
353
374
  }
375
+ // Per-call watchdog: a single hung handler must never wedge the stdio
376
+ // loop for every subsequent tool call. If the deadline fires, the race
377
+ // rejects with ToolTimeoutError; the losing handler keeps running in
378
+ // the background but the client gets a clean structured error and the
379
+ // next tool call is free to proceed.
380
+ const toolTimeoutMs = getMaxToolTimeoutMs();
381
+ let timeoutHandle;
382
+ const toolTimeout = new Promise((_, reject) => {
383
+ timeoutHandle = setTimeout(() => reject(new ToolTimeoutError(name, toolTimeoutMs)), toolTimeoutMs);
384
+ });
354
385
  try {
355
386
  this.currentSite.setCurrentToolName(name);
356
- const result = await this.handleToolCall(name, args || {});
387
+ const result = await Promise.race([
388
+ this.handleToolCall(name, args || {}),
389
+ toolTimeout,
390
+ ]);
391
+ if (timeoutHandle) {
392
+ clearTimeout(timeoutHandle);
393
+ }
357
394
  this.currentSite.setCurrentToolName(null);
358
395
  // Handle soft errors (e.g. unknown tool) — return isError without throwing.
359
396
  if (result && result.__respira_is_error) {
@@ -384,7 +421,31 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
384
421
  };
385
422
  }
386
423
  catch (error) {
424
+ if (timeoutHandle) {
425
+ clearTimeout(timeoutHandle);
426
+ }
387
427
  this.currentSite?.setCurrentToolName(null);
428
+ if (error instanceof ToolTimeoutError) {
429
+ const activeSite = this.getActiveSiteSummary();
430
+ return {
431
+ content: [
432
+ {
433
+ type: 'text',
434
+ text: JSON.stringify({
435
+ error: 'tool_timeout',
436
+ tool: error.toolName,
437
+ timeout_ms: error.timeoutMs,
438
+ message: error.message,
439
+ hint: 'Other tools should continue to work. ' +
440
+ 'If this operation genuinely needs more time, raise RESPIRA_MAX_TOOL_TIMEOUT_MS. ' +
441
+ 'For uploads specifically, also see RESPIRA_UPLOAD_TIMEOUT_MS and RESPIRA_MAX_UPLOAD_MB.',
442
+ site: activeSite,
443
+ }, null, 2),
444
+ },
445
+ ],
446
+ isError: true,
447
+ };
448
+ }
388
449
  // Handle Promise objects that weren't awaited (they would have .then method)
389
450
  if (error && typeof error.then === 'function') {
390
451
  // Try to await the Promise to get the actual error
@@ -575,10 +636,15 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
575
636
  },
576
637
  {
577
638
  name: 'wordpress_get_builder_info',
578
- description: 'Detect which page builder is active on the site and get its version, support level (Full Intelligence / Smart Defaults / Basic), available modules/widgets, and capabilities. Call this first before working with builder content to understand what format to use.',
639
+ description: 'Detect which page builder is active on the site and get its version, support level (Full Intelligence / Smart Defaults / Basic), available modules/widgets, and capabilities. Call this first before working with builder content to understand what format to use. Pass debug=true to bypass the cache and include per-builder detection_signals (constants, classes, active plugin slugs, plugin headers, theme) — use this when a builder is installed but reports detected=false.',
579
640
  inputSchema: {
580
641
  type: 'object',
581
- properties: {},
642
+ properties: {
643
+ debug: {
644
+ type: 'boolean',
645
+ description: 'When true, bypass the 1-hour detection cache and include detection_signals for each builder that exposes them. Use to diagnose false negatives.',
646
+ },
647
+ },
582
648
  },
583
649
  readOnlyHint: true,
584
650
  },
@@ -752,7 +818,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
752
818
  },
753
819
  {
754
820
  name: 'wordpress_read_post',
755
- description: 'Get full content of a specific post.',
821
+ description: 'Get full content of a specific post. Response includes author object ({id, login, display_name}), taxonomies map ({category, post_tag, ...} each with term {id, name, slug}), and featured_media ({id, url}) alongside title/content/slug/status/date/url/meta.',
756
822
  inputSchema: {
757
823
  type: 'object',
758
824
  properties: {
@@ -789,7 +855,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
789
855
  },
790
856
  {
791
857
  name: 'wordpress_update_post',
792
- description: 'Update a post. When direct editing is enabled and you target an original post, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
858
+ description: 'Update a post. Supports author reassignment, taxonomy terms (categories/tags/custom), and featured media. When direct editing is enabled and you target an original post, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
793
859
  inputSchema: {
794
860
  type: 'object',
795
861
  properties: {
@@ -813,6 +879,49 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
813
879
  type: 'string',
814
880
  description: 'URL slug (post_name). Example: "my-post" produces /my-post/.',
815
881
  },
882
+ author: {
883
+ description: 'Author (user id or user login). Requires edit_others_posts capability when different from the acting user.',
884
+ oneOf: [
885
+ { type: 'number' },
886
+ { type: 'string' },
887
+ ],
888
+ },
889
+ categories: {
890
+ type: 'array',
891
+ description: 'Shorthand for the built-in "category" taxonomy. Accepts term ids (number) or slugs/names (string). Replaces existing categories unless append_terms=true.',
892
+ items: {
893
+ oneOf: [
894
+ { type: 'number' },
895
+ { type: 'string' },
896
+ ],
897
+ },
898
+ },
899
+ tags: {
900
+ type: 'array',
901
+ description: 'Shorthand for the built-in "post_tag" taxonomy. Accepts term ids (number) or slugs/names (string).',
902
+ items: {
903
+ oneOf: [
904
+ { type: 'number' },
905
+ { type: 'string' },
906
+ ],
907
+ },
908
+ },
909
+ taxonomies: {
910
+ type: 'object',
911
+ description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies (e.g. product_cat). Example: { "product_cat": [12, "featured"] }.',
912
+ },
913
+ featuredMedia: {
914
+ type: 'number',
915
+ description: 'Attachment ID to set as the featured image. Pass 0 to clear.',
916
+ },
917
+ createMissingTerms: {
918
+ type: 'boolean',
919
+ description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict: unknown slug returns 400).',
920
+ },
921
+ appendTerms: {
922
+ type: 'boolean',
923
+ description: 'When true, append to existing taxonomy terms instead of replacing. Default false (replace).',
924
+ },
816
925
  customCss: {
817
926
  type: 'string',
818
927
  description: 'Custom CSS for the post. Auto-detects builder: Divi → _et_pb_custom_css, Elementor → _elementor_page_settings.custom_css.',
@@ -882,7 +991,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
882
991
  },
883
992
  {
884
993
  name: 'wordpress_upload_media',
885
- description: 'Upload a media file (image, document, video) to WordPress. Supports base64 encoded files, file URLs, file paths, or raw SVG markup strings (starting with <svg or <?xml).',
994
+ description: 'Upload a media file (image, document, video) to WordPress. Supports base64 encoded files, file URLs, file paths, or raw SVG markup strings (starting with <svg or <?xml). Fails fast with a clear error if the file exceeds 50MB (override via RESPIRA_MAX_UPLOAD_MB) or the upload does not complete within 120 seconds (override via RESPIRA_UPLOAD_TIMEOUT_MS).',
886
995
  inputSchema: {
887
996
  type: 'object',
888
997
  properties: {
@@ -1887,7 +1996,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1887
1996
  },
1888
1997
  {
1889
1998
  name: 'wordpress_list_custom_posts',
1890
- description: 'List posts of a custom post type.',
1999
+ description: 'List posts of a custom post type. Each row includes author ({id, login, display_name}), taxonomies map, and featured_media alongside id/title/slug/status/date/url.',
1891
2000
  inputSchema: {
1892
2001
  type: 'object',
1893
2002
  properties: {
@@ -1945,13 +2054,13 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1945
2054
  },
1946
2055
  {
1947
2056
  name: 'wordpress_create_custom_post',
1948
- description: 'Create a new custom post.',
2057
+ description: 'Create a new post. Supports author, taxonomy terms (categories/tags/custom), and featured media inline so the full "same category and author as X" pattern works in a single call.',
1949
2058
  inputSchema: {
1950
2059
  type: 'object',
1951
2060
  properties: {
1952
2061
  type: {
1953
2062
  type: 'string',
1954
- description: 'Post type name',
2063
+ description: 'Post type name (e.g. "post", "page", "product")',
1955
2064
  },
1956
2065
  title: {
1957
2066
  type: 'string',
@@ -1965,13 +2074,68 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1965
2074
  type: 'string',
1966
2075
  description: 'Post status (default: draft)',
1967
2076
  },
2077
+ slug: {
2078
+ type: 'string',
2079
+ description: 'URL slug (post_name). Example: "my-post" produces /my-post/.',
2080
+ },
2081
+ excerpt: {
2082
+ type: 'string',
2083
+ description: 'Optional post excerpt.',
2084
+ },
2085
+ date: {
2086
+ type: 'string',
2087
+ description: 'Optional publish date in YYYY-MM-DD HH:MM:SS (server local time) for backdating.',
2088
+ },
2089
+ author: {
2090
+ description: 'Author (user id or user login). Requires edit_others_posts capability when different from the acting user.',
2091
+ oneOf: [
2092
+ { type: 'number' },
2093
+ { type: 'string' },
2094
+ ],
2095
+ },
2096
+ categories: {
2097
+ type: 'array',
2098
+ description: 'Shorthand for the built-in "category" taxonomy. Accepts term ids (number) or slugs/names (string).',
2099
+ items: {
2100
+ oneOf: [
2101
+ { type: 'number' },
2102
+ { type: 'string' },
2103
+ ],
2104
+ },
2105
+ },
2106
+ tags: {
2107
+ type: 'array',
2108
+ description: 'Shorthand for the built-in "post_tag" taxonomy. Accepts term ids (number) or slugs/names (string).',
2109
+ items: {
2110
+ oneOf: [
2111
+ { type: 'number' },
2112
+ { type: 'string' },
2113
+ ],
2114
+ },
2115
+ },
2116
+ taxonomies: {
2117
+ type: 'object',
2118
+ description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies (e.g. product_cat). Example: { "product_cat": [12, "featured"] }.',
2119
+ },
2120
+ featuredMedia: {
2121
+ type: 'number',
2122
+ description: 'Attachment ID to set as the featured image.',
2123
+ },
2124
+ createMissingTerms: {
2125
+ type: 'boolean',
2126
+ description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict: unknown slug returns 400 and the post is rolled back).',
2127
+ },
2128
+ meta: {
2129
+ type: 'object',
2130
+ description: 'Post meta data (map of meta key to value).',
2131
+ },
1968
2132
  },
1969
2133
  required: ['type', 'title'],
1970
2134
  },
1971
2135
  },
1972
2136
  {
1973
2137
  name: 'wordpress_update_custom_post',
1974
- description: 'Update a custom post. When direct editing is enabled and you target an original item, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
2138
+ description: 'Update a custom post. Supports author reassignment, taxonomy terms (categories/tags/custom), and featured media. When direct editing is enabled and you target an original item, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
1975
2139
  inputSchema: {
1976
2140
  type: 'object',
1977
2141
  properties: {
@@ -1999,6 +2163,49 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1999
2163
  type: 'string',
2000
2164
  description: 'URL slug (post_name).',
2001
2165
  },
2166
+ author: {
2167
+ description: 'Author (user id or user login). Requires edit_others_posts capability when different from the acting user.',
2168
+ oneOf: [
2169
+ { type: 'number' },
2170
+ { type: 'string' },
2171
+ ],
2172
+ },
2173
+ categories: {
2174
+ type: 'array',
2175
+ description: 'Shorthand for the built-in "category" taxonomy. Accepts term ids (number) or slugs/names (string).',
2176
+ items: {
2177
+ oneOf: [
2178
+ { type: 'number' },
2179
+ { type: 'string' },
2180
+ ],
2181
+ },
2182
+ },
2183
+ tags: {
2184
+ type: 'array',
2185
+ description: 'Shorthand for the built-in "post_tag" taxonomy. Accepts term ids (number) or slugs/names (string).',
2186
+ items: {
2187
+ oneOf: [
2188
+ { type: 'number' },
2189
+ { type: 'string' },
2190
+ ],
2191
+ },
2192
+ },
2193
+ taxonomies: {
2194
+ type: 'object',
2195
+ description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies.',
2196
+ },
2197
+ featuredMedia: {
2198
+ type: 'number',
2199
+ description: 'Attachment ID to set as the featured image. Pass 0 to clear.',
2200
+ },
2201
+ createMissingTerms: {
2202
+ type: 'boolean',
2203
+ description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict).',
2204
+ },
2205
+ appendTerms: {
2206
+ type: 'boolean',
2207
+ description: 'When true, append to existing taxonomy terms instead of replacing. Default false (replace).',
2208
+ },
2002
2209
  meta: {
2003
2210
  type: 'object',
2004
2211
  description: 'Post meta data (e.g., { "_et_pb_custom_css": "/* CSS */" })',
@@ -2890,7 +3097,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2890
3097
  case 'wordpress_get_theme_docs':
2891
3098
  return await this.currentSite.getThemeDocs();
2892
3099
  case 'wordpress_get_builder_info':
2893
- return await this.currentSite.getBuilderInfo();
3100
+ return await this.currentSite.getBuilderInfo({ debug: Boolean(args?.debug) });
2894
3101
  case 'wordpress_list_pages':
2895
3102
  return await this.currentSite.listPages(args);
2896
3103
  case 'wordpress_read_page':