drupal-mcp-connector 2.7.4 → 2.8.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.
Files changed (141) hide show
  1. package/{.claude → .agents}/commands/drupal-audit-config-best-practices.md +3 -4
  2. package/{.claude → .agents}/commands/drupal-audit-site-health.md +3 -4
  3. package/{.claude → .agents}/commands/drupal-block-user.md +3 -4
  4. package/{.claude → .agents}/commands/drupal-bulk-create.md +3 -4
  5. package/{.claude → .agents}/commands/drupal-bulk-update.md +3 -4
  6. package/{.claude → .agents}/commands/drupal-config-get.md +3 -4
  7. package/{.claude → .agents}/commands/drupal-config-list.md +3 -4
  8. package/{.claude → .agents}/commands/drupal-config-set.md +3 -4
  9. package/{.claude → .agents}/commands/drupal-content-by-moderation-state.md +3 -4
  10. package/{.claude → .agents}/commands/drupal-create-block.md +3 -4
  11. package/{.claude → .agents}/commands/drupal-create-media.md +3 -4
  12. package/{.claude → .agents}/commands/drupal-create-menu-link.md +3 -4
  13. package/{.claude → .agents}/commands/drupal-create-node.md +6 -7
  14. package/{.claude → .agents}/commands/drupal-create-paragraph.md +3 -4
  15. package/{.claude → .agents}/commands/drupal-create-redirect.md +3 -4
  16. package/{.claude → .agents}/commands/drupal-create-taxonomy-term.md +3 -4
  17. package/{.claude → .agents}/commands/drupal-create-translation.md +3 -4
  18. package/{.claude → .agents}/commands/drupal-create-user.md +3 -4
  19. package/{.claude → .agents}/commands/drupal-delete-media.md +3 -4
  20. package/{.claude → .agents}/commands/drupal-delete-node.md +3 -4
  21. package/{.claude → .agents}/commands/drupal-delete-taxonomy-term.md +3 -4
  22. package/{.claude → .agents}/commands/drupal-describe-fields.md +3 -4
  23. package/{.claude → .agents}/commands/drupal-drush-cache-rebuild.md +3 -4
  24. package/{.claude → .agents}/commands/drupal-drush-config-export.md +3 -4
  25. package/{.claude → .agents}/commands/drupal-drush-config-import.md +3 -4
  26. package/{.claude → .agents}/commands/drupal-drush-config-status.md +3 -4
  27. package/{.claude → .agents}/commands/drupal-drush-cron.md +3 -4
  28. package/{.claude → .agents}/commands/drupal-drush-module-disable.md +3 -4
  29. package/{.claude → .agents}/commands/drupal-drush-module-enable.md +3 -4
  30. package/{.claude → .agents}/commands/drupal-drush-module-list.md +3 -4
  31. package/{.claude → .agents}/commands/drupal-drush-security-updates.md +3 -4
  32. package/{.claude → .agents}/commands/drupal-drush-sql-query.md +3 -4
  33. package/{.claude → .agents}/commands/drupal-drush-status.md +3 -4
  34. package/{.claude → .agents}/commands/drupal-drush-updatedb.md +3 -4
  35. package/{.claude → .agents}/commands/drupal-drush-user-create.md +3 -4
  36. package/{.claude → .agents}/commands/drupal-drush-user-list.md +3 -4
  37. package/{.claude → .agents}/commands/drupal-drush-watchdog.md +3 -4
  38. package/{.claude → .agents}/commands/drupal-entity-create.md +3 -4
  39. package/{.claude → .agents}/commands/drupal-entity-delete.md +3 -4
  40. package/{.claude → .agents}/commands/drupal-entity-get.md +3 -4
  41. package/{.claude → .agents}/commands/drupal-entity-list.md +3 -4
  42. package/{.claude → .agents}/commands/drupal-entity-update.md +6 -7
  43. package/{.claude → .agents}/commands/drupal-find-orphaned-media.md +3 -4
  44. package/{.claude → .agents}/commands/drupal-get-entity-schema.md +3 -4
  45. package/{.claude → .agents}/commands/drupal-get-media.md +3 -4
  46. package/{.claude → .agents}/commands/drupal-get-node.md +3 -4
  47. package/{.claude → .agents}/commands/drupal-get-paragraph.md +3 -4
  48. package/{.claude → .agents}/commands/drupal-get-revision.md +3 -4
  49. package/{.claude → .agents}/commands/drupal-get-taxonomy-term.md +3 -4
  50. package/{.claude → .agents}/commands/drupal-get-taxonomy-terms.md +3 -4
  51. package/{.claude → .agents}/commands/drupal-get-user-by-name.md +3 -4
  52. package/{.claude → .agents}/commands/drupal-get-user.md +3 -4
  53. package/{.claude → .agents}/commands/drupal-governance-status.md +3 -4
  54. package/{.claude → .agents}/commands/drupal-graphql-introspect.md +3 -4
  55. package/{.claude → .agents}/commands/drupal-graphql.md +3 -4
  56. package/{.claude → .agents}/commands/drupal-list-blocks.md +3 -4
  57. package/{.claude → .agents}/commands/drupal-list-content-types.md +3 -4
  58. package/{.claude → .agents}/commands/drupal-list-entity-types.md +3 -4
  59. package/{.claude → .agents}/commands/drupal-list-media-types.md +3 -4
  60. package/{.claude → .agents}/commands/drupal-list-media.md +3 -4
  61. package/{.claude → .agents}/commands/drupal-list-menu-links.md +3 -4
  62. package/{.claude → .agents}/commands/drupal-list-moderation-states.md +3 -4
  63. package/{.claude → .agents}/commands/drupal-list-nodes.md +3 -4
  64. package/{.claude → .agents}/commands/drupal-list-revisions.md +3 -4
  65. package/{.claude → .agents}/commands/drupal-list-roles.md +3 -4
  66. package/{.claude → .agents}/commands/drupal-list-sites.md +2 -3
  67. package/{.claude → .agents}/commands/drupal-list-translations.md +3 -4
  68. package/{.claude → .agents}/commands/drupal-list-users.md +3 -4
  69. package/{.claude → .agents}/commands/drupal-list-vocabularies.md +3 -4
  70. package/{.claude → .agents}/commands/drupal-mcp-whoami.md +3 -4
  71. package/{.claude → .agents}/commands/drupal-report-404-log.md +3 -4
  72. package/{.claude → .agents}/commands/drupal-report-accessibility-audit.md +3 -4
  73. package/{.claude → .agents}/commands/drupal-report-alias-coverage.md +3 -4
  74. package/{.claude → .agents}/commands/drupal-report-broken-embeds.md +3 -4
  75. package/{.claude → .agents}/commands/drupal-report-broken-links.md +3 -4
  76. package/{.claude → .agents}/commands/drupal-report-cache-config.md +3 -4
  77. package/{.claude → .agents}/commands/drupal-report-config-drift.md +3 -4
  78. package/{.claude → .agents}/commands/drupal-report-content-by-author.md +3 -4
  79. package/{.claude → .agents}/commands/drupal-report-content-summary.md +3 -4
  80. package/{.claude → .agents}/commands/drupal-report-duplicate-content.md +3 -4
  81. package/{.claude → .agents}/commands/drupal-report-field-completeness.md +3 -4
  82. package/{.claude → .agents}/commands/drupal-report-menu-integrity.md +3 -4
  83. package/{.claude → .agents}/commands/drupal-report-missing-field.md +3 -4
  84. package/{.claude → .agents}/commands/drupal-report-module-audit.md +3 -4
  85. package/{.claude → .agents}/commands/drupal-report-orphan-pages.md +3 -4
  86. package/{.claude → .agents}/commands/drupal-report-orphaned-references.md +3 -4
  87. package/{.claude → .agents}/commands/drupal-report-permission-audit.md +3 -4
  88. package/{.claude → .agents}/commands/drupal-report-pii-exposure.md +3 -4
  89. package/{.claude → .agents}/commands/drupal-report-readability.md +3 -4
  90. package/{.claude → .agents}/commands/drupal-report-recently-published.md +3 -4
  91. package/{.claude → .agents}/commands/drupal-report-redirect-health.md +3 -4
  92. package/{.claude → .agents}/commands/drupal-report-revision-hotspots.md +3 -4
  93. package/{.claude → .agents}/commands/drupal-report-scheduled-content.md +3 -4
  94. package/{.claude → .agents}/commands/drupal-report-seo-audit.md +3 -4
  95. package/{.claude → .agents}/commands/drupal-report-seo-meta-coverage.md +3 -4
  96. package/{.claude → .agents}/commands/drupal-report-stale-content.md +3 -4
  97. package/{.claude → .agents}/commands/drupal-report-status-report.md +3 -4
  98. package/{.claude → .agents}/commands/drupal-report-taxonomy-usage.md +3 -4
  99. package/{.claude → .agents}/commands/drupal-report-text-format-audit.md +3 -4
  100. package/{.claude → .agents}/commands/drupal-report-translation-coverage.md +3 -4
  101. package/{.claude → .agents}/commands/drupal-report-unpublished.md +3 -4
  102. package/{.claude → .agents}/commands/drupal-report-user-activity.md +3 -4
  103. package/{.claude → .agents}/commands/drupal-report-workflow-bottlenecks.md +3 -4
  104. package/{.claude → .agents}/commands/drupal-resolve-reference.md +3 -4
  105. package/{.claude → .agents}/commands/drupal-revert-revision.md +3 -4
  106. package/{.claude → .agents}/commands/drupal-schedule-publish.md +3 -4
  107. package/{.claude → .agents}/commands/drupal-search-content.md +3 -4
  108. package/{.claude → .agents}/commands/drupal-search.md +3 -4
  109. package/{.claude → .agents}/commands/drupal-security-info.md +3 -4
  110. package/{.claude → .agents}/commands/drupal-set-moderation-state.md +3 -4
  111. package/{.claude → .agents}/commands/drupal-site-info.md +3 -4
  112. package/{.claude → .agents}/commands/drupal-update-media.md +3 -4
  113. package/{.claude → .agents}/commands/drupal-update-menu-link.md +3 -4
  114. package/{.claude → .agents}/commands/drupal-update-node.md +9 -10
  115. package/{.claude → .agents}/commands/drupal-update-paragraph.md +3 -4
  116. package/{.claude → .agents}/commands/drupal-update-redirect.md +3 -4
  117. package/{.claude → .agents}/commands/drupal-update-taxonomy-term.md +3 -4
  118. package/{.claude → .agents}/commands/drupal-update-user.md +3 -4
  119. package/{.claude → .agents}/commands/drupal-upload-file-and-create-media.md +3 -4
  120. package/{.claude → .agents}/commands/drupal-upload-file.md +3 -4
  121. package/CHANGELOG.md +56 -0
  122. package/README.md +21 -11
  123. package/config/config.example.json +1 -1
  124. package/package.json +4 -2
  125. package/scripts/generate-commands.js +42 -21
  126. package/scripts/install-commands.js +157 -0
  127. package/src/lib/backends/backend-interface.js +20 -1
  128. package/src/lib/backends/jsonapi.js +47 -4
  129. package/src/lib/body-summary.js +60 -0
  130. package/src/lib/data-flow.js +437 -0
  131. package/src/lib/dispatch.js +36 -15
  132. package/src/lib/drupal-fetch.js +52 -6
  133. package/src/lib/field-definition.js +285 -0
  134. package/src/lib/patch-preflight.js +157 -66
  135. package/src/lib/security.js +14 -0
  136. package/src/lib/server-tools.js +9 -0
  137. package/src/lib/write-revision.js +82 -2
  138. package/src/tools/bulk.js +4 -3
  139. package/src/tools/entities.js +11 -7
  140. package/src/tools/index.js +3 -2
  141. package/src/tools/nodes.js +55 -43
