@respira/wordpress-mcp-server 8.0.5 → 8.1.0

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.
package/dist/server.js CHANGED
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Provides tools for AI coding assistants to interact with WordPress sites
5
5
  */
6
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
6
+ import { Server } from '@modelcontextprotocol/server';
7
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
9
8
  import { execSync } from 'child_process';
10
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
11
10
  import { homedir } from 'os';
@@ -1345,13 +1344,13 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1345
1344
  }
1346
1345
  setupHandlers() {
1347
1346
  // List available tools
1348
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
1347
+ this.server.setRequestHandler('tools/list', async () => {
1349
1348
  return {
1350
1349
  tools: await this.getTools(),
1351
1350
  };
1352
1351
  });
1353
1352
  // Handle tool calls
1354
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
1353
+ this.server.setRequestHandler('tools/call', async (request) => {
1355
1354
  const { name, arguments: args } = request.params;
1356
1355
  // respira_redeem_token must work even before any site is configured —
1357
1356
  // it's the bootstrap path that puts the first sites into the config.
@@ -2209,7 +2208,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2209
2208
  },
2210
2209
  {
2211
2210
  name: 'wordpress_delete_post',
2212
- description: 'Delete a post. IMPORTANT: By default, this only works on Respira-created duplicates. The force parameter only works if "Allow Direct Editing" is enabled in Respira settings (disabled by default for safety).',
2211
+ description: 'Delete a post. IMPORTANT: By default, this only works on Respira-created duplicates. The force parameter only works if "Allow Direct Editing" is enabled in Respira settings (disabled by default for safety).\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2213
2212
  inputSchema: {
2214
2213
  type: 'object',
2215
2214
  properties: {
@@ -2221,6 +2220,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2221
2220
  type: 'boolean',
2222
2221
  description: 'Force delete even if not a duplicate',
2223
2222
  },
2223
+ approval_token: {
2224
+ type: 'string',
2225
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2226
+ },
2224
2227
  },
2225
2228
  required: ['id'],
2226
2229
  },
@@ -2738,7 +2741,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2738
2741
  },
2739
2742
  {
2740
2743
  name: 'wordpress_create_site_pattern',
2741
- description: 'Stage creation of a native synced or unsynced user pattern. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.',
2744
+ description: 'Stage creation of a native synced or unsynced user pattern. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the create.',
2742
2745
  inputSchema: {
2743
2746
  type: 'object',
2744
2747
  properties: {
@@ -2750,13 +2753,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2750
2753
  verification_marker: { type: 'string' },
2751
2754
  dry_run: { type: 'boolean' },
2752
2755
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2756
+ approval_token: {
2757
+ type: 'string',
2758
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2759
+ },
2753
2760
  },
2754
2761
  required: ['title'],
2755
2762
  },
2756
2763
  },
2757
2764
  {
2758
2765
  name: 'wordpress_update_site_pattern',
2759
- description: 'Stage targeted path-addressed operations on an editable user pattern with stale-write, wrong-target, snapshot, and render protection.',
2766
+ description: 'Stage targeted path-addressed operations on an editable user pattern with stale-write, wrong-target, snapshot, and render protection.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the update.',
2760
2767
  inputSchema: {
2761
2768
  type: 'object',
2762
2769
  properties: {
@@ -2766,13 +2773,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2766
2773
  verification_marker: { type: 'string' },
2767
2774
  dry_run: { type: 'boolean' },
2768
2775
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2776
+ approval_token: {
2777
+ type: 'string',
2778
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2779
+ },
2769
2780
  },
2770
2781
  required: ['id', 'expected_fingerprint', 'operations'],
2771
2782
  },
2772
2783
  },
2773
2784
  {
2774
2785
  name: 'wordpress_delete_site_pattern',
2775
- description: 'Stage deletion of an editable user pattern. Referenced patterns are refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.',
2786
+ description: 'Stage deletion of an editable user pattern. Referenced patterns are refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2776
2787
  inputSchema: {
2777
2788
  type: 'object',
2778
2789
  properties: {
@@ -2781,6 +2792,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2781
2792
  allow_referenced_delete: { type: 'boolean' },
2782
2793
  dry_run: { type: 'boolean' },
2783
2794
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2795
+ approval_token: {
2796
+ type: 'string',
2797
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2798
+ },
2784
2799
  },
2785
2800
  required: ['id', 'expected_fingerprint'],
2786
2801
  },
@@ -2811,7 +2826,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2811
2826
  },
2812
2827
  {
2813
2828
  name: 'wordpress_create_site_navigation',
2814
- description: 'Stage creation of a native block navigation entity. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.',
2829
+ description: 'Stage creation of a native block navigation entity. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the create.',
2815
2830
  inputSchema: {
2816
2831
  type: 'object',
2817
2832
  properties: {
@@ -2822,13 +2837,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2822
2837
  verification_marker: { type: 'string' },
2823
2838
  dry_run: { type: 'boolean' },
2824
2839
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2840
+ approval_token: {
2841
+ type: 'string',
2842
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2843
+ },
2825
2844
  },
2826
2845
  required: ['title'],
2827
2846
  },
2828
2847
  },
2829
2848
  {
2830
2849
  name: 'wordpress_update_site_navigation',
2831
- description: 'Stage exact path-addressed operations on a native block navigation entity while preserving unknown extension blocks and rejecting stale or wrong targets.',
2850
+ description: 'Stage exact path-addressed operations on a native block navigation entity while preserving unknown extension blocks and rejecting stale or wrong targets.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the update.',
2832
2851
  inputSchema: {
2833
2852
  type: 'object',
2834
2853
  properties: {
@@ -2838,13 +2857,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2838
2857
  verification_marker: { type: 'string' },
2839
2858
  dry_run: { type: 'boolean' },
2840
2859
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2860
+ approval_token: {
2861
+ type: 'string',
2862
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2863
+ },
2841
2864
  },
2842
2865
  required: ['id', 'expected_fingerprint', 'operations'],
2843
2866
  },
2844
2867
  },
2845
2868
  {
2846
2869
  name: 'wordpress_delete_site_navigation',
2847
- description: 'Stage deletion of a native block navigation entity. Referenced navigation is refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.',
2870
+ description: 'Stage deletion of a native block navigation entity. Referenced navigation is refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2848
2871
  inputSchema: {
2849
2872
  type: 'object',
2850
2873
  properties: {
@@ -2853,6 +2876,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2853
2876
  allow_referenced_delete: { type: 'boolean' },
2854
2877
  dry_run: { type: 'boolean' },
2855
2878
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2879
+ approval_token: {
2880
+ type: 'string',
2881
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2882
+ },
2856
2883
  },
2857
2884
  required: ['id', 'expected_fingerprint'],
2858
2885
  },
@@ -3780,7 +3807,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3780
3807
  },
3781
3808
  {
3782
3809
  name: 'wordpress_delete_comment',
3783
- description: 'Delete a comment.',
3810
+ description: 'Delete a comment.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
3784
3811
  inputSchema: {
3785
3812
  type: 'object',
3786
3813
  properties: {
@@ -3788,6 +3815,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3788
3815
  type: 'number',
3789
3816
  description: 'Comment ID',
3790
3817
  },
3818
+ approval_token: {
3819
+ type: 'string',
3820
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
3821
+ },
3791
3822
  },
3792
3823
  required: ['id'],
3793
3824
  },
@@ -3931,7 +3962,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3931
3962
  },
