drupal-mcp-connector 2.7.2 → 2.7.3
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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/index.js +5 -2
- package/src/lib/load-secrets.js +40 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.7.3] - 2026-08-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Launcher and startup name a secret-table / config mismatch (#211).**
|
|
14
|
+
The shipped Keychain table matches `config/config.example.json`. A
|
|
15
|
+
`config.json` that uses different `clientSecretEnv` names without a
|
|
16
|
+
`config/secrets.map` left the table and the config each valid and
|
|
17
|
+
jointly inert. Per-item Keychain misses stay silent (break-glass).
|
|
18
|
+
When the table matches **no** named secret, `bin/drupal-mcp-launch.sh`
|
|
19
|
+
and `src/lib/load-secrets.js` now print one stderr line that names
|
|
20
|
+
the unmapped variables and says `secrets.map` is absent (or that the
|
|
21
|
+
map does not name them). Fail-closed start when every named secret is
|
|
22
|
+
unset is unchanged.
|
|
23
|
+
|
|
10
24
|
## [2.7.2] - 2026-08-18
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
Built by **Jeremy Michael Cerda** (opensource@wilkesliberty.com). Maintained by [Wilkes & Liberty, LLC](https://github.com/Wilkes-Liberty).
|
|
11
11
|
|
|
12
|
-
**If the client only shows `drupal_list_sites` and `drupal_governance_status`**, the secret env vars named in `config.json` are unset. Upgrade to **2.7.
|
|
12
|
+
**If the client only shows `drupal_list_sites` and `drupal_governance_status`**, the secret env vars named in `config.json` are unset. Upgrade to **2.7.3** (or at least 2.6.1), or stay on 2.6.0 and launch via `bin/drupal-mcp-launch.sh` with a `config/secrets.map` (`ENV_VAR=keychain-item`). Then restart the MCP server. See [#199](https://github.com/Wilkes-Liberty/drupal-mcp-connector/issues/199).
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
34
34
|
import { toNodeHandler } from "@modelcontextprotocol/node";
|
|
35
35
|
|
|
36
36
|
import { listSiteNames, getTlsConfig, loadConfig, CLIENT_VERSION } from "./lib/config.js";
|
|
37
|
-
import { loadLocalSecrets, secretLoadFatalMessage } from "./lib/load-secrets.js";
|
|
37
|
+
import { loadLocalSecrets, secretLoadFatalMessage, secretTableMismatchMessage } from "./lib/load-secrets.js";
|
|
38
38
|
import {
|
|
39
39
|
makeBearerCheck,
|
|
40
40
|
resolveInboundAuthConfig,
|
|
@@ -68,7 +68,10 @@ if (secretFatal) {
|
|
|
68
68
|
console.error(`[drupal-mcp-connector] FATAL: ${secretFatal}`);
|
|
69
69
|
process.exit(1);
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
const secretMismatch = secretTableMismatchMessage(secretLoad);
|
|
72
|
+
if (secretMismatch) {
|
|
73
|
+
console.error(`[drupal-mcp-connector] WARNING: ${secretMismatch}`);
|
|
74
|
+
} else if (secretLoad.unset.length) {
|
|
72
75
|
console.error(
|
|
73
76
|
"[drupal-mcp-connector] WARNING: config.json names secret env vars that are unset: " +
|
|
74
77
|
`${secretLoad.unset.join(", ")}. Those sites will fail closed.`
|
package/src/lib/load-secrets.js
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
* The default table matches config/config.example.json. A gitignored
|
|
10
10
|
* config/secrets.map replaces that table for a deployment whose env-var
|
|
11
11
|
* names differ. Per-item Keychain misses stay silent (inert break-glass).
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Zero overlap between the table and the names in config.json is a
|
|
13
|
+
* distinct case — the two files are jointly inert — and the caller should
|
|
14
|
+
* say so. If every named secret is still unset after this step, the caller
|
|
15
|
+
* must refuse to start.
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
import { execFileSync } from "node:child_process";
|
|
@@ -101,7 +103,7 @@ export function lookupKeychainItem(item) {
|
|
|
101
103
|
* @param {NodeJS.ProcessEnv} [options.env] Mutated when a lookup succeeds.
|
|
102
104
|
* @param {typeof readFileSync} [options.readFile]
|
|
103
105
|
* @param {(item: string) => string} [options.lookup]
|
|
104
|
-
* @returns {{pairs: number, resolved: number, named: string[], unset: string[]}}
|
|
106
|
+
* @returns {{pairs: number, resolved: number, named: string[], unset: string[], tableVars: string[], source: "map"|"default"}}
|
|
105
107
|
*/
|
|
106
108
|
export function loadLocalSecrets({
|
|
107
109
|
cwd = process.cwd(),
|
|
@@ -109,11 +111,13 @@ export function loadLocalSecrets({
|
|
|
109
111
|
readFile = readFileSync,
|
|
110
112
|
lookup = lookupKeychainItem,
|
|
111
113
|
} = {}) {
|
|
114
|
+
let source = "map";
|
|
112
115
|
let pairs;
|
|
113
116
|
try {
|
|
114
117
|
pairs = parseSecretMap(readFile(join(cwd, "config", "secrets.map"), "utf8"));
|
|
115
118
|
} catch {
|
|
116
119
|
pairs = DEFAULT_SECRET_PAIRS;
|
|
120
|
+
source = "default";
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
let resolved = 0;
|
|
@@ -140,16 +144,47 @@ export function loadLocalSecrets({
|
|
|
140
144
|
|
|
141
145
|
const envMap = new Map(Object.entries(env));
|
|
142
146
|
const unset = named.filter((name) => !envMap.get(name));
|
|
143
|
-
|
|
147
|
+
const tableVars = pairs.map(([varName]) => varName);
|
|
148
|
+
return { pairs: pairs.length, resolved, named, unset, tableVars, source };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* One-line diagnosis when the secret table and config.json share no names.
|
|
153
|
+
* Null when there is any overlap, or when config names no secrets.
|
|
154
|
+
* Per-item Keychain misses are not a mismatch.
|
|
155
|
+
* @param {{named: string[], tableVars?: string[], source?: "map"|"default"}} loaded
|
|
156
|
+
* @returns {string|null}
|
|
157
|
+
*/
|
|
158
|
+
export function secretTableMismatchMessage(loaded) {
|
|
159
|
+
if (!loaded.named.length) return null;
|
|
160
|
+
const table = new Set(loaded.tableVars || []);
|
|
161
|
+
const unmapped = loaded.named.filter((name) => !table.has(name));
|
|
162
|
+
if (unmapped.length !== loaded.named.length) return null;
|
|
163
|
+
const source = loaded.source === "map"
|
|
164
|
+
? "config/secrets.map does not name them"
|
|
165
|
+
: "using shipped defaults; config/secrets.map is absent";
|
|
166
|
+
const unset = Array.isArray(loaded.unset) ? loaded.unset : [];
|
|
167
|
+
const closer = unset.length
|
|
168
|
+
? "Unset: " + unset.join(", ") + ". Those sites will fail closed."
|
|
169
|
+
: "Named secrets already in the environment stay set; the table will not populate any of them.";
|
|
170
|
+
return (
|
|
171
|
+
"no secret-table entries match clientSecretEnv/apiTokenEnv names in config.json " +
|
|
172
|
+
"(" + unmapped.join(", ") + "); " + source + ". " +
|
|
173
|
+
closer
|
|
174
|
+
);
|
|
144
175
|
}
|
|
145
176
|
|
|
146
177
|
/**
|
|
147
178
|
* Refuse to boot a server that can only advertise diagnostic tools.
|
|
148
|
-
* @param {{named: string[], unset: string[]}} loaded
|
|
179
|
+
* @param {{named: string[], unset: string[], tableVars?: string[], source?: "map"|"default"}} loaded
|
|
149
180
|
* @returns {string|null} Fatal message, or null when start is allowed.
|
|
150
181
|
*/
|
|
151
182
|
export function secretLoadFatalMessage(loaded) {
|
|
152
183
|
if (!loaded.named.length || loaded.unset.length !== loaded.named.length) return null;
|
|
184
|
+
const mismatch = secretTableMismatchMessage(loaded);
|
|
185
|
+
if (mismatch) {
|
|
186
|
+
return mismatch + " Refusing to start. Map those names in config/secrets.map (ENV_VAR=keychain-item) or export them before launch.";
|
|
187
|
+
}
|
|
153
188
|
return (
|
|
154
189
|
`every clientSecretEnv/apiTokenEnv named in config.json is unset (${loaded.unset.join(", ")}). ` +
|
|
155
190
|
"Refusing to start. Map those names in config/secrets.map (ENV_VAR=keychain-item) " +
|