@@ -1,18 +1,17 @@
1
1
  ---
2
2
  description: "User activity summary: active vs blocked accounts, never-logged-in users, and users inactive beyond a threshold. Useful for security audits and account hygiene."
3
3
  argument-hint: "[site] [inactiveDays] [limit]"
4
- allowed-tools: mcp__drupal__drupal_report_user_activity
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_report_user_activity` MCP tool.
6
+ Call the MCP tool `drupal_report_user_activity`.
8
7
 
9
8
  User activity summary: active vs blocked accounts, never-logged-in users, and users inactive beyond a threshold. Useful for security audits and account hygiene.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Optional:**
14
13
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
15
14
  - `inactiveDays` (number): Days without login to flag as inactive
16
15
  - `limit` (number)
17
16
 
18
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
17
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Find content stuck in a non-published moderation state (draft/needs_review) beyond N days — editorial bottlenecks. Reads moderation_state; gated when content_moderation isn't exposed."
3
3
  argument-hint: "[site] [type] [days] [states] [sampleSize]"
4
- allowed-tools: mcp__drupal__drupal_report_workflow_bottlenecks
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_report_workflow_bottlenecks` MCP tool.
6
+ Call the MCP tool `drupal_report_workflow_bottlenecks`.
8
7
 
9
8
  Find content stuck in a non-published moderation state (draft/needs_review) beyond N days — editorial bottlenecks. Reads moderation_state; gated when content_moderation isn't exposed.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Optional:**
