drupal-mcp-connector 2.24.2 → 2.26.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.
- package/.agents/commands/drupal-entity-update.md +1 -1
- package/.agents/commands/drupal-list-menu-links.md +2 -2
- package/.agents/commands/drupal-schedule-publish.md +5 -3
- package/.agents/commands/drupal-update-menu-link.md +3 -3
- package/.agents/commands/drupal-update-node.md +1 -1
- package/CHANGELOG.md +87 -0
- package/README.md +57 -1
- package/bin/drupal-mcp-doctor.js +16 -0
- package/bin/drupal-mcp-wizard.js +17 -0
- package/package.json +10 -5
- package/src/index.js +19 -19
- package/src/lib/backends/jsonapi.js +71 -8
- package/src/lib/dispatch.js +22 -0
- package/src/lib/doctor.js +645 -0
- package/src/lib/module-tools.js +33 -2
- package/src/lib/operations.js +1 -1
- package/src/lib/patch-preflight.js +32 -32
- package/src/lib/principal.js +13 -5
- package/src/lib/security.js +42 -44
- package/src/lib/sentinel-draft.js +59 -3
- package/src/lib/server-tools.js +3 -3
- package/src/lib/transports.js +153 -0
- package/src/lib/version-skew.js +193 -0
- package/src/lib/wizard.js +902 -0
- package/src/lib/workflow-prompts.js +5 -5
- package/src/tools/codegen.js +2 -2
- package/src/tools/config.js +11 -6
- package/src/tools/entities.js +1 -1
- package/src/tools/index.js +1 -1
- package/src/tools/media.js +2 -2
- package/src/tools/nodes.js +5 -3
- package/src/tools/reports-config.js +3 -3
- package/src/tools/reports-content.js +4 -2
- package/src/tools/revisions.js +2 -1
- package/src/tools/scheduler.js +97 -10
- package/src/tools/structure.js +4 -4
- package/src/tools/translations.js +7 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connector vs site version-skew detection (DEV-766).
|
|
3
|
+
*
|
|
4
|
+
* Used by doctor and the wizard when a readiness probe reports versions.
|
|
5
|
+
* Never auto-upgrades. Skip (do not fail) when the site reports nothing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CLIENT_VERSION } from "./config.js";
|
|
9
|
+
|
|
10
|
+
/** Integration-contract 1.1 needs mcp_sentinel ≥ 2.9.0 when a version is visible. */
|
|
11
|
+
export const SENTINEL_MIN_FOR_CONTRACT = "2.9.0";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Compare dotted numeric versions (`1.2.3`). Non-numeric suffixes are ignored.
|
|
15
|
+
* @param {string} a
|
|
16
|
+
* @param {string} b
|
|
17
|
+
* @returns {number} Negative when a < b.
|
|
18
|
+
*/
|
|
19
|
+
export function compareSemver(a, b) {
|
|
20
|
+
const parse = (value) => String(value).replace(/^v/i, "").split(".").map((part) => {
|
|
21
|
+
const n = parseInt(part, 10);
|
|
22
|
+
return Number.isFinite(n) ? n : 0;
|
|
23
|
+
});
|
|
24
|
+
const left = parse(a);
|
|
25
|
+
const right = parse(b);
|
|
26
|
+
const len = Math.max(left.length, right.length, 3);
|
|
27
|
+
for (let i = 0; i < len; i++) {
|
|
28
|
+
const l = left[i] || 0;
|
|
29
|
+
const r = right[i] || 0;
|
|
30
|
+
if (l < r) return -1;
|
|
31
|
+
if (l > r) return 1;
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pull version fields from a readiness body / response headers when present.
|
|
38
|
+
* @param {object|null} body
|
|
39
|
+
* @param {{get?: function(string): string|null}|undefined} headers
|
|
40
|
+
* @returns {{sentinelVersion: string|null, minConnectorVersion: string|null, siteConnectorVersion: string|null}}
|
|
41
|
+
*/
|
|
42
|
+
export function extractReportedVersions(body, headers) {
|
|
43
|
+
const headerGet = typeof headers?.get === "function"
|
|
44
|
+
? (name) => headers.get(name)
|
|
45
|
+
: () => null;
|
|
46
|
+
const sentinelVersion = firstString(
|
|
47
|
+
body?.sentinel_version,
|
|
48
|
+
body?.versions?.sentinel,
|
|
49
|
+
body?.mcp_sentinel,
|
|
50
|
+
headerGet("x-mcp-sentinel-version"),
|
|
51
|
+
headerGet("x-drupal-mcp-sentinel-version"),
|
|
52
|
+
);
|
|
53
|
+
const minConnectorVersion = firstString(
|
|
54
|
+
body?.min_connector_version,
|
|
55
|
+
body?.connector_min_version,
|
|
56
|
+
body?.compatible_connector,
|
|
57
|
+
body?.versions?.min_connector,
|
|
58
|
+
);
|
|
59
|
+
const siteConnectorVersion = firstString(
|
|
60
|
+
body?.connector_version,
|
|
61
|
+
body?.versions?.connector,
|
|
62
|
+
headerGet("x-mcp-connector-version"),
|
|
63
|
+
);
|
|
64
|
+
return { sentinelVersion, minConnectorVersion, siteConnectorVersion };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Detect connector vs site version skew when the site reports versions.
|
|
69
|
+
* @param {object} [reported] extractReportedVersions() result.
|
|
70
|
+
* @param {string} [connectorVersion]
|
|
71
|
+
* @returns {{skew: boolean, detectable: boolean, detail: string, nextFix: string|null, youAreOn: string, siteNeeds: string|null}}
|
|
72
|
+
*/
|
|
73
|
+
export function detectVersionSkew(reported, connectorVersion = CLIENT_VERSION) {
|
|
74
|
+
const youAreOn = `connector ${connectorVersion} (integration contract 1.1)`;
|
|
75
|
+
const { sentinelVersion, minConnectorVersion, siteConnectorVersion } = reported ?? {};
|
|
76
|
+
|
|
77
|
+
if (minConnectorVersion && compareSemver(connectorVersion, minConnectorVersion) < 0) {
|
|
78
|
+
return {
|
|
79
|
+
skew: true,
|
|
80
|
+
detectable: true,
|
|
81
|
+
youAreOn,
|
|
82
|
+
siteNeeds: `connector >= ${minConnectorVersion}`,
|
|
83
|
+
detail: `You are on ${youAreOn}; this site asks for connector >= ${minConnectorVersion}.`,
|
|
84
|
+
nextFix:
|
|
85
|
+
`Upgrade this package (npm i -g drupal-mcp-connector@${minConnectorVersion} or newer). ` +
|
|
86
|
+
"Doctor/wizard never auto-upgrade.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (sentinelVersion && compareSemver(sentinelVersion, SENTINEL_MIN_FOR_CONTRACT) < 0) {
|
|
91
|
+
return {
|
|
92
|
+
skew: true,
|
|
93
|
+
detectable: true,
|
|
94
|
+
youAreOn,
|
|
95
|
+
siteNeeds: `mcp_sentinel >= ${SENTINEL_MIN_FOR_CONTRACT}`,
|
|
96
|
+
detail:
|
|
97
|
+
`You are on ${youAreOn}; site MCP Sentinel is ${sentinelVersion}, ` +
|
|
98
|
+
`which is below ${SENTINEL_MIN_FOR_CONTRACT} required for contract 1.1.`,
|
|
99
|
+
nextFix:
|
|
100
|
+
"Upgrade mcp_sentinel on the Drupal site to " +
|
|
101
|
+
`${SENTINEL_MIN_FOR_CONTRACT} or newer (composer require drupal/mcp_sentinel). ` +
|
|
102
|
+
"Doctor/wizard never auto-upgrade.",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (siteConnectorVersion) {
|
|
107
|
+
const youMajor = String(connectorVersion).split(".")[0];
|
|
108
|
+
const siteMajor = String(siteConnectorVersion).split(".")[0];
|
|
109
|
+
if (youMajor !== siteMajor) {
|
|
110
|
+
return {
|
|
111
|
+
skew: true,
|
|
112
|
+
detectable: true,
|
|
113
|
+
youAreOn,
|
|
114
|
+
siteNeeds: `connector ${siteConnectorVersion} (same major)`,
|
|
115
|
+
detail:
|
|
116
|
+
`You are on ${youAreOn}; the site reported connector ${siteConnectorVersion} ` +
|
|
117
|
+
"(different major — treat as skew).",
|
|
118
|
+
nextFix:
|
|
119
|
+
"Align connector and site majors before relying on new contract fields. " +
|
|
120
|
+
"Doctor/wizard never auto-upgrade.",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (sentinelVersion || minConnectorVersion || siteConnectorVersion) {
|
|
126
|
+
const bits = [
|
|
127
|
+
sentinelVersion ? `Sentinel ${sentinelVersion}` : null,
|
|
128
|
+
siteConnectorVersion ? `site connector ${siteConnectorVersion}` : null,
|
|
129
|
+
minConnectorVersion ? `min connector ${minConnectorVersion}` : null,
|
|
130
|
+
].filter(Boolean);
|
|
131
|
+
return {
|
|
132
|
+
skew: false,
|
|
133
|
+
detectable: true,
|
|
134
|
+
youAreOn,
|
|
135
|
+
siteNeeds: bits.join(", ") || null,
|
|
136
|
+
detail: `No skew detected. You are on ${youAreOn}; site reports ${bits.join(", ")}.`,
|
|
137
|
+
nextFix: null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
skew: false,
|
|
143
|
+
detectable: false,
|
|
144
|
+
youAreOn,
|
|
145
|
+
siteNeeds: null,
|
|
146
|
+
detail: `You are on ${youAreOn}. This site did not report a Sentinel/connector version, so skew was not checked.`,
|
|
147
|
+
nextFix: null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Doctor/wizard check row for a version-skew result.
|
|
153
|
+
* @param {ReturnType<typeof detectVersionSkew>} report
|
|
154
|
+
* @returns {{id: string, title: string, status: "pass"|"fail"|"skipped", detail: string, nextFix: string|null}}
|
|
155
|
+
*/
|
|
156
|
+
export function versionSkewCheck(report) {
|
|
157
|
+
if (!report.detectable) {
|
|
158
|
+
return {
|
|
159
|
+
id: "version_skew",
|
|
160
|
+
title: "Connector / site version skew",
|
|
161
|
+
status: "skipped",
|
|
162
|
+
detail: report.detail,
|
|
163
|
+
nextFix: null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (report.skew) {
|
|
167
|
+
return {
|
|
168
|
+
id: "version_skew",
|
|
169
|
+
title: "Connector / site version skew",
|
|
170
|
+
status: "fail",
|
|
171
|
+
detail: report.detail,
|
|
172
|
+
nextFix: report.nextFix,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
id: "version_skew",
|
|
177
|
+
title: "Connector / site version skew",
|
|
178
|
+
status: "pass",
|
|
179
|
+
detail: report.detail,
|
|
180
|
+
nextFix: null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {...*} values
|
|
186
|
+
* @returns {string|null}
|
|
187
|
+
*/
|
|
188
|
+
function firstString(...values) {
|
|
189
|
+
for (const value of values) {
|
|
190
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|