drupal-mcp-connector 2.7.2 → 2.7.4

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 (132) hide show
  1. package/.claude/commands/drupal-audit-config-best-practices.md +1 -1
  2. package/.claude/commands/drupal-audit-site-health.md +1 -1
  3. package/.claude/commands/drupal-block-user.md +1 -1
  4. package/.claude/commands/drupal-bulk-create.md +1 -1
  5. package/.claude/commands/drupal-bulk-update.md +1 -1
  6. package/.claude/commands/drupal-config-get.md +1 -1
  7. package/.claude/commands/drupal-config-list.md +1 -1
  8. package/.claude/commands/drupal-config-set.md +1 -1
  9. package/.claude/commands/drupal-content-by-moderation-state.md +3 -3
  10. package/.claude/commands/drupal-create-block.md +1 -1
  11. package/.claude/commands/drupal-create-media.md +1 -1
  12. package/.claude/commands/drupal-create-menu-link.md +1 -1
  13. package/.claude/commands/drupal-create-node.md +1 -1
  14. package/.claude/commands/drupal-create-paragraph.md +1 -1
  15. package/.claude/commands/drupal-create-redirect.md +1 -1
  16. package/.claude/commands/drupal-create-taxonomy-term.md +1 -1
  17. package/.claude/commands/drupal-create-translation.md +1 -1
  18. package/.claude/commands/drupal-create-user.md +1 -1
  19. package/.claude/commands/drupal-delete-media.md +1 -1
  20. package/.claude/commands/drupal-delete-node.md +1 -1
  21. package/.claude/commands/drupal-delete-taxonomy-term.md +1 -1
  22. package/.claude/commands/drupal-describe-fields.md +1 -1
  23. package/.claude/commands/drupal-drush-cache-rebuild.md +1 -1
  24. package/.claude/commands/drupal-drush-config-export.md +1 -1
  25. package/.claude/commands/drupal-drush-config-import.md +1 -1
  26. package/.claude/commands/drupal-drush-config-status.md +1 -1
  27. package/.claude/commands/drupal-drush-cron.md +1 -1
  28. package/.claude/commands/drupal-drush-module-disable.md +1 -1
  29. package/.claude/commands/drupal-drush-module-enable.md +1 -1
  30. package/.claude/commands/drupal-drush-module-list.md +1 -1
  31. package/.claude/commands/drupal-drush-security-updates.md +1 -1
  32. package/.claude/commands/drupal-drush-sql-query.md +1 -1
  33. package/.claude/commands/drupal-drush-status.md +1 -1
  34. package/.claude/commands/drupal-drush-updatedb.md +1 -1
  35. package/.claude/commands/drupal-drush-user-create.md +1 -1
  36. package/.claude/commands/drupal-drush-user-list.md +1 -1
  37. package/.claude/commands/drupal-drush-watchdog.md +1 -1
  38. package/.claude/commands/drupal-entity-create.md +1 -1
  39. package/.claude/commands/drupal-entity-delete.md +1 -1
  40. package/.claude/commands/drupal-entity-get.md +1 -1
  41. package/.claude/commands/drupal-entity-list.md +1 -1
  42. package/.claude/commands/drupal-entity-update.md +1 -1
  43. package/.claude/commands/drupal-find-orphaned-media.md +1 -1
  44. package/.claude/commands/drupal-get-entity-schema.md +1 -1
  45. package/.claude/commands/drupal-get-media.md +1 -1
  46. package/.claude/commands/drupal-get-node.md +1 -1
  47. package/.claude/commands/drupal-get-paragraph.md +1 -1
  48. package/.claude/commands/drupal-get-revision.md +1 -1
  49. package/.claude/commands/drupal-get-taxonomy-term.md +1 -1
  50. package/.claude/commands/drupal-get-taxonomy-terms.md +1 -1
  51. package/.claude/commands/drupal-get-user-by-name.md +1 -1
  52. package/.claude/commands/drupal-get-user.md +1 -1
  53. package/.claude/commands/drupal-governance-status.md +1 -1
  54. package/.claude/commands/drupal-graphql-introspect.md +1 -1
  55. package/.claude/commands/drupal-graphql.md +1 -1
  56. package/.claude/commands/drupal-list-blocks.md +1 -1
  57. package/.claude/commands/drupal-list-content-types.md +1 -1
  58. package/.claude/commands/drupal-list-entity-types.md +1 -1
  59. package/.claude/commands/drupal-list-media-types.md +1 -1
  60. package/.claude/commands/drupal-list-media.md +1 -1
  61. package/.claude/commands/drupal-list-menu-links.md +1 -1
  62. package/.claude/commands/drupal-list-moderation-states.md +1 -1
  63. package/.claude/commands/drupal-list-nodes.md +1 -1
  64. package/.claude/commands/drupal-list-revisions.md +1 -1
  65. package/.claude/commands/drupal-list-roles.md +1 -1
  66. package/.claude/commands/drupal-list-translations.md +1 -1
  67. package/.claude/commands/drupal-list-users.md +1 -1
  68. package/.claude/commands/drupal-list-vocabularies.md +1 -1
  69. package/.claude/commands/drupal-mcp-whoami.md +1 -1
  70. package/.claude/commands/drupal-report-404-log.md +1 -1
  71. package/.claude/commands/drupal-report-accessibility-audit.md +1 -1
  72. package/.claude/commands/drupal-report-alias-coverage.md +1 -1
  73. package/.claude/commands/drupal-report-broken-embeds.md +1 -1
  74. package/.claude/commands/drupal-report-broken-links.md +1 -1
  75. package/.claude/commands/drupal-report-cache-config.md +1 -1
  76. package/.claude/commands/drupal-report-config-drift.md +1 -1
  77. package/.claude/commands/drupal-report-content-by-author.md +1 -1
  78. package/.claude/commands/drupal-report-content-summary.md +1 -1
  79. package/.claude/commands/drupal-report-duplicate-content.md +1 -1
  80. package/.claude/commands/drupal-report-field-completeness.md +1 -1
  81. package/.claude/commands/drupal-report-menu-integrity.md +1 -1
  82. package/.claude/commands/drupal-report-missing-field.md +1 -1
  83. package/.claude/commands/drupal-report-module-audit.md +1 -1
  84. package/.claude/commands/drupal-report-orphan-pages.md +1 -1
  85. package/.claude/commands/drupal-report-orphaned-references.md +1 -1
  86. package/.claude/commands/drupal-report-permission-audit.md +1 -1
  87. package/.claude/commands/drupal-report-pii-exposure.md +1 -1
  88. package/.claude/commands/drupal-report-readability.md +1 -1
  89. package/.claude/commands/drupal-report-recently-published.md +1 -1
  90. package/.claude/commands/drupal-report-redirect-health.md +1 -1
  91. package/.claude/commands/drupal-report-revision-hotspots.md +1 -1
  92. package/.claude/commands/drupal-report-scheduled-content.md +1 -1
  93. package/.claude/commands/drupal-report-seo-audit.md +1 -1
  94. package/.claude/commands/drupal-report-seo-meta-coverage.md +1 -1
  95. package/.claude/commands/drupal-report-stale-content.md +1 -1
  96. package/.claude/commands/drupal-report-status-report.md +1 -1
  97. package/.claude/commands/drupal-report-taxonomy-usage.md +1 -1
  98. package/.claude/commands/drupal-report-text-format-audit.md +1 -1
  99. package/.claude/commands/drupal-report-translation-coverage.md +1 -1
  100. package/.claude/commands/drupal-report-unpublished.md +1 -1
  101. package/.claude/commands/drupal-report-user-activity.md +1 -1
  102. package/.claude/commands/drupal-report-workflow-bottlenecks.md +1 -1
  103. package/.claude/commands/drupal-resolve-reference.md +1 -1
  104. package/.claude/commands/drupal-revert-revision.md +1 -1
  105. package/.claude/commands/drupal-schedule-publish.md +1 -1
  106. package/.claude/commands/drupal-search-content.md +1 -1
  107. package/.claude/commands/drupal-search.md +1 -1
  108. package/.claude/commands/drupal-security-info.md +1 -1
  109. package/.claude/commands/drupal-set-moderation-state.md +1 -1
  110. package/.claude/commands/drupal-site-info.md +1 -1
  111. package/.claude/commands/drupal-update-media.md +1 -1
  112. package/.claude/commands/drupal-update-menu-link.md +1 -1
  113. package/.claude/commands/drupal-update-node.md +1 -1
  114. package/.claude/commands/drupal-update-paragraph.md +1 -1
  115. package/.claude/commands/drupal-update-redirect.md +1 -1
  116. package/.claude/commands/drupal-update-taxonomy-term.md +1 -1
  117. package/.claude/commands/drupal-update-user.md +1 -1
  118. package/.claude/commands/drupal-upload-file-and-create-media.md +1 -1
  119. package/.claude/commands/drupal-upload-file.md +1 -1
  120. package/CHANGELOG.md +38 -0
  121. package/README.md +2 -1
  122. package/package.json +1 -1
  123. package/src/index.js +5 -2
  124. package/src/lib/dispatch.js +63 -15
  125. package/src/lib/load-secrets.js +40 -5
  126. package/src/lib/operations.js +39 -2
  127. package/src/lib/security.js +1 -1
  128. package/src/lib/site-target.js +78 -0
  129. package/src/lib/tool-prompts.js +2 -1
  130. package/src/tools/config.js +5 -2
  131. package/src/tools/index.js +21 -1
  132. package/src/tools/moderation.js +88 -9