14
13
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
@@ -17,4 +16,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
17
16
  - `states` (array (pass as JSON)): Moderation states to treat as bottlenecks
18
17
  - `sampleSize` (number)
19
18
 
20
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
19
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Resolve a human name or title to a Drupal entity UUID. Use this before creating or updating an entity reference when you only know the label (e.g. a taxonomy term name, a user name, or a node title) and not its UUID. Read-only: returns the best match { id, title } plus any ambiguous candidates. Filters on 'title' for nodes and 'name' for taxonomy_term / user."
3
3
  argument-hint: "<entityType> <bundle> <name> [site] [limit]"
4
- allowed-tools: mcp__drupal__drupal_resolve_reference
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_resolve_reference` MCP tool.
6
+ Call the MCP tool `drupal_resolve_reference`.
8
7
 
9
8
  Resolve a human name or title to a Drupal entity UUID. Use this before creating or updating an entity reference when you only know the label (e.g. a taxonomy term name, a user name, or a node title) and not its UUID. Read-only: returns the best match { id, title } plus any ambiguous candidates. Filters on 'title' for nodes and 'name' for taxonomy_term / user.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `entityType` (string): Entity type machine name, e.g. 'node', 'taxonomy_term', 'user'
@@ -19,4 +18,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
19
18
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
20
19
  - `limit` (number): Maximum candidates to consider
21
20
 
22
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
21
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Revert a content node to a prior revision (GOVERNED WRITE). Reads the target revision and replays its editable content as a NEW current revision (history is preserved, nothing is deleted). Internal ids, revision metadata, and computed paths are not written back. Subject to the site's write security policy. Confirm with the user before calling."
3
3
  argument-hint: "<type> <id> <version> [site]"
4
- allowed-tools: mcp__drupal__drupal_revert_revision
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_revert_revision` MCP tool.
6
+ Call the MCP tool `drupal_revert_revision`.
8
7
 
9
8
  Revert a content node to a prior revision (GOVERNED WRITE). Reads the target revision and replays its editable content as a NEW current revision (history is preserved, nothing is deleted). Internal ids, revision metadata, and computed paths are not written back. Subject to the site's write security policy. Confirm with the user before calling.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `type` (string): Content type machine name
@@ -18,4 +17,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
18
17
  **Optional:**
19
18
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
20
19
 
21
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
20
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Schedule a content node to publish and/or unpublish at a future time using the Drupal Scheduler module. Sets the publish_on and unpublish_on fields on the node. Requires the Scheduler module to be installed and enabled for the content type, with the publish_on / unpublish_on fields present on the bundle — otherwise the call fails with a clear capability error. Timestamps accept ISO 8601 (e.g. '2026-07-01T12:00:00Z') or a Unix epoch and are passed through unchanged. Provide at least one of publishOn or unpublishOn."
3
3
  argument-hint: "<type> <id> [site] [publishOn] [unpublishOn]"
