drupal-mcp-connector 2.16.0 → 2.18.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.
@@ -9,12 +9,15 @@
9
9
  * - drupal_create_translation — add a language as an unpublished forward draft
10
10
  *
11
11
  * Continuation of an existing unpublished translation is drupal_update_node
12
- * with `langcode`. Reads of that draft are drupal_get_node / drupal_get_revision
12
+ * with `langcode` (media: drupal_update_media; paragraph: drupal_update_paragraph).
13
+ * Reads of that draft are drupal_get_node / drupal_get_media / drupal_get_paragraph
13
14
  * with `langcode`.
14
15
  *
15
16
  * Paragraph field-value translation uses the same surface with
16
- * entityType "paragraph" and the pinned paragraph revision. Image alt is a
17
- * node relationship (`meta.alt`) with the shared file target unchanged.
17
+ * entityType "paragraph" and the pinned paragraph revision. Media uses the
18
+ * node live/working pair (`entityType: "media"`). Image alt on a media entity
19
+ * is the file relationship (`field_media_image` + `meta.alt`) with the shared
20
+ * file target unchanged.
18
21
  */
19
22
 
20
23
  import { getSiteConfig } from "../lib/config.js";
@@ -31,9 +34,11 @@ import { paragraphRevisionId } from "../lib/err-relationships.js";
31
34
  import {
32
35
  assertDraftLangcode,
33
36
  createTranslationDraft,
37
+ isMissingTranslationEndpoint,
34
38
  readTranslationInventory,
35
39
  resolveNodeTranslationPair,
36
- } from "../lib/draft-write.js";
40
+ supportsSentinelDraft,
41
+ } from "../lib/sentinel-draft.js";
37
42
  import { mapTranslationRow } from "../lib/translation-rows.js";
38
43
 
39
44
  const LIST_NOTE =