@@ -20,6 +20,25 @@ import {
20
20
  resolveSecurityConfig, assertReadAllowed, assertWriteAllowed, assertPublishAllowed,
21
21
  redactCanonicalEntity,
22
22
  } from "../lib/security.js";
23
+ import { collectEntities } from "../lib/reports-support.js";
24
+
25
+ /** Cap for the client-side scan when JSON:API cannot filter the field. */
26
+ const SAMPLE_CAP = 500;
27
+
28
+ /**
29
+ * Drupal core JSON:API cannot filter on the computed `moderation_state`
30
+ * field. The error is a 500 whose detail is `'moderation_state' not found`
31
+ * (#162). jsonapi_extras (or a similar alias) can make the field filterable;
32
+ * those sites never hit this.
33
+ *
34
+ * @param {unknown} err
35
+ * @returns {boolean}
36
+ */
37
+ export function isUnfilterableModerationState(err) {
38
+ const msg = String(err?.message || "");
39
+ if (!/moderation_state/i.test(msg)) return false;
40
+ return /not found/i.test(msg) || /not filterable/i.test(msg) || /invalid filter/i.test(msg);
41
+ }
23
42
 
24
43
  /** Read a node's moderation_state from a canonical entity, tolerating shapes. */
25
44
  function moderationStateOf(entity) {
@@ -49,6 +68,12 @@ async function setModerationState({ site: siteName, type, id, state }) {
49
68
 
50
69
  /**
51
70
  * List nodes of a type in a given moderation state, paged + redacted.
71
+ *
72
+ * Stock JSON:API cannot filter on the computed `moderation_state` field
73
+ * (#162). When the site accepts the filter (jsonapi_extras or similar) the
74
+ * result is exact. Otherwise we scan a bounded recent set and filter
75
+ * client-side rather than surfacing Drupal's 500 as a transient error.
76
+ *
52
77
  * @param {object} args - { site?, type, state, limit?, offset? }.
53
78
  */
54
79
  async function contentByModerationState({ site: siteName, type, state, limit = 20, offset = 0 }) {
@@ -56,14 +81,68 @@ async function contentByModerationState({ site: siteName, type, state, limit = 2
56
81
  const sec = resolveSecurityConfig(site);
57
82
  assertReadAllowed(sec, "node", type);
58
83
  const backend = await resolveBackend(site);
59
- const res = await backend.listEntities({
60
- entityType: "node", bundle: type,
61
- filters: [{ field: "moderation_state", op: "eq", value: state }],
62
- sort: [{ field: "changed", dir: "desc" }],
63
- page: { limit, offset },
64
- });
65
- const nodes = res.entities.map((e) => redactCanonicalEntity(e, sec, "node"));
66
- return { type, state, total: res.page?.total ?? nodes.length, approximate: res.approximate ?? false, offset, nextOffset: offset + nodes.length, nodes };
84
+ const sort = [{ field: "changed", dir: "desc" }];
85
+ const canFilter = typeof backend.capabilities === "function"
86
+ ? Boolean(backend.capabilities()?.filter)
87
+ : true;
88
+
89
+ if (canFilter) {
90
+ try {
91
+ const res = await backend.listEntities({
92
+ entityType: "node", bundle: type,
93
+ filters: [{ field: "moderation_state", op: "eq", value: state }],
94
+ sort,
95
+ page: { limit, offset },
96
+ });
97
+ const nodes = res.entities.map((e) => redactCanonicalEntity(e, sec, "node"));
98
+ return {
99
+ type, state, source: "filter",
100
+ total: res.page?.total ?? nodes.length,
101
+ approximate: res.approximate ?? false,
102
+ offset, nextOffset: offset + nodes.length, nodes,
103
+ };
104
+ } catch (err) {
105
+ if (!isUnfilterableModerationState(err)) throw err;
106
+ }
107
+ }
108
+
109
+ const scanned = await collectEntities(
110
+ backend,
111
+ { entityType: "node", bundle: type, sort },
112
+ SAMPLE_CAP,
113
+ );
114
+ const wanted = String(state).toLowerCase();
115
+ const matches = [];
116
+ let sawState = false;
117
+ for (const entity of scanned) {
118
+ const got = moderationStateOf(entity);
119
+ if (got === null || got === undefined) continue;
120
+ sawState = true;
121
+ if (String(got).toLowerCase() === wanted) matches.push(entity);
122
+ }
123
+ if (!sawState && scanned.length > 0) {
124
+ return {
125
+ type, state, unavailable: true, source: "sampled", scanned: scanned.length,
126
+ reason:
127
+ "moderation_state is not filterable over JSON:API on this site (it is a " +
128
+ "computed field) and no sampled entity exposed the field. Enable " +
129
+ "content_moderation on the bundle, or expose a filterable alias " +
130
+ "(jsonapi_extras).",
131
+ };
132
+ }
133
+ const page = matches.slice(offset, offset + limit);
134
+ return {
135
+ type, state, source: "sampled",
136
+ approximate: scanned.length >= SAMPLE_CAP,
137
+ scanned: scanned.length,
138
+ total: matches.length,
139
+ offset, nextOffset: offset + page.length,
140
+ nodes: page.map((e) => redactCanonicalEntity(e, sec, "node")),
141
+ note:
142
+ "Stock JSON:API cannot filter on moderation_state (computed field). " +
143
+ "Results are a client-side sample of recent nodes, not a complete list. " +
144
+ "A filterable alias (jsonapi_extras) makes this exact.",
145
+ };
67
146
  }
68
147
 
69
148
  /**
@@ -101,7 +180,7 @@ export const definitions = [
101
180
  },
102
181
  {
103
182
  name: "drupal_content_by_moderation_state",
104
- description: "List nodes of a content type currently in a given moderation state (e.g. what is in 'draft' or 'needs_review').",
183
+ description: "List nodes of a content type currently in a given moderation state (e.g. what is in 'draft' or 'needs_review'). Stock JSON:API cannot filter the computed moderation_state field; when the site rejects that filter the tool samples recent nodes client-side and marks the result approximate, instead of returning Drupal's 500.",
105
184
  inputSchema: {
106
185
  type: "object", required: ["type", "state"],
107
186
  properties: {