4
- allowed-tools: mcp__drupal__drupal_schedule_publish
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_schedule_publish` MCP tool.
6
+ Call the MCP tool `drupal_schedule_publish`.
8
7
 
9
8
  Schedule a content node to publish and/or unpublish at a future time using the Drupal Scheduler module. Sets the publish_on and unpublish_on fields on the node. Requires the Scheduler module to be installed and enabled for the content type, with the publish_on / unpublish_on fields present on the bundle — otherwise the call fails with a clear capability error. Timestamps accept ISO 8601 (e.g. '2026-07-01T12:00:00Z') or a Unix epoch and are passed through unchanged. Provide at least one of publishOn or unpublishOn.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `type` (string): Content type machine name, e.g. 'article'
@@ -19,4 +18,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
19
18
  - `publishOn` (string): When to publish — ISO 8601 datetime or Unix epoch. Sets the Scheduler publish_on field.
20
19
  - `unpublishOn` (string): When to unpublish — ISO 8601 datetime or Unix epoch. Sets the Scheduler unpublish_on field.
21
20
 
22
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
21
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Search nodes by title substring. Returns title, path alias, and body summary."
3
3
  argument-hint: "<query> [site] [type] [status] [limit]"
4
- allowed-tools: mcp__drupal__drupal_search_content
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_search_content` MCP tool.
6
+ Call the MCP tool `drupal_search_content`.
8
7
 
9
8
  Search nodes by title substring. Returns title, path alias, and body summary.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `query` (string): Search term to match against node titles
@@ -19,4 +18,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
19
18
  - `status` (boolean (true/false)): Filter by publish status
20
19
  - `limit` (number)
21
20
 
22
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
21
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Search content by a query string. Best-effort title match over a content type (mode:'fallback'); relevance-ranked search requires a Search API/Solr endpoint."
3
3
  argument-hint: "<query> [site] [type] [limit]"
4
- allowed-tools: mcp__drupal__drupal_search
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_search` MCP tool.
6
+ Call the MCP tool `drupal_search`.
8
7
 
9
8
  Search content by a query string. Best-effort title match over a content type (mode:'fallback'); relevance-ranked search requires a Search API/Solr endpoint.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `query` (string): Search term
@@ -18,4 +17,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
18
17
  - `type` (string): Content type machine name (default: article)
19
18
  - `limit` (number)
20
19
 
21
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
20
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  description: "Show the active security configuration for a site — what's allowed, what's blocked, what fields are redacted. Run this to understand the current access policy."
3
3
  argument-hint: "[site]"
4
- allowed-tools: mcp__drupal__drupal_security_info
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_security_info` MCP tool.
6
+ Call the MCP tool `drupal_security_info`.
8
7
 
9
8
  Show the active security configuration for a site — what's allowed, what's blocked, what fields are redacted. Run this to understand the current access policy.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Optional:**
14
13
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
15
14
 
16
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
15
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Transition a content node to a moderation state (content_moderation), e.g. 'draft', 'needs_review', 'published', 'archived'. Governed write."
3
3
  argument-hint: "<type> <id> <state> [site]"
4
- allowed-tools: mcp__drupal__drupal_set_moderation_state
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_set_moderation_state` MCP tool.
6
+ Call the MCP tool `drupal_set_moderation_state`.
8
7
 
9
8
  Transition a content node to a moderation state (content_moderation), e.g. 'draft', 'needs_review', 'published', 'archived'. Governed write.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `type` (string): Content type machine name
@@ -18,4 +17,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
18
17
  **Optional:**
19
18
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
20
19
 
21
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
20
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  description: "Get the base URL and the list of available resource/query types for a configured site (works for JSON:API and GraphQL backends)."
3
3
  argument-hint: "[site]"
4
- allowed-tools: mcp__drupal__drupal_site_info
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_site_info` MCP tool.
6
+ Call the MCP tool `drupal_site_info`.
8
7
 
9
8
  Get the base URL and the list of available resource/query types for a configured site (works for JSON:API and GraphQL backends).
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Optional:**
14
13
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
15
14
 
16
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
15
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update a media entity's name, status, or field values. Partial: omitted fields (status included) are left untouched."
3
3
  argument-hint: "<type> <id> [site] [name] [status] [fields]"
4
- allowed-tools: mcp__drupal__drupal_update_media
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_media` MCP tool.
6
+ Call the MCP tool `drupal_update_media`.
8
7
 
9
8
  Update a media entity's name, status, or field values. Partial: omitted fields (status included) are left untouched.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `type` (string)
@@ -20,4 +19,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
20
19
  - `status` (boolean (true/false)): Published flag. Only sent when provided; requires allowPublish when true.
21
20
  - `fields` (object (pass as JSON)): Field values. Entity-reference values in JSON:API linkage shape ({ data: { type, id } }) are sent as relationships automatically.
22
21
 
23
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
22
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update a custom menu link by UUID (rename, re-weight, re-target, re-parent, enable/disable). Only the fields you pass change. The link's enabled state is preserved across edits — an unrelated change will not disable a live link — unless you pass 'enabled' explicitly. Checked against the site security config."
3
3
  argument-hint: "<id> [site] [title] [link] [menu] [weight] [parent] [enabled]"
4
- allowed-tools: mcp__drupal__drupal_update_menu_link
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_menu_link` MCP tool.
6
+ Call the MCP tool `drupal_update_menu_link`.
8
7
 
9
8
  Update a custom menu link by UUID (rename, re-weight, re-target, re-parent, enable/disable). Only the fields you pass change. The link's enabled state is preserved across edits — an unrelated change will not disable a live link — unless you pass 'enabled' explicitly. Checked against the site security config.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `id` (string): Menu link UUID
@@ -22,4 +21,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
22
21
  - `parent` (string): New parent link plugin id (e.g. 'menu_link_content:<uuid>'), or '' for top level. Omit to leave unchanged.