3932
3963
  {
3933
3964
  name: 'wordpress_delete_term',
3934
- description: 'Delete a term.',
3965
+ description: 'Delete a term.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
3935
3966
  inputSchema: {
3936
3967
  type: 'object',
3937
3968
  properties: {
@@ -3943,6 +3974,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3943
3974
  type: 'number',
3944
3975
  description: 'Term ID',
3945
3976
  },
3977
+ approval_token: {
3978
+ type: 'string',
3979
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
3980
+ },
3946
3981
  },
3947
3982
  required: ['taxonomy', 'id'],
3948
3983
  },
@@ -4535,7 +4570,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4535
4570
  },
4536
4571
  {
4537
4572
  name: 'wordpress_delete_option',
4538
- description: 'Delete an option.',
4573
+ description: 'Delete an option.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
4539
4574
  inputSchema: {
4540
4575
  type: 'object',
4541
4576
  properties: {
@@ -4543,6 +4578,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4543
4578
  type: 'string',
4544
4579
  description: 'Option name',
4545
4580
  },
4581
+ approval_token: {
4582
+ type: 'string',
4583
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
4584
+ },
4546
4585
  },
4547
4586
  required: ['option'],
4548
4587
  },
@@ -6923,7 +6962,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6923
6962
  };
6924
6963
  }
6925
6964
  case 'wordpress_delete_post':
6926
- return await client.deletePost(args.id, args.force);
6965
+ return await client.deletePost(args.id, args.force, args.approval_token);
6927
6966
  case 'wordpress_list_media':
6928
6967
  return await client.listMedia(args);
