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.
- package/.claude/commands/drupal-audit-config-best-practices.md +1 -1
- package/.claude/commands/drupal-audit-site-health.md +1 -1
- package/.claude/commands/drupal-block-user.md +1 -1
- package/.claude/commands/drupal-bulk-create.md +1 -1
- package/.claude/commands/drupal-bulk-update.md +1 -1
- package/.claude/commands/drupal-config-get.md +1 -1
- package/.claude/commands/drupal-config-list.md +1 -1
- package/.claude/commands/drupal-config-set.md +1 -1
- package/.claude/commands/drupal-content-by-moderation-state.md +3 -3
- package/.claude/commands/drupal-create-block.md +1 -1
- package/.claude/commands/drupal-create-media.md +1 -1
- package/.claude/commands/drupal-create-menu-link.md +1 -1
- package/.claude/commands/drupal-create-node.md +1 -1
- package/.claude/commands/drupal-create-paragraph.md +1 -1
- package/.claude/commands/drupal-create-redirect.md +1 -1
- package/.claude/commands/drupal-create-taxonomy-term.md +1 -1
- package/.claude/commands/drupal-create-translation.md +1 -1
- package/.claude/commands/drupal-create-user.md +1 -1
- package/.claude/commands/drupal-delete-media.md +1 -1
- package/.claude/commands/drupal-delete-node.md +1 -1
- package/.claude/commands/drupal-delete-taxonomy-term.md +1 -1
- package/.claude/commands/drupal-describe-fields.md +1 -1
- package/.claude/commands/drupal-drush-cache-rebuild.md +1 -1
- package/.claude/commands/drupal-drush-config-export.md +1 -1
- package/.claude/commands/drupal-drush-config-import.md +1 -1
- package/.claude/commands/drupal-drush-config-status.md +1 -1
- package/.claude/commands/drupal-drush-cron.md +1 -1
- package/.claude/commands/drupal-drush-module-disable.md +1 -1
- package/.claude/commands/drupal-drush-module-enable.md +1 -1
- package/.claude/commands/drupal-drush-module-list.md +1 -1
- package/.claude/commands/drupal-drush-security-updates.md +1 -1
- package/.claude/commands/drupal-drush-sql-query.md +1 -1
- package/.claude/commands/drupal-drush-status.md +1 -1
- package/.claude/commands/drupal-drush-updatedb.md +1 -1
- package/.claude/commands/drupal-drush-user-create.md +1 -1
- package/.claude/commands/drupal-drush-user-list.md +1 -1
- package/.claude/commands/drupal-drush-watchdog.md +1 -1
- package/.claude/commands/drupal-entity-create.md +1 -1
- package/.claude/commands/drupal-entity-delete.md +1 -1
- package/.claude/commands/drupal-entity-get.md +1 -1
- package/.claude/commands/drupal-entity-list.md +1 -1
- package/.claude/commands/drupal-entity-update.md +1 -1
- package/.claude/commands/drupal-find-orphaned-media.md +1 -1
- package/.claude/commands/drupal-get-entity-schema.md +1 -1
- package/.claude/commands/drupal-get-media.md +1 -1
- package/.claude/commands/drupal-get-node.md +1 -1
- package/.claude/commands/drupal-get-paragraph.md +1 -1
- package/.claude/commands/drupal-get-revision.md +1 -1
- package/.claude/commands/drupal-get-taxonomy-term.md +1 -1
- package/.claude/commands/drupal-get-taxonomy-terms.md +1 -1
- package/.claude/commands/drupal-get-user-by-name.md +1 -1
- package/.claude/commands/drupal-get-user.md +1 -1
- package/.claude/commands/drupal-governance-status.md +1 -1
- package/.claude/commands/drupal-graphql-introspect.md +1 -1
- package/.claude/commands/drupal-graphql.md +1 -1
- package/.claude/commands/drupal-list-blocks.md +1 -1
- package/.claude/commands/drupal-list-content-types.md +1 -1
- package/.claude/commands/drupal-list-entity-types.md +1 -1
- package/.claude/commands/drupal-list-media-types.md +1 -1
- package/.claude/commands/drupal-list-media.md +1 -1
- package/.claude/commands/drupal-list-menu-links.md +1 -1
- package/.claude/commands/drupal-list-moderation-states.md +1 -1
- package/.claude/commands/drupal-list-nodes.md +1 -1
- package/.claude/commands/drupal-list-revisions.md +1 -1
- package/.claude/commands/drupal-list-roles.md +1 -1
- package/.claude/commands/drupal-list-translations.md +1 -1
- package/.claude/commands/drupal-list-users.md +1 -1
- package/.claude/commands/drupal-list-vocabularies.md +1 -1
- package/.claude/commands/drupal-mcp-whoami.md +1 -1
- package/.claude/commands/drupal-report-404-log.md +1 -1
- package/.claude/commands/drupal-report-accessibility-audit.md +1 -1
- package/.claude/commands/drupal-report-alias-coverage.md +1 -1
- package/.claude/commands/drupal-report-broken-embeds.md +1 -1
- package/.claude/commands/drupal-report-broken-links.md +1 -1
- package/.claude/commands/drupal-report-cache-config.md +1 -1
- package/.claude/commands/drupal-report-config-drift.md +1 -1
- package/.claude/commands/drupal-report-content-by-author.md +1 -1
- package/.claude/commands/drupal-report-content-summary.md +1 -1
- package/.claude/commands/drupal-report-duplicate-content.md +1 -1
- package/.claude/commands/drupal-report-field-completeness.md +1 -1
- package/.claude/commands/drupal-report-menu-integrity.md +1 -1
- package/.claude/commands/drupal-report-missing-field.md +1 -1
- package/.claude/commands/drupal-report-module-audit.md +1 -1
- package/.claude/commands/drupal-report-orphan-pages.md +1 -1
- package/.claude/commands/drupal-report-orphaned-references.md +1 -1
- package/.claude/commands/drupal-report-permission-audit.md +1 -1
- package/.claude/commands/drupal-report-pii-exposure.md +1 -1
- package/.claude/commands/drupal-report-readability.md +1 -1
- package/.claude/commands/drupal-report-recently-published.md +1 -1
- package/.claude/commands/drupal-report-redirect-health.md +1 -1
- package/.claude/commands/drupal-report-revision-hotspots.md +1 -1
- package/.claude/commands/drupal-report-scheduled-content.md +1 -1
- package/.claude/commands/drupal-report-seo-audit.md +1 -1
- package/.claude/commands/drupal-report-seo-meta-coverage.md +1 -1
- package/.claude/commands/drupal-report-stale-content.md +1 -1
- package/.claude/commands/drupal-report-status-report.md +1 -1
- package/.claude/commands/drupal-report-taxonomy-usage.md +1 -1
- package/.claude/commands/drupal-report-text-format-audit.md +1 -1
- package/.claude/commands/drupal-report-translation-coverage.md +1 -1
- package/.claude/commands/drupal-report-unpublished.md +1 -1
- package/.claude/commands/drupal-report-user-activity.md +1 -1
- package/.claude/commands/drupal-report-workflow-bottlenecks.md +1 -1
- package/.claude/commands/drupal-resolve-reference.md +1 -1
- package/.claude/commands/drupal-revert-revision.md +1 -1
- package/.claude/commands/drupal-schedule-publish.md +1 -1
- package/.claude/commands/drupal-search-content.md +1 -1
- package/.claude/commands/drupal-search.md +1 -1
- package/.claude/commands/drupal-security-info.md +1 -1
- package/.claude/commands/drupal-set-moderation-state.md +1 -1
- package/.claude/commands/drupal-site-info.md +1 -1
- package/.claude/commands/drupal-update-media.md +1 -1
- package/.claude/commands/drupal-update-menu-link.md +1 -1
- package/.claude/commands/drupal-update-node.md +1 -1
- package/.claude/commands/drupal-update-paragraph.md +1 -1
- package/.claude/commands/drupal-update-redirect.md +1 -1
- package/.claude/commands/drupal-update-taxonomy-term.md +1 -1
- package/.claude/commands/drupal-update-user.md +1 -1
- package/.claude/commands/drupal-upload-file-and-create-media.md +1 -1
- package/.claude/commands/drupal-upload-file.md +1 -1
- package/CHANGELOG.md +38 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/index.js +5 -2
- package/src/lib/dispatch.js +63 -15
- package/src/lib/load-secrets.js +40 -5
- package/src/lib/operations.js +39 -2
- package/src/lib/security.js +1 -1
- package/src/lib/site-target.js +78 -0
- package/src/lib/tool-prompts.js +2 -1
- package/src/tools/config.js +5 -2
- package/src/tools/index.js +21 -1
- package/src/tools/moderation.js +88 -9
package/src/tools/moderation.js
CHANGED
|
@@ -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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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: {
|