23
22
  - `enabled` (boolean (true/false)): Enable/disable the link. Omit to preserve the current state.
24
23
 
25
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
24
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
- description: "Update an existing node. Only include fields you want to change. For moderated content types, use moderationState (e.g. 'published') rather than status. When the target is published and moderated and you omit moderationState, the connector defaults the write to moderation_state 'draft' (forward revision) so live default revisions are not mutated by accident. Entity-reference fields go in `relationships`, not `fields`. Paragraph / ERR identifiers are resolved to include meta.target_revision_id before PATCH; the write fails if any ref cannot be resolved (an unresolved identifier persists as an empty field). On moderated targets an id-mismatch PATCH preflight runs first — including on dryRun — so a core working-copy guard failure is reported before the real write and no revision is saved by the probe. workingCopy:null from drupal_list_revisions is not proof the node is writable (possiblyPatchBlocked / #201). Preflight here does not un-orphan paragraphs already created; probe the host before creating dependents."
2
+ description: "Update an existing node. Only include fields you want to change. For moderated content types, use moderationState (e.g. 'published') rather than status. When the target is published and moderated and you omit moderationState, the connector defaults the write to moderation_state 'draft' (forward revision) so live default revisions are not mutated by accident. Entity-reference fields go in `relationships`, not `fields`. Paragraph / ERR identifiers are resolved to include meta.target_revision_id before PATCH; the write fails if any ref cannot be resolved (an unresolved identifier persists as an empty field). On moderated targets an id-mismatch PATCH preflight runs first — including on dryRun — against the same URL the write will hit. An addressable working copy is PATCHed via ?resourceVersion=rel:working-copy (#166); dryRun uses that same target. workingCopy:null from drupal_list_revisions is not proof the node is writable (possiblyPatchBlocked / #201). Preflight here does not un-orphan paragraphs already created; probe the host before creating dependents."
3
3
  argument-hint: "<type> <id> [site] [title] [body] [summary] [format] [status] [moderationState] [fields] [relationships] [dryRun] [returning]"
4
- allowed-tools: mcp__drupal__drupal_update_node
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_node` MCP tool.
6
+ Call the MCP tool `drupal_update_node`.
8
7
 
9
- Update an existing node. Only include fields you want to change. For moderated content types, use moderationState (e.g. 'published') rather than status. When the target is published and moderated and you omit moderationState, the connector defaults the write to moderation_state 'draft' (forward revision) so live default revisions are not mutated by accident. Entity-reference fields go in `relationships`, not `fields`. Paragraph / ERR identifiers are resolved to include meta.target_revision_id before PATCH; the write fails if any ref cannot be resolved (an unresolved identifier persists as an empty field). On moderated targets an id-mismatch PATCH preflight runs first — including on dryRun — so a core working-copy guard failure is reported before the real write and no revision is saved by the probe. workingCopy:null from drupal_list_revisions is not proof the node is writable (possiblyPatchBlocked / #201). Preflight here does not un-orphan paragraphs already created; probe the host before creating dependents.
8
+ Update an existing node. Only include fields you want to change. For moderated content types, use moderationState (e.g. 'published') rather than status. When the target is published and moderated and you omit moderationState, the connector defaults the write to moderation_state 'draft' (forward revision) so live default revisions are not mutated by accident. Entity-reference fields go in `relationships`, not `fields`. Paragraph / ERR identifiers are resolved to include meta.target_revision_id before PATCH; the write fails if any ref cannot be resolved (an unresolved identifier persists as an empty field). On moderated targets an id-mismatch PATCH preflight runs first — including on dryRun — against the same URL the write will hit. An addressable working copy is PATCHed via ?resourceVersion=rel:working-copy (#166); dryRun uses that same target. workingCopy:null from drupal_list_revisions is not proof the node is writable (possiblyPatchBlocked / #201). Preflight here does not un-orphan paragraphs already created; probe the host before creating dependents.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `type` (string)
@@ -18,13 +17,13 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
18
17
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
19
18
  - `title` (string)
20
19
  - `body` (string)
21
- - `summary` (string): Body summary/teaser — writes the `summary` property of the body field (core `text_with_summary`). Many headless sites instead use a dedicated summary/deck field for teasers and meta descriptions; on those, set that field in `fields` a value written here will be stored but may never be rendered.
22
- - `format` (string): Text format machine name for the body, e.g. 'basic_html'. Defaults to the site config's `defaultTextFormat`, then 'full_html'. Set this when the site's formats do not include full_html, or to avoid writing content into a more permissive format than intended.
20
+ - `summary` (string): Body summary/teaser — writes body.summary on core text_with_summary only. Refused when the sampled body field has no summary property (text_long / text_formatted) or the schema cannot be determined. Prefer the site's dedicated deck/summary field via `fields`.
21
+ - `format` (string): Text format machine name for the body, e.g. 'basic_html'. When the body field's allowed_formats lists exactly one format, that is the default. A caller format outside that list is refused before write. When allowed_formats cannot be resolved, defaults to the site config's `defaultTextFormat`, then 'full_html'.
23
22
  - `status` (boolean (true/false)): Published flag for NON-moderated types: true = publish, false = unpublish. Ignored if moderationState is set.
24
23
  - `moderationState` (string): Moderation state transition for content_moderation types, e.g. 'draft', 'published', 'archived'. Takes precedence over status. Required to keep or re-publish a live node — omitting it on a published moderated node defaults the write to 'draft'.
25
- - `fields` (object (pass as JSON)): Scalar/attribute field values keyed by machine name. Entity-reference fields go in `relationships`, not here.
24
+ - `fields` (object (pass as JSON)): Scalar/attribute field values keyed by machine name. Formatted text: a string or { value, format?, summary? }. format must be in the field's allowed_formats; a single allowed format is used when omitted. Entity-reference fields go in `relationships`, not here.
26
25
  - `relationships` (object (pass as JSON)): Entity-reference fields as JSON:API relationships, keyed by field machine name. Single-value uses { data: { type, id } }; multi-value uses { data: [{ type, id }, …] }. Paragraph / ERR items must carry meta.target_revision_id — the connector injects it when missing, and fails the write if it cannot.
27
- - `dryRun` (boolean (true/false)): Validate, resolve ERR identifiers, and (on moderated targets) run the core PATCH-guard probe against Drupal, then return a preview without the real write. The probe uses a non-matching data.id so Drupal does not save. A working-copy 400 fails the dryRun.
26
+ - `dryRun` (boolean (true/false)): Validate, resolve ERR identifiers, and (on moderated targets) run the core PATCH-guard probe against Drupal, then return a preview without the real write. The probe uses a non-matching data.id so Drupal does not save, and hits the same URL as the real write (canonical, or ?resourceVersion=rel:working-copy when a draft is addressable). A working-copy 400 fails the dryRun.
28
27
  - `returning` (string): Response verbosity. "full" (default) returns the complete saved entity; "minimal" returns just identity + state (id, type, bundle, title, status, changed, url) — much smaller, recommended for bulk writes where the echoed body would dominate the response.
29
28
 
30
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
29
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update an existing Paragraph entity's field values by paragraph type (bundle) and UUID. Only the attributes you pass are changed (partial update); the host entity's reference to the paragraph is unchanged (same UUID), so this maintains a component paragraph in place without re-embedding. Returns relationshipData including meta.target_revision_id for a later host attach. Use drupal_get_entity_schema (entityType 'paragraph', the bundle) to discover fields. Governed by the site security policy."
3
3
  argument-hint: "<paragraphType> <id> [site] [attributes]"
4
- allowed-tools: mcp__drupal__drupal_update_paragraph
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_paragraph` MCP tool.
6
+ Call the MCP tool `drupal_update_paragraph`.
8
7
 