6929
6968
  case 'wordpress_upload_media': {
@@ -7154,7 +7193,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7154
7193
  case 'wordpress_update_comment':
7155
7194
  return await client.updateComment(args.id, args);
7156
7195
  case 'wordpress_delete_comment':
7157
- return await client.deleteComment(args.id);
7196
+ return await client.deleteComment(args.id, args.approval_token);
7158
7197
  // Taxonomies
7159
7198
  case 'wordpress_list_taxonomies':
7160
7199
  return await client.listTaxonomies();
@@ -7169,7 +7208,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7169
7208
  case 'wordpress_update_term':
7170
7209
  return await client.updateTerm(args.taxonomy, args.id, args);
7171
7210
  case 'wordpress_delete_term':
7172
- return await client.deleteTerm(args.taxonomy, args.id);
7211
+ return await client.deleteTerm(args.taxonomy, args.id, args.approval_token);
7173
7212
  // Custom Post Types
7174
7213
  case 'wordpress_list_post_types':
7175
7214
  return await client.listPostTypes();
@@ -7244,7 +7283,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7244
7283
  case 'wordpress_update_option':
7245
7284
  return await client.updateOption(args.option, args.value);
7246
7285
  case 'wordpress_delete_option':
7247
- return await client.deleteOption(args.option);
7286
+ return await client.deleteOption(args.option, args.approval_token);
7248
7287
  // Media enhancements
7249
7288
  case 'wordpress_get_media':
7250
7289
  return await client.getMedia(args.id);
@@ -7763,7 +7802,18 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7763
7802
  hint: 'Install Advanced Custom Fields on the target site, then retry. ACF Pro is required for repeaters, flexible content, galleries, and options pages.'
7764
7803
  };
7765
7804
  }
7766
- return await this.currentSite.dispatchAcfTool(acfName, args);
7805
+ // Same class of bug as the mcp-v8.0.3 Bricks/Elementor fix: `client`
7806
+ // (resolved above via resolveClient(args), site_id-aware) is the
7807
+ // correct target — NOT `this.currentSite`, which is only the
7808
+ // globally active site. This ACF branch used `this.currentSite!`,
7809
+ // so respira_acf_update_option(s)/get_option(s) and every other ACF
7810
+ // tool silently read/wrote the active site while the response
7811
+ // envelope (built from args.site_id in getActiveSiteSummary/
7812
+ // withSiteContext) still named the pinned site_id, making the
7813
+ // wrong-site write invisible. Reported by john@localmarketingpros.com
7814
+ // (17-site DSO rollout): a population pass pinned to site B landed
7815
+ // on site A instead.
7816
+ return await client.dispatchAcfTool(acfName, args);
7767
7817
  }
7768
7818
  // Check if this is a widget shortcut (wordpress_add_*).
7769
7819
  if (name.startsWith('wordpress_add_')) {
@@ -8089,7 +8139,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
8089
8139
  },
8090
8140
  {
8091
8141
  name: 'wordpress_bulk_pages_operation',
8092
- description: 'Apply an operation across up to 100 pages. Supports strip_inline_styles, find_and_replace, and custom operations. Each page gets a snapshot for rollback. Rate limited to 3/hr.',
8142
+ description: 'Apply an operation across up to 100 pages. Supports strip_inline_styles, find_and_replace, and custom operations. Each page gets a snapshot for rollback. Rate limited to 3/hr.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the operation.',
8093
8143
  inputSchema: {
8094
8144
  type: 'object',
8095
8145
  properties: {
@@ -8106,6 +8156,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
8106
8156
  type: 'object',
8107
8157
  description: 'Execution options: {dry_run: boolean}',
8108
8158
  },
8159
+ approval_token: {
8160
+ type: 'string',
8161
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
8162
+ },
8109
8163
  },
8110
8164
  required: ['page_ids', 'operation'],
8111
8165
  },
@@ -8491,8 +8545,14 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
8491
8545
  }));
8492
8546
  }
8493
8547
  async run() {
8494
- const transport = new StdioServerTransport();
8495
- await this.server.connect(transport);
8548
+ // SDK 2's stdio serving entry owns the opening exchange. It selects
8549
+ // server/discover for MCP 2026-07-28 clients and the legacy initialize
8550
+ // handshake for older clients, then pins this configured server instance
8551
+ // to the connection.
8552
+ serveStdio(() => this.server, {
8553
+ legacy: 'serve',
8554
+ onerror: (error) => console.error('[respira-mcp] protocol error:', error.message),
8555
+ });
8496
8556
  // v6.17: one-line bootstrap log so the customer can see whether this MCP
8497
8557
  // came back after a Claude restart. Pre-v6.17 the only stderr line was
8498
8558
  // "Respira WordPress MCP Server running on stdio" which didn't tell you