@@ -62,7 +67,8 @@ async function listTranslations({ site: siteName, entityType = "node", type, id
62
67
  assertReadAllowed(sec, entityType, type);
63
68
 
64
69
  const backend = await resolveBackend(site);
65
- if ((entityType === "node" || entityType === "paragraph") && typeof backend.rawQuery === "function") {
70
+ if ((entityType === "node" || entityType === "paragraph" || entityType === "media")
71
+ && supportsSentinelDraft(backend)) {
66
72
  try {
67
73
  const meta = await readTranslationInventory(backend, { entityType, bundle: type, id });
68
74
  const liveLangs = (meta.live?.translations ?? []).map((row) => row.langcode);
@@ -80,7 +86,7 @@ async function listTranslations({ site: siteName, entityType = "node", type, id
80
86
  note: LIST_NOTE,
81
87
  };
82
88
  } catch (error) {
83
- if (!/does not provide Sentinel's governed draft-translation endpoint/.test(String(error?.message))) {
89
+ if (!isMissingTranslationEndpoint(error)) {
84
90
  throw error;
85
91
  }
86
92
  }
@@ -127,8 +133,8 @@ async function createTranslation({
127
133
  const sec = resolveSecurityConfig(site);
128
134
  assertWriteAllowed(sec, "update", entityType, type);
129
135
 
130
- if (entityType !== "node" && entityType !== "paragraph") {
131
- throw new Error("Governed translation create is implemented for nodes and paragraphs.");
136
+ if (entityType !== "node" && entityType !== "paragraph" && entityType !== "media") {
137
+ throw new Error("Governed translation create is implemented for nodes, paragraphs, and media.");
132
138
  }
133
139
 
134
140
  const backend = await resolveBackend(site);
@@ -151,12 +157,14 @@ async function createTranslation({
151
157
  draftRevision = await resolveNodeTranslationPair(backend, {
152
158
  entityType, bundle: type, id, existing,
153
159
  });
154
- if (drafted.status === undefined && drafted.moderation_state === undefined) {
155
- drafted.moderation_state = "draft";
160
+ if (entityType === "node") {
161
+ if (drafted.status === undefined && drafted.moderation_state === undefined) {
162
+ drafted.moderation_state = "draft";
163
+ }
164
+ drafted = await applySafeDraftDefault({
165
+ backend, entityType, bundle: type, id, attributes: drafted, existingEntity: existing,
166
+ });
156
167
  }
157
- drafted = await applySafeDraftDefault({
158
- backend, entityType, bundle: type, id, attributes: drafted, existingEntity: existing,
159
- });
160
168
  assertPublishAllowed(sec, drafted);
161
169
  }
162
170
 
@@ -174,7 +182,7 @@ async function createTranslation({
174
182
  entityType, bundle: type, id, langcode: targetLang, attributes: drafted, relationships, draftRevision,
175
183
  });
176
184
  const redacted = omitLiveComputedMetatag(redactCanonicalEntity(created, sec, entityType));
177
- if (entityType !== "node") return redacted;
185
+ if (entityType === "paragraph") return redacted;
178
186
  const workingVid = entityRevisionId(created) ?? draftRevision.workingVid;
179
187
  const liveVid = draftRevision.liveVid;
180
188
  if (liveVid === undefined || liveVid === null) {
@@ -193,7 +201,7 @@ export const definitions = [
193
201
  {
194
202
  name: "drupal_list_translations",
195
203
  description:
196
- "List live and working translation langcodes for a Drupal node or paragraph. Uses Sentinel's " +
204
+ "List live and working translation langcodes for a Drupal node, paragraph, or media entity. Uses Sentinel's " +
197
205
  "translation inventory when available (live default revision vs unpublished working " +
198
206
  "draft), including core content_translation_outdated and source when the server sends them. " +
199
207
  "Core JSON:API alone serves one language and cannot prove others are absent. " +
@@ -202,7 +210,7 @@ export const definitions = [
202
210
  type: "object", required: ["type", "id"],
203
211
  properties: {
204
212
  site: { type: "string", description: "Named site (omit for default)" },
205
- entityType: { type: "string", description: "Entity type machine name. Default: 'node'." },
213
+ entityType: { type: "string", description: "Entity type machine name. Default: 'node'. Use 'paragraph' or 'media' when listing those." },
206
214
  type: { type: "string", description: "Bundle machine name, e.g. 'basic_page'" },
207
215
  id: { type: "string", description: "Entity UUID" },
208
216
  },
@@ -219,15 +227,17 @@ export const definitions = [
219
227
  "paragraph ERR pins stay unchanged. An existing translation is a conflict, not an overwrite. " +
220
228
  "The response includes `_revisions.live` / `_revisions.working` when known. " +
221
229
  "Computed `metatag` is omitted on the draft body because JSON:API resolves it from the live default (#283); use field_metatags. " +
222
- "Continue a node draft with drupal_update_node and langcode; continue a paragraph with " +
223
- "drupal_update_paragraph and langcode. Image alt is a relationship (same file UUID, " +
224
- "meta.alt). For paragraphs pass revisionId as the host pin. Requires Sentinel's " +
225
- "draft-translation endpoint. Publication stays denied for content-tier callers.",
230
+ "Continue a node draft with drupal_update_node and langcode; a paragraph with " +
231
+ "drupal_update_paragraph and langcode; media with drupal_update_media and langcode. " +
232
+ "Image alt is a relationship (same file UUID, meta.alt) — on media that is " +
233
+ "field_media_image, not a node image field. For paragraphs pass revisionId as the " +
234
+ "host pin. Requires Sentinel's draft-translation endpoint. Publication stays denied " +
235
+ "for content-tier callers.",
226
236
  inputSchema: {
227
237
  type: "object", required: ["type", "id", "langcode"],
228
238
  properties: {
229
239
  site: { type: "string" },
230
- entityType: { type: "string", description: "Entity type machine name. Default: 'node'. Use 'paragraph' for paragraph field values." },
240
+ entityType: { type: "string", description: "Entity type machine name. Default: 'node'. Use 'paragraph' for paragraph field values, 'media' for media (name, caption, image alt)." },
231
241
  type: { type: "string", description: "Bundle machine name, e.g. 'basic_page' or 'p_hero'" },
232
242
  id: { type: "string", description: "Entity UUID" },
233
243
  langcode: { type: "string", description: "Target language code, e.g. 'es', 'de', 'pt-br'" },