9
8
  Update an existing Paragraph entity's field values by paragraph type (bundle) and UUID. Only the attributes you pass are changed (partial update); the host entity's reference to the paragraph is unchanged (same UUID), so this maintains a component paragraph in place without re-embedding. Returns relationshipData including meta.target_revision_id for a later host attach. Use drupal_get_entity_schema (entityType 'paragraph', the bundle) to discover fields. Governed by the site security policy.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `paragraphType` (string): Paragraph type / bundle machine name, e.g. 'text', 'image', 'cta'
@@ -18,4 +17,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
18
17
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
19
18
  - `attributes` (object (pass as JSON)): Paragraph field values to change, keyed by Drupal machine name, e.g. { field_body: { value: '<p>..</p>', format: 'full_html' } }
20
19
 
21
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
20
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update an existing redirect by UUID: repoint its source or target, or change its status code (e.g. 301↔302). Only the fields you pass are changed (partial update). Use this to activate/fix a redirect that isn't firing (e.g. one created with a stale source). Governed by the site security policy."
3
3
  argument-hint: "<id> [site] [source] [target] [statusCode]"
4
- allowed-tools: mcp__drupal__drupal_update_redirect
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_redirect` MCP tool.
6
+ Call the MCP tool `drupal_update_redirect`.
8
7
 
9
8
  Update an existing redirect by UUID: repoint its source or target, or change its status code (e.g. 301↔302). Only the fields you pass are changed (partial update). Use this to activate/fix a redirect that isn't firing (e.g. one created with a stale source). Governed by the site security policy.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `id` (string): Redirect entity UUID
@@ -19,4 +18,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
19
18
  - `target` (string): New destination path/URI. Omit to leave unchanged.
20
19
  - `statusCode` (number): New HTTP redirect status code (301/302/303/307/308). Omit to leave unchanged.
21
20
 
22
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
21
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update an existing taxonomy term's name, description, or weight."
3
3
  argument-hint: "<vocabulary> <id> [site] [name] [description] [weight]"
4
- allowed-tools: mcp__drupal__drupal_update_taxonomy_term
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_taxonomy_term` MCP tool.
6
+ Call the MCP tool `drupal_update_taxonomy_term`.
8
7
 
9
8
  Update an existing taxonomy term's name, description, or weight.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `vocabulary` (string)
@@ -20,4 +19,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
20
19
  - `description` (string)
21
20
  - `weight` (number)
22
21
 
23
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
22
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Update a Drupal user account. Only include fields you want to change. Can reassign roles by providing a full replacement role list."
3
3
  argument-hint: "<id> [site] [name] [mail] [password] [status] [roles] [timezone]"
4
- allowed-tools: mcp__drupal__drupal_update_user
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_update_user` MCP tool.
6
+ Call the MCP tool `drupal_update_user`.
8
7
 
9
8
  Update a Drupal user account. Only include fields you want to change. Can reassign roles by providing a full replacement role list.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `id` (string): User UUID
@@ -22,4 +21,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
22
21
  - `roles` (array (pass as JSON)): Full replacement role UUID list
23
22
  - `timezone` (string)
24
23
 
25
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
24
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Convenience tool: upload a local file and immediately create a Media entity in one step. Best for the common 'add an image' workflow. Media defaults to unpublished."
3
3
  argument-hint: "<filePath> <mediaType> <fieldName> [site] [mediaName] [altText] [status]"
4
- allowed-tools: mcp__drupal__drupal_upload_file_and_create_media
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_upload_file_and_create_media` MCP tool.
6
+ Call the MCP tool `drupal_upload_file_and_create_media`.
8
7
 
