@respira/wordpress-mcp-server 6.19.11 → 7.1.1
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/README.md +17 -5
- package/certs/mozilla-ca.pem +3228 -0
- package/dist/__tests__/startup-guard.test.d.ts +2 -0
- package/dist/__tests__/startup-guard.test.d.ts.map +1 -0
- package/dist/__tests__/startup-guard.test.js +49 -0
- package/dist/__tests__/startup-guard.test.js.map +1 -0
- package/dist/__tests__/system-ca.test.d.ts +2 -0
- package/dist/__tests__/system-ca.test.d.ts.map +1 -0
- package/dist/__tests__/system-ca.test.js +232 -0
- package/dist/__tests__/system-ca.test.js.map +1 -0
- package/dist/bricks-tools.d.ts.map +1 -1
- package/dist/bricks-tools.js +121 -0
- package/dist/bricks-tools.js.map +1 -1
- package/dist/config.d.ts +1 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -10
- package/dist/config.js.map +1 -1
- package/dist/index.js +25 -2
- package/dist/index.js.map +1 -1
- package/dist/install-skills.d.ts +20 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +206 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +348 -6
- package/dist/server.js.map +1 -1
- package/dist/startup-guard.d.ts +73 -0
- package/dist/startup-guard.d.ts.map +1 -0
- package/dist/startup-guard.js +169 -0
- package/dist/startup-guard.js.map +1 -0
- package/dist/system-ca.d.ts +30 -0
- package/dist/system-ca.d.ts.map +1 -0
- package/dist/system-ca.js +195 -0
- package/dist/system-ca.js.map +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/wordpress-client.d.ts +23 -6
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +74 -23
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +4 -3
- package/skills/respira-builder-edits/SKILL.md +100 -0
- package/skills/respira-setup/SKILL.md +36 -18
- package/skills/respira-site-audit/SKILL.md +113 -0
- package/skills/respira-woocommerce/SKILL.md +135 -0
package/dist/server.js
CHANGED
|
@@ -704,6 +704,15 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
704
704
|
const direct = process.env.RESPIRA_USAGE_TOKEN?.trim();
|
|
705
705
|
if (direct)
|
|
706
706
|
return direct.startsWith('Bearer ') ? direct.slice(7).trim() : direct;
|
|
707
|
+
// respira.press /mcp/report-issue accepts the top-level license key as
|
|
708
|
+
// an api-shaped bearer, but pre-v6.19.4 the MCP reporter never tried
|
|
709
|
+
// RESPIRA_LICENSE_KEY. Sites installed via the standard npx/env flow
|
|
710
|
+
// then fell through to the WordPress API key, which is valid for the
|
|
711
|
+
// plugin but not for respira.press, producing a 401 exactly when the
|
|
712
|
+
// customer was trying to file a bug.
|
|
713
|
+
const licenseKey = process.env.RESPIRA_LICENSE_KEY?.trim();
|
|
714
|
+
if (licenseKey)
|
|
715
|
+
return licenseKey.startsWith('Bearer ') ? licenseKey.slice(7).trim() : licenseKey;
|
|
707
716
|
const headers = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
708
717
|
if (headers) {
|
|
709
718
|
for (const part of headers.split(/[,|]/)) {
|
|
@@ -1220,12 +1229,27 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1220
1229
|
process.env.RESPIRA_DEBUG === 'true' ||
|
|
1221
1230
|
process.env.NODE_ENV === 'development';
|
|
1222
1231
|
const stack = debugEnabled && error?.stack ? error.stack : undefined;
|
|
1232
|
+
// v6.21.2: surface the structured WP_Error envelope attached by
|
|
1233
|
+
// wordpress-client. Pre-fix, the agent only ever saw the human
|
|
1234
|
+
// message; structured fields (missing_args, required_path_keys,
|
|
1235
|
+
// hint, instructions, registered_abilities_sample, destructive_tools)
|
|
1236
|
+
// were dropped, breaking every error-handling skill built against
|
|
1237
|
+
// the documented envelope contract. Mihai's QA run-4 caught this
|
|
1238
|
+
// as the "envelope-vs-text mismatch".
|
|
1239
|
+
const respiraErrorEnvelope = error && typeof error === 'object' && error.respiraErrorEnvelope
|
|
1240
|
+
? error.respiraErrorEnvelope
|
|
1241
|
+
: undefined;
|
|
1223
1242
|
return {
|
|
1224
1243
|
content: [
|
|
1225
1244
|
{
|
|
1226
1245
|
type: 'text',
|
|
1227
1246
|
text: JSON.stringify({
|
|
1228
1247
|
error: errorWithUpdateNotice,
|
|
1248
|
+
// Spread the structured envelope at top level so callers
|
|
1249
|
+
// reading `result.code`, `result.missing_args`, etc. find
|
|
1250
|
+
// them without nesting. Top-level keeps the contract close
|
|
1251
|
+
// to the WP_Error wire shape.
|
|
1252
|
+
...(respiraErrorEnvelope ?? {}),
|
|
1229
1253
|
site: activeSite,
|
|
1230
1254
|
stack,
|
|
1231
1255
|
// v6.17: every error response carries the report path so the
|
|
@@ -1356,19 +1380,19 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1356
1380
|
name: 'wordpress_invoke_ability',
|
|
1357
1381
|
description: 'Invoke an inhaled WordPress Abilities API ability through the Respira safety wrapper. ' +
|
|
1358
1382
|
'The wrapper snapshots the target post if the call looks like a write, runs the underlying ability, logs the call to the audit trail, and returns a structured envelope with a rollback URL when a snapshot was taken. ' +
|
|
1359
|
-
'The ability must (a) be registered on the site via wp_register_ability, AND (b) be inhaled (the admin toggled it on at Respira > MCP Abilities). Otherwise the call returns a structured 403/404 with instructions
|
|
1360
|
-
'Use this to call any Yoast / Elementor / WooCommerce / ACF / Jetpack ability — anything the site has inhaled — with Respira\'s snapshot-before-write protection layered on top. ' +
|
|
1361
|
-
'Pass ability="vendor/ability-name" and args={…}.
|
|
1383
|
+
'The ability must (a) be registered on the site via wp_register_ability, AND (b) be inhaled (the admin toggled it on at Respira > MCP Abilities). Exception: abilities under the `respira-playbooks/` namespace bypass the inhale opt-in gate because the customer\'s own agent authored them on-site via respira_create_playbook (which already ran the destructive-tool refusal validator). Otherwise the call returns a structured 403/404 with instructions and a sample of the actually-registered ability names so the agent can self-correct. ' +
|
|
1384
|
+
'Use this to call any Yoast / Elementor / WooCommerce / ACF / Jetpack ability — anything the site has inhaled — plus any Playbook the agent authored, with Respira\'s snapshot-before-write protection layered on top. ' +
|
|
1385
|
+
'Pass ability="vendor/ability-name" and args={…}. IMPORTANT: the wire field is `args` here, NOT `input`. Playbook authors who reference `{{input.x}}` in their step templates are referring to the args object — Respira binds the incoming args under the name `input` inside the playbook executor. So `wordpress_invoke_ability({ability:"respira-playbooks/case-study-create", args:{client_name:"AcmeBank"}})` populates `{{input.client_name}}` inside the playbook steps.',
|
|
1362
1386
|
inputSchema: {
|
|
1363
1387
|
type: 'object',
|
|
1364
1388
|
properties: {
|
|
1365
1389
|
ability: {
|
|
1366
1390
|
type: 'string',
|
|
1367
|
-
description: 'Full ability name as registered, e.g. "yoast-seo/analyze-post"
|
|
1391
|
+
description: 'Full ability name as registered, e.g. "yoast-seo/analyze-post", "elementor/get-widget", or "respira-playbooks/case-study-create".',
|
|
1368
1392
|
},
|
|
1369
1393
|
args: {
|
|
1370
1394
|
type: 'object',
|
|
1371
|
-
description: 'Arguments to forward to the ability\'s execute handler. Shape varies per ability.',
|
|
1395
|
+
description: 'Arguments to forward to the ability\'s execute handler. Shape varies per ability — for a playbook, the args become `{{input.x}}` references inside the playbook steps. Always send as `args`, never as `input`.',
|
|
1372
1396
|
},
|
|
1373
1397
|
},
|
|
1374
1398
|
required: ['ability'],
|
|
@@ -1947,6 +1971,24 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1947
1971
|
},
|
|
1948
1972
|
idempotentHint: true,
|
|
1949
1973
|
},
|
|
1974
|
+
{
|
|
1975
|
+
name: 'wordpress_make_responsive',
|
|
1976
|
+
description: 'Make a desktop-only page mobile-correct in one call. Auto-detects the page builder, then generates tablet + mobile breakpoint overrides deterministically: scales font sizes (and injects a fluid clamp() for headings where the builder supports raw CSS), scales padding/margin with a sensible mobile floor, scales gaps, collapses multi-column grids toward 1 column on mobile, and switches horizontal flex layouts to vertical on mobile when they hold several children. Idempotent and non-destructive — it NEVER overwrites a responsive value you already set, so re-running is a no-op and hand-tuned breakpoints are preserved. Build the desktop layout first, then call this. Supported today: Bricks (full, incl. fluid clamp) and Elementor v3 (breakpoint overrides). Pass dry_run=true to preview the exact change list without writing. A snapshot is taken before any write, so the change is one-click reversible.',
|
|
1977
|
+
inputSchema: {
|
|
1978
|
+
type: 'object',
|
|
1979
|
+
properties: {
|
|
1980
|
+
page_id: {
|
|
1981
|
+
type: 'number',
|
|
1982
|
+
description: 'Page or post ID to make responsive.',
|
|
1983
|
+
},
|
|
1984
|
+
dry_run: {
|
|
1985
|
+
type: 'boolean',
|
|
1986
|
+
description: 'If true, returns the list of changes that would be applied without writing them (default: false).',
|
|
1987
|
+
},
|
|
1988
|
+
},
|
|
1989
|
+
required: ['page_id'],
|
|
1990
|
+
},
|
|
1991
|
+
},
|
|
1950
1992
|
{
|
|
1951
1993
|
name: 'wordpress_update_module',
|
|
1952
1994
|
description: 'Update a specific module within a page builder page. Supports finding modules by admin_label, path, or type. Only updates the specified module while preserving all other content.',
|
|
@@ -3087,6 +3129,249 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3087
3129
|
},
|
|
3088
3130
|
readOnlyHint: true,
|
|
3089
3131
|
},
|
|
3132
|
+
// Custom Structures (v7.1) — agent-creatable CPTs, taxonomies, ACF field groups.
|
|
3133
|
+
// Stored in wp_options on the WP side, registered against core via register_post_type
|
|
3134
|
+
// / register_taxonomy / acf_add_local_field_group at init. Slug allowlist + reserved-slug
|
|
3135
|
+
// protection on the WP side; capability check is `manage_options`. Deletions don't drop
|
|
3136
|
+
// existing posts/terms — they orphan them in the DB. Delete those individually first if
|
|
3137
|
+
// you want them gone permanently.
|
|
3138
|
+
{
|
|
3139
|
+
name: 'wordpress_create_post_type',
|
|
3140
|
+
description: "Create a new Custom Post Type that the site will register on every request. Stored in wp_option respira_custom_post_types and registered via register_post_type() on init. Slug rules: lowercase letters/digits/underscores, must start with a letter, 1-20 chars, not a reserved WP/WooCommerce slug. Use case: agent provisions structure for a new content type (case studies, reviews, products) without writing PHP. After this call the type appears in wp-admin's left-nav and is queryable via wordpress_list_custom_posts and the existing post CRUD.",
|
|
3141
|
+
inputSchema: {
|
|
3142
|
+
type: 'object',
|
|
3143
|
+
properties: {
|
|
3144
|
+
slug: { type: 'string', description: 'Slug (1-20 chars, lowercase, letters/digits/underscore, must start with letter). Example: "case_study".' },
|
|
3145
|
+
label: { type: 'string', description: 'Plural label, shown in wp-admin menus. Example: "Case Studies".' },
|
|
3146
|
+
singular_label: { type: 'string', description: 'Singular label. Defaults to label with trailing "s" stripped. Example: "Case Study".' },
|
|
3147
|
+
description: { type: 'string', description: 'Short description shown in wp-admin Tools UI.' },
|
|
3148
|
+
public: { type: 'boolean', description: 'Whether the post type is queryable from the front-end. Default true.' },
|
|
3149
|
+
show_in_rest: { type: 'boolean', description: 'Whether to expose via the WP REST API + Gutenberg editor. Default true.' },
|
|
3150
|
+
hierarchical: { type: 'boolean', description: 'true = page-like (parent/child). false = post-like (flat). Default false.' },
|
|
3151
|
+
menu_icon: { type: 'string', description: 'Dashicons class (e.g. "dashicons-portfolio") or a data URI. Default "dashicons-admin-post".' },
|
|
3152
|
+
supports: { type: 'array', items: { type: 'string' }, description: 'Capabilities the type supports. Common: title, editor, thumbnail, excerpt, custom-fields, author, comments, revisions. Default: title, editor, thumbnail, custom-fields.' },
|
|
3153
|
+
taxonomies: { type: 'array', items: { type: 'string' }, description: 'Existing taxonomy slugs to attach. Pass an empty array if none. To create a new taxonomy + attach it, call wordpress_create_taxonomy with post_types: [<this slug>] separately.' },
|
|
3154
|
+
},
|
|
3155
|
+
required: ['slug', 'label'],
|
|
3156
|
+
},
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
name: 'wordpress_update_post_type',
|
|
3160
|
+
description: 'Update a Respira-owned Custom Post Type. Only works on types created via wordpress_create_post_type (the option store remembers ownership). Use cases: change label, toggle public/hierarchical, add/remove taxonomy attachments. Slug is immutable — delete + recreate if you need a different slug.',
|
|
3161
|
+
inputSchema: {
|
|
3162
|
+
type: 'object',
|
|
3163
|
+
properties: {
|
|
3164
|
+
slug: { type: 'string', description: 'Slug of the post type to update.' },
|
|
3165
|
+
label: { type: 'string' },
|
|
3166
|
+
singular_label: { type: 'string' },
|
|
3167
|
+
description: { type: 'string' },
|
|
3168
|
+
public: { type: 'boolean' },
|
|
3169
|
+
show_in_rest: { type: 'boolean' },
|
|
3170
|
+
hierarchical: { type: 'boolean' },
|
|
3171
|
+
menu_icon: { type: 'string' },
|
|
3172
|
+
supports: { type: 'array', items: { type: 'string' } },
|
|
3173
|
+
taxonomies: { type: 'array', items: { type: 'string' } },
|
|
3174
|
+
},
|
|
3175
|
+
required: ['slug'],
|
|
3176
|
+
},
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
name: 'wordpress_delete_post_type',
|
|
3180
|
+
description: "Delete a Respira-owned Custom Post Type. Posts of this type are NOT deleted — they stay in the database as orphans. The response includes orphaned_post_count so the agent knows how many posts to clean up via wordpress_delete_post (each requires its own approval round-trip). Refuses on slugs Respira didn't create.",
|
|
3181
|
+
inputSchema: {
|
|
3182
|
+
type: 'object',
|
|
3183
|
+
properties: {
|
|
3184
|
+
slug: { type: 'string', description: 'Slug of the post type to delete.' },
|
|
3185
|
+
},
|
|
3186
|
+
required: ['slug'],
|
|
3187
|
+
},
|
|
3188
|
+
destructiveHint: true,
|
|
3189
|
+
},
|
|
3190
|
+
{
|
|
3191
|
+
name: 'wordpress_list_custom_post_types',
|
|
3192
|
+
description: "List every Custom Post Type Respira created via wordpress_create_post_type. Different from wordpress_list_post_types — that one enumerates EVERY registered post type (core, theme, third-party, Respira-created). This one shows only Respira-owned ones, with their full definition + creation timestamp.",
|
|
3193
|
+
inputSchema: { type: 'object', properties: {} },
|
|
3194
|
+
readOnlyHint: true,
|
|
3195
|
+
},
|
|
3196
|
+
{
|
|
3197
|
+
name: 'wordpress_create_taxonomy',
|
|
3198
|
+
description: "Create a new taxonomy registered against WordPress core via register_taxonomy() on init. Stored in wp_option respira_custom_taxonomies. Slug rules same as CPT (lowercase, letters/digits/underscore, 1-20 chars, must start with letter). Attach to one or more post types via the post_types array. After creation, wordpress_create_term lands new terms inside the taxonomy.",
|
|
3199
|
+
inputSchema: {
|
|
3200
|
+
type: 'object',
|
|
3201
|
+
properties: {
|
|
3202
|
+
slug: { type: 'string', description: 'Slug. Example: "industry".' },
|
|
3203
|
+
label: { type: 'string', description: 'Plural label. Example: "Industries".' },
|
|
3204
|
+
singular_label: { type: 'string', description: 'Singular label. Default: label with trailing "s" stripped.' },
|
|
3205
|
+
description: { type: 'string' },
|
|
3206
|
+
post_types: { type: 'array', items: { type: 'string' }, description: 'Post type slugs to attach this taxonomy to. Example: ["case_study", "page"].' },
|
|
3207
|
+
public: { type: 'boolean', description: 'Default true.' },
|
|
3208
|
+
show_in_rest: { type: 'boolean', description: 'Default true.' },
|
|
3209
|
+
hierarchical: { type: 'boolean', description: 'true = category-like (parent/child terms). false = tag-like (flat). Default false.' },
|
|
3210
|
+
},
|
|
3211
|
+
required: ['slug', 'label'],
|
|
3212
|
+
},
|
|
3213
|
+
},
|
|
3214
|
+
{
|
|
3215
|
+
name: 'wordpress_update_taxonomy',
|
|
3216
|
+
description: 'Update a Respira-owned taxonomy. Slug is immutable. Use this to add/remove post type attachments or change labels.',
|
|
3217
|
+
inputSchema: {
|
|
3218
|
+
type: 'object',
|
|
3219
|
+
properties: {
|
|
3220
|
+
slug: { type: 'string' },
|
|
3221
|
+
label: { type: 'string' },
|
|
3222
|
+
singular_label: { type: 'string' },
|
|
3223
|
+
description: { type: 'string' },
|
|
3224
|
+
post_types: { type: 'array', items: { type: 'string' } },
|
|
3225
|
+
public: { type: 'boolean' },
|
|
3226
|
+
show_in_rest: { type: 'boolean' },
|
|
3227
|
+
hierarchical: { type: 'boolean' },
|
|
3228
|
+
},
|
|
3229
|
+
required: ['slug'],
|
|
3230
|
+
},
|
|
3231
|
+
},
|
|
3232
|
+
{
|
|
3233
|
+
name: 'wordpress_delete_taxonomy',
|
|
3234
|
+
description: "Delete a Respira-owned taxonomy. Existing terms stay in the database (wp_terms + wp_term_taxonomy rows are preserved) but stop appearing until the taxonomy is re-registered. Response includes orphaned_term_count.",
|
|
3235
|
+
inputSchema: {
|
|
3236
|
+
type: 'object',
|
|
3237
|
+
properties: {
|
|
3238
|
+
slug: { type: 'string' },
|
|
3239
|
+
},
|
|
3240
|
+
required: ['slug'],
|
|
3241
|
+
},
|
|
3242
|
+
destructiveHint: true,
|
|
3243
|
+
},
|
|
3244
|
+
{
|
|
3245
|
+
name: 'wordpress_list_custom_taxonomies',
|
|
3246
|
+
description: 'List every taxonomy Respira created. Different from wordpress_list_taxonomies — that one enumerates every registered taxonomy site-wide.',
|
|
3247
|
+
inputSchema: { type: 'object', properties: {} },
|
|
3248
|
+
readOnlyHint: true,
|
|
3249
|
+
},
|
|
3250
|
+
{
|
|
3251
|
+
name: 'wordpress_create_acf_field_group',
|
|
3252
|
+
description: "Create an ACF (Advanced Custom Fields) field group. Requires ACF (free or Pro) active on the site — call wordpress_get_site_context first to confirm. Stored in wp_option respira_custom_acf_field_groups and registered via acf_add_local_field_group() on acf/init. Use to bind structured fields (text, number, image, post relation) to specific post types via the location rules.",
|
|
3253
|
+
inputSchema: {
|
|
3254
|
+
type: 'object',
|
|
3255
|
+
properties: {
|
|
3256
|
+
key: { type: 'string', description: 'ACF group key. Defaults to "group_<uuid>" if omitted. Must start with "group_".' },
|
|
3257
|
+
title: { type: 'string', description: 'Human-readable title shown in the post edit screen.' },
|
|
3258
|
+
fields: { type: 'array', description: 'Array of ACF field definitions. Each follows ACF\'s schema: { key, label, name, type, ... }. See ACF docs for type-specific properties.', items: { type: 'object' } },
|
|
3259
|
+
location: { type: 'array', description: 'ACF location rules nested array. Example: [[{param:"post_type", operator:"==", value:"case_study"}]] = attach to case_study post type.', items: { type: 'array', items: { type: 'object' } } },
|
|
3260
|
+
},
|
|
3261
|
+
required: ['title'],
|
|
3262
|
+
},
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
name: 'wordpress_update_acf_field_group',
|
|
3266
|
+
description: 'Update a Respira-owned ACF field group. Use to add/remove fields, change location rules, or rename the group.',
|
|
3267
|
+
inputSchema: {
|
|
3268
|
+
type: 'object',
|
|
3269
|
+
properties: {
|
|
3270
|
+
key: { type: 'string', description: 'ACF group key.' },
|
|
3271
|
+
title: { type: 'string' },
|
|
3272
|
+
fields: { type: 'array', items: { type: 'object' } },
|
|
3273
|
+
location: { type: 'array', items: { type: 'array', items: { type: 'object' } } },
|
|
3274
|
+
},
|
|
3275
|
+
required: ['key'],
|
|
3276
|
+
},
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
name: 'wordpress_delete_acf_field_group',
|
|
3280
|
+
description: "Delete a Respira-owned ACF field group. Existing field values on posts stay in postmeta but stop appearing in edit screens. Restore by recreating the group with the same key.",
|
|
3281
|
+
inputSchema: {
|
|
3282
|
+
type: 'object',
|
|
3283
|
+
properties: {
|
|
3284
|
+
key: { type: 'string' },
|
|
3285
|
+
},
|
|
3286
|
+
required: ['key'],
|
|
3287
|
+
},
|
|
3288
|
+
destructiveHint: true,
|
|
3289
|
+
},
|
|
3290
|
+
{
|
|
3291
|
+
name: 'wordpress_list_acf_field_groups',
|
|
3292
|
+
description: 'List every ACF field group Respira created. Requires ACF active. Refuses with respira_acf_not_active otherwise.',
|
|
3293
|
+
inputSchema: { type: 'object', properties: {} },
|
|
3294
|
+
readOnlyHint: true,
|
|
3295
|
+
},
|
|
3296
|
+
// Playbooks (v7.1) — JSON workflow compositions that register themselves as Abilities.
|
|
3297
|
+
// Once created, a playbook becomes callable via respira_invoke_ability under the
|
|
3298
|
+
// ability id "respira-playbooks/<id>". The playbook executor runs each step against
|
|
3299
|
+
// Respira's own REST handlers — same auth + audit + snapshot belt as a direct agent
|
|
3300
|
+
// call. Destructive tools are refused at create-time (see respira_playbook_destructive_tool_refused).
|
|
3301
|
+
{
|
|
3302
|
+
name: 'wordpress_create_playbook',
|
|
3303
|
+
description: "Author a Playbook — a typed JSON workflow that registers itself as a WordPress Ability and becomes a callable MCP tool. Use this to crystallize a repeatable workflow (\"create a case study\", \"publish a weekly digest\") so future invocations are one tool call instead of an instruction-by-instruction agent run. Steps run server-side; the agent sees a single typed result. Destructive tools (delete_*, restore_snapshot, apply_builder_patch) are refused at create-time — split the flow and have the agent call destructive ops directly. Use {{input.x}} and {{step_capture.field}} for templating between steps.",
|
|
3304
|
+
inputSchema: {
|
|
3305
|
+
type: 'object',
|
|
3306
|
+
properties: {
|
|
3307
|
+
id: { type: 'string', description: 'Stable id for this playbook. Lowercase, letters/digits/hyphens, 1-48 chars. Becomes part of the ability id (respira-playbooks/<id>). Example: "case-study-create".' },
|
|
3308
|
+
label: { type: 'string', description: 'Human-readable label shown in the wp-admin Playbooks screen and in tool list descriptions. Example: "Create Case Study".' },
|
|
3309
|
+
description: { type: 'string', description: 'One-line description shown to agents in the tool list. Example: "Creates a case study post with client name and industry."' },
|
|
3310
|
+
input_schema: { type: 'object', description: 'JSON schema for the playbook\'s input. Must be type:"object" with properties + required arrays. Validated by wp_register_ability on every invocation.' },
|
|
3311
|
+
steps: {
|
|
3312
|
+
type: 'array',
|
|
3313
|
+
description: 'Ordered list of steps. Each step is { tool, args, capture? }. tool = MCP tool name (must be in the executor allowlist; see DESTRUCTIVE_TOOLS for what is refused). args = literal values + mustache templates ({{input.x}}, {{capture_name.field}}). capture = optional name to bind this step\'s result under so later steps can reference it.',
|
|
3314
|
+
items: {
|
|
3315
|
+
type: 'object',
|
|
3316
|
+
properties: {
|
|
3317
|
+
tool: { type: 'string' },
|
|
3318
|
+
args: { type: 'object' },
|
|
3319
|
+
capture: { type: 'string', description: 'Optional. Bind this step\'s result under this name so later steps can reference it as {{<capture_name>.field}}.' },
|
|
3320
|
+
},
|
|
3321
|
+
required: ['tool', 'args'],
|
|
3322
|
+
},
|
|
3323
|
+
},
|
|
3324
|
+
returns: { type: 'object', description: 'Optional. Shape of the return payload. Templates resolved from input + captures. Example: { post_id: "{{post.id}}", url: "{{post.link}}" }.' },
|
|
3325
|
+
},
|
|
3326
|
+
required: ['id', 'label', 'input_schema', 'steps'],
|
|
3327
|
+
},
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
name: 'wordpress_list_playbooks',
|
|
3331
|
+
description: 'List every Playbook stored on this site, with id / ability_id / label / description / step count / invocation_count / last_invoked_at. Useful for "what playbooks are available" discovery before authoring a new one.',
|
|
3332
|
+
inputSchema: { type: 'object', properties: {} },
|
|
3333
|
+
readOnlyHint: true,
|
|
3334
|
+
},
|
|
3335
|
+
{
|
|
3336
|
+
name: 'wordpress_get_playbook',
|
|
3337
|
+
description: 'Get one Playbook\'s full definition (input_schema, steps, returns template, audit metadata). Use to read the source before updating or before invoking.',
|
|
3338
|
+
inputSchema: {
|
|
3339
|
+
type: 'object',
|
|
3340
|
+
properties: {
|
|
3341
|
+
id: { type: 'string', description: 'Playbook id (the value from wordpress_list_playbooks, not the ability_id).' },
|
|
3342
|
+
},
|
|
3343
|
+
required: ['id'],
|
|
3344
|
+
},
|
|
3345
|
+
readOnlyHint: true,
|
|
3346
|
+
},
|
|
3347
|
+
{
|
|
3348
|
+
name: 'wordpress_update_playbook',
|
|
3349
|
+
description: 'Modify an existing Playbook. Id is immutable — to change it, delete + recreate. Validation re-runs on the merged payload, so the same destructive-tool refusal applies to updates.',
|
|
3350
|
+
inputSchema: {
|
|
3351
|
+
type: 'object',
|
|
3352
|
+
properties: {
|
|
3353
|
+
id: { type: 'string', description: 'Playbook id to update.' },
|
|
3354
|
+
label: { type: 'string' },
|
|
3355
|
+
description: { type: 'string' },
|
|
3356
|
+
input_schema: { type: 'object' },
|
|
3357
|
+
steps: { type: 'array', items: { type: 'object' } },
|
|
3358
|
+
returns: { type: 'object' },
|
|
3359
|
+
},
|
|
3360
|
+
required: ['id'],
|
|
3361
|
+
},
|
|
3362
|
+
},
|
|
3363
|
+
{
|
|
3364
|
+
name: 'wordpress_delete_playbook',
|
|
3365
|
+
description: 'Delete a Playbook. The corresponding Ability drops out of the MCP catalog on the next handshake. Existing posts/data created by past invocations are NOT touched.',
|
|
3366
|
+
inputSchema: {
|
|
3367
|
+
type: 'object',
|
|
3368
|
+
properties: {
|
|
3369
|
+
id: { type: 'string', description: 'Playbook id to delete.' },
|
|
3370
|
+
},
|
|
3371
|
+
required: ['id'],
|
|
3372
|
+
},
|
|
3373
|
+
destructiveHint: true,
|
|
3374
|
+
},
|
|
3090
3375
|
{
|
|
3091
3376
|
name: 'wordpress_list_custom_posts',
|
|
3092
3377
|
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.',
|
|
@@ -3480,7 +3765,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3480
3765
|
},
|
|
3481
3766
|
{
|
|
3482
3767
|
name: 'wordpress_delete_media',
|
|
3483
|
-
description: 'Delete a media file.',
|
|
3768
|
+
description: 'Delete a media file. Approval-gated since v7.1.0-beta.1 — the first call returns `code: respira_approval_required` with an `approval_token`; pass that token in the second call to confirm. Same flow as `delete_page` / `delete_user` / `delete_plugin`.',
|
|
3484
3769
|
inputSchema: {
|
|
3485
3770
|
type: 'object',
|
|
3486
3771
|
properties: {
|
|
@@ -3488,6 +3773,14 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3488
3773
|
type: 'number',
|
|
3489
3774
|
description: 'Media ID',
|
|
3490
3775
|
},
|
|
3776
|
+
approval_token: {
|
|
3777
|
+
type: 'string',
|
|
3778
|
+
description: 'Approval token returned by the first call. Send the exact token verbatim on the second call to confirm the deletion. Tokens are single-use, tied to the {action, media_id} pair, and expire after 5 minutes.',
|
|
3779
|
+
},
|
|
3780
|
+
force: {
|
|
3781
|
+
type: 'boolean',
|
|
3782
|
+
description: 'When true, bypasses Trash and permanently deletes the file (and any sibling thumbnails). Defaults to false. The WP default is to move the attachment to Trash; pass force=true to remove the underlying file too.',
|
|
3783
|
+
},
|
|
3491
3784
|
},
|
|
3492
3785
|
required: ['id'],
|
|
3493
3786
|
},
|
|
@@ -4576,6 +4869,8 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4576
4869
|
...(await client.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace)),
|
|
4577
4870
|
respira_approvals_url: client.getApprovalsUrl(),
|
|
4578
4871
|
};
|
|
4872
|
+
case 'wordpress_make_responsive':
|
|
4873
|
+
return await client.makeResponsive(args.page_id, args.dry_run || false);
|
|
4579
4874
|
case 'wordpress_update_module':
|
|
4580
4875
|
return {
|
|
4581
4876
|
...(await client.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
|
|
@@ -4760,6 +5055,53 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4760
5055
|
return await client.listPostTypes();
|
|
4761
5056
|
case 'wordpress_get_post_type':
|
|
4762
5057
|
return await client.getPostType(args.type);
|
|
5058
|
+
// Custom Structures (v7.1) — agent-creatable CPTs, taxonomies, ACF field groups.
|
|
5059
|
+
// All routes are under /wp-json/respira/v1/custom-structures/* on the plugin side.
|
|
5060
|
+
// No dedicated wrappers on wordpress-client.ts — the generic callRestV1 forwards
|
|
5061
|
+
// straight to the plugin handler which has full validation + audit-logging.
|
|
5062
|
+
case 'wordpress_create_post_type':
|
|
5063
|
+
return await client.callRestV1('POST', '/custom-structures/post-types', args);
|
|
5064
|
+
case 'wordpress_update_post_type': {
|
|
5065
|
+
const { slug, ...rest } = args;
|
|
5066
|
+
return await client.callRestV1('PUT', `/custom-structures/post-types/${slug}`, rest);
|
|
5067
|
+
}
|
|
5068
|
+
case 'wordpress_delete_post_type':
|
|
5069
|
+
return await client.callRestV1('DELETE', `/custom-structures/post-types/${args.slug}`);
|
|
5070
|
+
case 'wordpress_list_custom_post_types':
|
|
5071
|
+
return await client.callRestV1('GET', '/custom-structures/post-types');
|
|
5072
|
+
case 'wordpress_create_taxonomy':
|
|
5073
|
+
return await client.callRestV1('POST', '/custom-structures/taxonomies', args);
|
|
5074
|
+
case 'wordpress_update_taxonomy': {
|
|
5075
|
+
const { slug, ...rest } = args;
|
|
5076
|
+
return await client.callRestV1('PUT', `/custom-structures/taxonomies/${slug}`, rest);
|
|
5077
|
+
}
|
|
5078
|
+
case 'wordpress_delete_taxonomy':
|
|
5079
|
+
return await client.callRestV1('DELETE', `/custom-structures/taxonomies/${args.slug}`);
|
|
5080
|
+
case 'wordpress_list_custom_taxonomies':
|
|
5081
|
+
return await client.callRestV1('GET', '/custom-structures/taxonomies');
|
|
5082
|
+
case 'wordpress_create_acf_field_group':
|
|
5083
|
+
return await client.callRestV1('POST', '/custom-structures/acf-field-groups', args);
|
|
5084
|
+
case 'wordpress_update_acf_field_group': {
|
|
5085
|
+
const { key, ...rest } = args;
|
|
5086
|
+
return await client.callRestV1('PUT', `/custom-structures/acf-field-groups/${key}`, rest);
|
|
5087
|
+
}
|
|
5088
|
+
case 'wordpress_delete_acf_field_group':
|
|
5089
|
+
return await client.callRestV1('DELETE', `/custom-structures/acf-field-groups/${args.key}`);
|
|
5090
|
+
case 'wordpress_list_acf_field_groups':
|
|
5091
|
+
return await client.callRestV1('GET', '/custom-structures/acf-field-groups');
|
|
5092
|
+
// Playbooks (v7.1)
|
|
5093
|
+
case 'wordpress_create_playbook':
|
|
5094
|
+
return await client.callRestV1('POST', '/playbooks', args);
|
|
5095
|
+
case 'wordpress_list_playbooks':
|
|
5096
|
+
return await client.callRestV1('GET', '/playbooks');
|
|
5097
|
+
case 'wordpress_get_playbook':
|
|
5098
|
+
return await client.callRestV1('GET', `/playbooks/${args.id}`);
|
|
5099
|
+
case 'wordpress_update_playbook': {
|
|
5100
|
+
const { id, ...rest } = args;
|
|
5101
|
+
return await client.callRestV1('PUT', `/playbooks/${id}`, rest);
|
|
5102
|
+
}
|
|
5103
|
+
case 'wordpress_delete_playbook':
|
|
5104
|
+
return await client.callRestV1('DELETE', `/playbooks/${args.id}`);
|
|
4763
5105
|
case 'wordpress_list_custom_posts':
|
|
4764
5106
|
return await client.listCustomPosts(args.type, args);
|
|
4765
5107
|
case 'wordpress_get_custom_post':
|