9
8
  Convenience tool: upload a local file and immediately create a Media entity in one step. Best for the common 'add an image' workflow. Media defaults to unpublished.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `filePath` (string): Local path to the file (must resolve under MCP_UPLOAD_ROOT or the connector working directory)
@@ -21,4 +20,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
21
20
  - `altText` (string): Alt text for image media
22
21
  - `status` (boolean (true/false)): Published flag. Defaults to false. Requires allowPublish when true.
23
22
 
24
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
23
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  description: "Upload a local file to Drupal and create a File entity. Returns the file UUID to use when creating a Media entity. For images, the typical flow is: drupal_upload_file → drupal_create_media."
3
3
  argument-hint: "<filePath> <bundle> <fieldName> [site] [entityType]"
4
- allowed-tools: mcp__drupal__drupal_upload_file
5
4
  ---
6
5
 
7
- Call the `mcp__drupal__drupal_upload_file` MCP tool.
6
+ Call the MCP tool `drupal_upload_file`.
8
7
 
9
8
  Upload a local file to Drupal and create a File entity. Returns the file UUID to use when creating a Media entity. For images, the typical flow is: drupal_upload_file → drupal_create_media.
10
9
 
11
- Parse the request in `$ARGUMENTS` into this tool's parameters:
10
+ Parse the arguments supplied with this command into this tool's parameters:
12
11
 
13
12
  **Required:**
14
13
  - `filePath` (string): Local path to the file (must resolve under MCP_UPLOAD_ROOT or the connector working directory)
@@ -19,4 +18,4 @@ Parse the request in `$ARGUMENTS` into this tool's parameters:
19
18
  - `site` (string): Named site from connector config. Omit only on reads: multi-site configs fall back to defaultSite (often local/dev, not production). Writes require an explicit site when more than one site is configured. Every response includes `_target` { name, baseUrl, source } (`hint` when you passed site, `default` when you did not).
20
19
  - `entityType` (string): Drupal entity type (usually 'media' or 'node')
21
20
 
22
- If a required parameter is missing from `$ARGUMENTS`, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
21
+ If a required parameter is missing, ask before calling — do not invent values. Coerce each value to its JSON type (booleans → true/false, numbers → numeric, object/array → parse JSON), then make the single tool call and summarize the result.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.8.0] - 2026-08-25
11
+
12
+ ### Fixed
13
+ - **Iterative updates PATCH an existing working copy (#166).**
14
+ `drupal_update_node`, `drupal_entity_update`, and `drupal_bulk_update`
15
+ resolve `rel:working-copy` before the write. When that alias is
16
+ addressable, both the dryRun probe and the real PATCH use
17
+ `?resourceVersion=rel:working-copy` instead of the canonical URL, so a
18
+ second edit lands on the same forward revision. dryRun can no longer
19
+ succeed when that write would 400. An addressable draft is edited in
20
+ place — the connector does not discard it or tell the caller to publish
21
+ first. The stray-revision case (alias does not resolve, core still
22
+ blocks) still refuses with revision-surgery language (#201). A stale or
23
+ concurrent working-copy 400 is refused without retrying the canonical
24
+ URL. Successful writes include `_revisions: { live, working }` when both
25
+ vids can be read.
26
+ - **`summary` is refused when body has no summary property (#163).**
27
+ `drupal_create_node` and `drupal_update_node` introspect the sampled body
28
+ field before writing `summary`. A `text_long` / `text_formatted` body (or
29
+ an undetermined schema) fails closed with an actionable message to set the
30
+ site's deck field via `fields`. Core `text_with_summary` still accepts
31
+ `summary` and returns `_warnings` with `summary_parameter_deprecated`.
32
+ dryRun uses the same check.
33
+ - **Node writes honor field `allowed_formats` (#168).** Create/update (including
34
+ `dryRun`) resolve Field API `allowed_formats` from JSON:API `field_config`
35
+ and, when that is unavailable, Drush `config:get`. A single allowed format
36
+ is the default when the caller omits `format`. A caller format outside the
37
+ list is refused before mutation. The historical `defaultTextFormat` /
38
+ `full_html` fallback applies only while the list cannot be resolved — never
39
+ when `full_html` is excluded by field config.
40
+
41
+ ### Added
42
+ - **Northbound data-flow budgets bind to principal and target (#179).** Every
43
+ governed tool call that resolves a site now carries request-scoped principal
44
+ and authoritative-target context. Row, byte, page, request, and chained-action
45
+ counters use the same finite defaults as mcp_sentinel (500 results / 8 MiB /
46
+ 600 req/60s / 120 pages/60s) and are keyed by inbound principal + target —
47
+ not by MCP session — so pagination, retries, batching, and a new chain id
48
+ cannot reset them. JSON:API, GraphQL, and the server-tool bridge send the
49
+ source wire contract (`X-MCP-Declared-Ceiling`, narrow-only;
50
+ `X-MCP-Declared-Destination` from the entitlement pair). Denials name a
51
+ stable reason plus a correlation id and do not echo restricted payload.
52
+ Optional `security.declaredCeiling` and `security.readBudgets` override the
53
+ defaults. Stdio / local-operator traffic still sends the declared headers;
54
+ connector-side counters enforce when an inbound OAuth principal is present.
55
+
56
+ ### Changed
57
+ - **Slash-command stubs moved to `.agents/commands/` (#218).** Generated
58
+ `/drupal-*` files are harness-agnostic (protocol tool names, no
59
+ `mcp__drupal__*` `allowed-tools`). `.claude/` is no longer tracked or
60
+ published. `npm run install:commands` copies stubs into operator home
61
+ directories (`~/.claude/commands` with the Claude Code adapter,
62
+ `~/.grok/commands` as-is) so the bare `/drupal-*` form still works
63
+ without a vendor folder in this repo or in a consuming project. The npm
64
+ `files` list now ships `.agents/commands/` instead of `.claude/commands/`.
65
+
10
66
  ## [2.7.4] - 2026-08-18
11
67
 
12
68
  ### Fixed
package/README.md CHANGED
@@ -49,7 +49,9 @@ Each site declares which backend(s) it exposes via the `api` key:
49
49
  - **Writes go through JSON:API.** Use a JSON:API-enabled site as the write plane; keep GraphQL as a read plane where that suits your architecture.
50
50
  - **`defaultTextFormat` sets the body text format** used by the `body` convenience
51
51
  parameter on node writes, e.g. `{ "baseUrl": "…", "defaultTextFormat": "basic_html" }`.
52
- Individual calls can override it with `format`. Without either, the connector falls
52
+ Individual calls can override it with `format`. Field API `allowed_formats` wins
53
+ when it can be resolved: a single allowed format is the default, and a format
54
+ outside the list is refused. Without a resolved list, the connector falls
53
55
  back to `full_html`, which many governed sites deliberately do not define — and which
54
56
  is the most permissive core format, so setting this per site is the safer posture. A
55
57
  text format is Drupal's HTML-filtering boundary, so it is worth choosing deliberately
@@ -119,20 +121,27 @@ governed call to that tool. These are protocol-native, so they work everywhere t
119
121
  prompts capability is supported — the client renders them per its own convention
120
122
  (e.g. Claude Code shows `/mcp__drupal__drupal-create-node`).
121
123
 
122
- #### Claude Code slash commands (`/drupal-*`)
123
- For the literal bare `/drupal-<tool>` form in **Claude Code specifically**, the
124
- connector also ships generated command files under `.claude/commands/`. Because
125
- Claude Code project commands are per-project, copy them into your own project (or
126
- regenerate from the installed package):
124
+ #### Filesystem slash commands (`/drupal-*`)
125
+ MCP prompts (above) are the protocol-native slash surface. For the literal bare
126
+ `/drupal-<tool>` form, the connector ships generated stubs under
127
+ **`.agents/commands/`** a harness-agnostic tree, not a vendor rule directory.
128
+ Clients that scan `.agents/commands/` (including Grok Build when this repo is
129
+ the project) pick them up automatically.
130
+
131
+ Clients that only scan a vendor home path (Claude Code `~/.claude/commands`,
132
+ Grok `~/.grok/commands`) install from that tree into the **operator home**, so
133
+ no consuming project has to commit `.claude/` or `.grok/`:
127
134
 
128
135
  ```bash
129
- mkdir -p .claude/commands
130
- cp node_modules/drupal-mcp-connector/.claude/commands/drupal-*.md .claude/commands/
131
- # …or, from a clone of the connector: npm run generate:commands
136
+ npm run generate:commands # refresh .agents/commands/ (from a clone)
137
+ npm run install:commands # copies into ~/.claude/commands and ~/.grok/commands
138
+ # npm run install:commands -- --clients claude
139
+ # npm run install:commands -- --home /path/to/fake-home # tests / CI
132
140
  ```
133
141
 
134
- Other MCP agents don't need this step — they get the same coverage from the per-tool
135
- prompts above.
142
+ From an npm install, the same files ship at
143
+ `node_modules/drupal-mcp-connector/.agents/commands/`. Do not copy them into an
144
+ application repo's `.claude/` or `.grok/` directory.
136
145
 
137
146
  ### Security Model
138
147
 
@@ -161,6 +170,7 @@ Additional connector-side gates (2.1+ / 2.2+):
161
170
  - **Uploads** only from `MCP_UPLOAD_ROOT` (or the process cwd); sensitive paths (`.env*`, `.ssh`, connector `config.json`) are refused.
162
171
  - **HTTPS:** non-loopback binds require an inbound OAuth resource server (`auth.issuer` + `auth.audience`), not `MCP_AUTH_TOKEN`. `MCP_AUTH_TOKEN` remains valid on loopback. `MCP_ALLOW_UNAUTHENTICATED=1` is only for a trusted proxy. Non-loopback TLS defaults to 120 req/min rate limiting. Inbound JWT scopes (and optional `auth.grants`) filter which tools, resources, prompts, and sites a principal can discover or invoke.
163
172
  - **GraphQL is off by default.** `drupal_graphql` / introspect require `security.allowGraphql` (true only on the `development` preset). Raw GraphQL results still bypass entity allowlists and field redaction — prefer JSON:API entity tools when connector policy must hold. Mutations also need `allowGraphqlMutations`.
173
+ - **Northbound data-flow budgets (2.8+):** row, byte, page, request, and chained-action counters keyed by inbound principal + authoritative target (same finite defaults as MCP Sentinel), so pagination, retries, batching, or a new chain id cannot reset them. Governed reads carry the declared-ceiling / declared-destination wire contract; tune with `security.declaredCeiling` and `security.readBudgets`.
164
174
 
165
175
  Full detail: **[docs/security.md](docs/security.md)** and **[docs/security-hardening.md](docs/security-hardening.md)**.
166
176