@getmarrow/install 0.1.54 → 0.1.56
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/README.md +17 -7
- package/package.json +1 -1
- package/src/installer.js +545 -88
package/README.md
CHANGED
|
@@ -75,17 +75,19 @@ Tool visibility is not authorization. Every visible call still reaches Marrow's
|
|
|
75
75
|
Marrow's hosted API, website, and dashboard update automatically; local SDK dependencies, generated runtime files, MCP hooks/configuration, and pinned package versions do not silently rewrite themselves. Keeping them current delivers new client-side features, compatibility improvements, and any published security fixes. Supported clients report their package version during authenticated status/runtime activity, and Marrow returns a `client_update` notice with the exact action when the version is behind or unknown.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
npx -y @getmarrow/install@latest
|
|
78
|
+
# When doctor reports stale or mixed MCP configuration
|
|
79
|
+
npx -y @getmarrow/install@latest update
|
|
80
|
+
|
|
81
|
+
# Restart the detected owning harnesses once, then verify
|
|
82
|
+
npx -y @getmarrow/install@latest doctor --self-test
|
|
80
83
|
|
|
81
84
|
# Measured API read health and local backlog
|
|
82
85
|
npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
|
|
83
|
-
|
|
84
|
-
# Use only when doctor reports drift
|
|
85
|
-
npx -y @getmarrow/install@latest --repair
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`
|
|
88
|
+
`update` resolves official npm metadata once, selects one exact verified MCP target, and synchronizes every Marrow-managed surface in the detected owning workspace while retaining unrelated user hooks and configuration. Restart the detected owning harnesses once after it completes; running processes do not change before that restart. Run the doctor verification once after restart. Do not run separate `marrow-mcp setup` and restart cycles for the same detected workspace.
|
|
89
|
+
|
|
90
|
+
`activate` remains available for initial activation. After explicit activation, the local controller can restore drifted Marrow-managed hooks and configuration. Package upgrades, owner policy, credentials, explicitly disabled hooks, and unrelated local files remain explicit and subject to the operator's normal change policy.
|
|
89
91
|
|
|
90
92
|
## Automatic Controller
|
|
91
93
|
|
|
@@ -99,7 +101,15 @@ npx @getmarrow/install controller stop
|
|
|
99
101
|
|
|
100
102
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still writes supported configuration and verifies one server-side install self-test without certifying that hooks continuously ran; run `npx @getmarrow/install sidecar` under an owner-managed service and pass `--no-controller`. The controller does not silently upgrade packages, change governance policy, rotate credentials, or modify unrelated project configuration.
|
|
101
103
|
|
|
102
|
-
## What's New in v0.1.
|
|
104
|
+
## What's New in v0.1.56
|
|
105
|
+
|
|
106
|
+
v0.1.56 pins the officially verified MCP `3.9.80` release from source `ee1eda3f201965a6530256accbdf175660dd8ad6` with registry integrity `sha512-uou3X18pESV39EMmddDrYN7yd6MrZzosrnQ1eRtvK5j3yuBLAlupj5kQVrrKEWAL6xwuLebXq2isivK2O/2WrA==`; SDK remains `3.7.62`. Doctor now directs stale, mixed, or version-unknown MCP installations through one `npx -y @getmarrow/install@latest update`, followed by one owning-harness restart and one `doctor --self-test` verification. The update resolves one official target and applies it consistently across detected Marrow-managed instructions, MCP launch configuration, and supported native hooks without claiming that already-running processes changed.
|
|
107
|
+
|
|
108
|
+
## Previous: v0.1.55
|
|
109
|
+
|
|
110
|
+
v0.1.55 pins sealed MCP candidate `3.9.79` from source `11f00049043d0aba90704ecbf69f32d2278a4573` for ordinary setup, generated launch configuration, and native hooks. Doctor, update, and repair retain exact-version resolution and never propagate a newer local version unless official registry metadata verifies it; offline operation preserves unverified-ahead owner surfaces and uses the sealed candidate for new managed targets.
|
|
111
|
+
|
|
112
|
+
## Previous: v0.1.54
|
|
103
113
|
|
|
104
114
|
v0.1.54 pins sealed MCP candidate `3.9.77` from source `1e782d8ba6bbb54bfaa322f0300565c7176f1969` and makes ordinary setup use the primary tool surface without writing a profile variable. Explicit `core` and `full` selections remain preserved, invalid values fail closed with a bounded repair, and human/JSON self-test output distinguishes configured/effective profile, expected visibility, actual post-reload visibility, and backend-projected entitlement state. Backend projections are status evidence only and always report `authorizes_calls: false`.
|
|
105
115
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -10,9 +10,9 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
10
10
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
11
11
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
12
12
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
13
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
14
|
-
const MCP_ADAPTER_SOURCE_SHA = '
|
|
15
|
-
const MCP_ADAPTER_INTEGRITY = 'sha512-
|
|
13
|
+
const MCP_ADAPTER_VERSION = '3.9.80';
|
|
14
|
+
const MCP_ADAPTER_SOURCE_SHA = 'ee1eda3f201965a6530256accbdf175660dd8ad6';
|
|
15
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-uou3X18pESV39EMmddDrYN7yd6MrZzosrnQ1eRtvK5j3yuBLAlupj5kQVrrKEWAL6xwuLebXq2isivK2O/2WrA==';
|
|
16
16
|
const SDK_ADAPTER_VERSION = '3.7.62';
|
|
17
17
|
const SDK_ADAPTER_INTEGRITY = 'sha512-n1i6Be09TpAQ9BPNRKY7aCvA2iSUPpJfw8djw2MELwpNbBCtKiZ29Jji77BK/6EFLUpSIcTW/Gmdf/ccf0JRYQ==';
|
|
18
18
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
@@ -31,6 +31,196 @@ const ADAPTER_PROVENANCE = Object.freeze({
|
|
|
31
31
|
integrity: SDK_ADAPTER_INTEGRITY,
|
|
32
32
|
}),
|
|
33
33
|
});
|
|
34
|
+
const MCP_REGISTRY_LATEST_URL = 'https://registry.npmjs.org/%40getmarrow%2Fmcp/latest';
|
|
35
|
+
const MCP_REGISTRY_VERIFICATION_COMMAND = 'npm view @getmarrow/mcp@latest name version dist.integrity dist.tarball --json --registry=https://registry.npmjs.org';
|
|
36
|
+
const INSTALLER_UPDATE_COMMAND = 'npx -y @getmarrow/install@latest update';
|
|
37
|
+
const INSTALLER_DOCTOR_COMMAND = 'npx -y @getmarrow/install@latest doctor --self-test';
|
|
38
|
+
const INSTALLER_RESTART_INSTRUCTION = 'After the update completes, restart the detected owning harnesses once. Running Marrow MCP processes remain unchanged until that restart.';
|
|
39
|
+
const MCP_STABLE_VERSION_RE = /^(\d{1,6})\.(\d{1,6})\.(\d{1,9})$/;
|
|
40
|
+
const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
41
|
+
const VERIFIED_MCP_EXECUTABLE_TARGET = Symbol('verified_mcp_executable_target');
|
|
42
|
+
|
|
43
|
+
function validSha512Integrity(value) {
|
|
44
|
+
if (!SHA512_INTEGRITY_RE.test(value)) return false;
|
|
45
|
+
const encoded = value.slice('sha512-'.length);
|
|
46
|
+
try {
|
|
47
|
+
const digest = Buffer.from(encoded, 'base64');
|
|
48
|
+
return digest.length === 64 && digest.toString('base64') === encoded;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parsedStableMcpVersion(value) {
|
|
55
|
+
const match = String(value || '').match(MCP_STABLE_VERSION_RE);
|
|
56
|
+
if (!match) return null;
|
|
57
|
+
const parts = match.slice(1).map(Number);
|
|
58
|
+
return parts.every(Number.isSafeInteger) ? parts : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function compareMcpVersions(left, right) {
|
|
62
|
+
const leftParts = parsedStableMcpVersion(left);
|
|
63
|
+
const rightParts = parsedStableMcpVersion(right);
|
|
64
|
+
if (!leftParts || !rightParts) return null;
|
|
65
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
66
|
+
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index];
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function compatibleMcpTargetVersion(value) {
|
|
72
|
+
const candidate = parsedStableMcpVersion(value);
|
|
73
|
+
const sealed = parsedStableMcpVersion(MCP_ADAPTER_VERSION);
|
|
74
|
+
return Boolean(candidate && sealed
|
|
75
|
+
&& candidate[0] === sealed[0]
|
|
76
|
+
&& candidate[1] === sealed[1]
|
|
77
|
+
&& compareMcpVersions(value, MCP_ADAPTER_VERSION) >= 0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mcpVersionsInText(value) {
|
|
81
|
+
return [...String(value || '').matchAll(/@getmarrow\/mcp@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/g)]
|
|
82
|
+
.map((match) => match[1]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function marrowManagedBlockInText(value) {
|
|
86
|
+
const content = String(value || '');
|
|
87
|
+
const start = content.indexOf(MARROW_BLOCK_START);
|
|
88
|
+
if (start < 0) return '';
|
|
89
|
+
const end = content.indexOf(MARROW_BLOCK_END, start + MARROW_BLOCK_START.length);
|
|
90
|
+
if (end < 0) return '';
|
|
91
|
+
return content.slice(start, end + MARROW_BLOCK_END.length);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function unverifiedAheadMcpVersions(versions, targetVersion = MCP_ADAPTER_VERSION) {
|
|
95
|
+
if (!parsedStableMcpVersion(targetVersion)) return [];
|
|
96
|
+
return [...new Set((Array.isArray(versions) ? versions : [])
|
|
97
|
+
.filter((version) => parsedStableMcpVersion(version)
|
|
98
|
+
&& compareMcpVersions(version, targetVersion) > 0))].sort((left, right) => compareMcpVersions(left, right));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function mcpRegistryVerificationAction(versions) {
|
|
102
|
+
const ahead = [...new Set(Array.isArray(versions) ? versions : [])].sort((left, right) => compareMcpVersions(left, right));
|
|
103
|
+
const targets = ahead.map((version) => `@getmarrow/mcp@${version}`).join(', ');
|
|
104
|
+
return `Run ${MCP_REGISTRY_VERIFICATION_COMMAND} with official npm registry access, then rerun npx -y @getmarrow/install@latest doctor --self-test. Automatic repair is suppressed${targets ? ` for ${targets}` : ''}; preserve each existing surface until registry metadata verifies it or the owner chooses the sealed or verified version.`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function verifiedMcpRegistryMetadata(value) {
|
|
108
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
109
|
+
const version = typeof value.version === 'string' ? value.version : '';
|
|
110
|
+
const integrity = typeof value.dist?.integrity === 'string' ? value.dist.integrity : '';
|
|
111
|
+
const tarball = typeof value.dist?.tarball === 'string' ? value.dist.tarball : '';
|
|
112
|
+
const expectedTarball = `https://registry.npmjs.org/@getmarrow/mcp/-/mcp-${version}.tgz`;
|
|
113
|
+
if (value.name !== '@getmarrow/mcp'
|
|
114
|
+
|| !compatibleMcpTargetVersion(version)
|
|
115
|
+
|| !validSha512Integrity(integrity)
|
|
116
|
+
|| tarball !== expectedTarball) return null;
|
|
117
|
+
return { version, integrity, tarball };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveMcpTargetVersion(options = {}) {
|
|
121
|
+
const registry = verifiedMcpRegistryMetadata(options.registryMetadata);
|
|
122
|
+
if (registry) {
|
|
123
|
+
const target = {
|
|
124
|
+
version: registry.version,
|
|
125
|
+
source: 'verified_npm_registry',
|
|
126
|
+
integrity: registry.integrity,
|
|
127
|
+
source_sha: null,
|
|
128
|
+
};
|
|
129
|
+
Object.defineProperty(target, VERIFIED_MCP_EXECUTABLE_TARGET, { value: true });
|
|
130
|
+
return target;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
version: MCP_ADAPTER_VERSION,
|
|
134
|
+
source: 'sealed_installer',
|
|
135
|
+
integrity: MCP_ADAPTER_INTEGRITY,
|
|
136
|
+
source_sha: MCP_ADAPTER_SOURCE_SHA,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function executableMcpTarget(options = {}) {
|
|
141
|
+
const target = options.mcpTarget;
|
|
142
|
+
if (target?.[VERIFIED_MCP_EXECUTABLE_TARGET] === true
|
|
143
|
+
&& target.source === 'verified_npm_registry'
|
|
144
|
+
&& compatibleMcpTargetVersion(target.version)
|
|
145
|
+
&& validSha512Integrity(target.integrity)
|
|
146
|
+
&& target.source_sha === null) return target;
|
|
147
|
+
return resolveMcpTargetVersion();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function expectedMcpInspectionVersion(options = {}) {
|
|
151
|
+
if (options.expectedVersion === MCP_ADAPTER_VERSION) return MCP_ADAPTER_VERSION;
|
|
152
|
+
return executableMcpTarget({ mcpTarget: options.expectedTarget }).version;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function readMcpRegistryMetadata(options = {}) {
|
|
156
|
+
if (Object.prototype.hasOwnProperty.call(options, 'mcpRegistryMetadata')) {
|
|
157
|
+
return options.mcpRegistryMetadata;
|
|
158
|
+
}
|
|
159
|
+
if (options.resolveMcpRegistry !== true) return null;
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
const timer = setTimeout(() => controller.abort(), 2500);
|
|
162
|
+
try {
|
|
163
|
+
const fetcher = typeof options.registryFetch === 'function' ? options.registryFetch : fetch;
|
|
164
|
+
const response = await fetcher(MCP_REGISTRY_LATEST_URL, {
|
|
165
|
+
headers: { accept: 'application/json' },
|
|
166
|
+
redirect: 'error',
|
|
167
|
+
signal: controller.signal,
|
|
168
|
+
});
|
|
169
|
+
if (!response.ok) return null;
|
|
170
|
+
return await response.json();
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
} finally {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function adapterProvenanceForMcpTarget(target) {
|
|
179
|
+
if (!target || target.version === MCP_ADAPTER_VERSION) return ADAPTER_PROVENANCE;
|
|
180
|
+
return {
|
|
181
|
+
mcp: {
|
|
182
|
+
package: '@getmarrow/mcp',
|
|
183
|
+
version: target.version,
|
|
184
|
+
source_sha: target.source_sha,
|
|
185
|
+
integrity: target.integrity,
|
|
186
|
+
integrity_state: target.source === 'verified_npm_registry'
|
|
187
|
+
? 'verified_npm_registry_metadata'
|
|
188
|
+
: 'exact_current_configuration',
|
|
189
|
+
},
|
|
190
|
+
sdk: ADAPTER_PROVENANCE.sdk,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function retargetMcpPackageSpec(value, version) {
|
|
195
|
+
const target = compatibleMcpTargetVersion(version) ? version : MCP_ADAPTER_VERSION;
|
|
196
|
+
return String(value).split(MCP_PACKAGE_SPEC).join(`@getmarrow/mcp@${target}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function retargetMcpDowngradeRecommendation(value, targetVersion) {
|
|
200
|
+
if (typeof value !== 'string' || !compatibleMcpTargetVersion(targetVersion)) return value;
|
|
201
|
+
return value.replace(/@getmarrow\/mcp@(\d+\.\d+\.\d+)/g, (match, version) => (
|
|
202
|
+
parsedStableMcpVersion(version) && version !== targetVersion
|
|
203
|
+
? `@getmarrow/mcp@${targetVersion}`
|
|
204
|
+
: match
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function alignMcpRecommendationVersions(value, targetVersion, key = '', verificationAction = null) {
|
|
209
|
+
if (Array.isArray(value)) {
|
|
210
|
+
return value.map((entry) => alignMcpRecommendationVersions(entry, targetVersion, key, verificationAction));
|
|
211
|
+
}
|
|
212
|
+
if (!value || typeof value !== 'object') {
|
|
213
|
+
return /(?:command|fix|instruction|next_action|notice)$/i.test(key)
|
|
214
|
+
? verificationAction && typeof value === 'string' && /@getmarrow\/mcp@\d+\.\d+\.\d+/.test(value)
|
|
215
|
+
? verificationAction
|
|
216
|
+
: retargetMcpDowngradeRecommendation(value, targetVersion)
|
|
217
|
+
: value;
|
|
218
|
+
}
|
|
219
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
220
|
+
entryKey,
|
|
221
|
+
alignMcpRecommendationVersions(entryValue, targetVersion, entryKey, verificationAction),
|
|
222
|
+
]));
|
|
223
|
+
}
|
|
34
224
|
const MCP_CONTEXT_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp context-hook`;
|
|
35
225
|
const MCP_PRE_ACTION_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp pre-action-hook`;
|
|
36
226
|
const MCP_ACTION_RESULT_HOOK_COMMAND = `npx -y --package=${MCP_PACKAGE_SPEC} marrow-mcp hook`;
|
|
@@ -366,34 +556,55 @@ function inspectMcpProcesses(options = {}) {
|
|
|
366
556
|
.filter(isMcpProcessCommand)
|
|
367
557
|
.map((command) => explicitMcpVersion(command) || packageMcpVersion(command) || 'unknown');
|
|
368
558
|
const versions = [...new Set(active.filter((version) => version !== 'unknown'))].sort();
|
|
559
|
+
const expectedVersion = expectedMcpInspectionVersion(options);
|
|
369
560
|
const unknownVersionProcesses = active.filter((version) => version === 'unknown').length;
|
|
370
|
-
const
|
|
561
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(versions, expectedVersion);
|
|
562
|
+
const staleVersions = versions.filter((version) => version !== expectedVersion
|
|
563
|
+
&& !aheadUnverifiedVersions.includes(version));
|
|
371
564
|
const mixedVersions = versions.length > 1 || (versions.length > 0 && unknownVersionProcesses > 0);
|
|
372
565
|
const stale = staleVersions.length > 0;
|
|
373
|
-
const
|
|
374
|
-
const
|
|
566
|
+
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
567
|
+
const needsRepair = stale || mixedVersions || unknownVersionProcesses > 0 || aheadUnverified;
|
|
568
|
+
const automaticRepairSuppressed = aheadUnverified;
|
|
375
569
|
return {
|
|
376
570
|
available: process.platform === 'linux' || Array.isArray(options.commands),
|
|
377
|
-
expected_version:
|
|
571
|
+
expected_version: expectedVersion,
|
|
378
572
|
active_processes: active.length,
|
|
379
573
|
active_versions: versions,
|
|
380
574
|
unknown_version_processes: unknownVersionProcesses,
|
|
381
575
|
stale_versions: staleVersions,
|
|
576
|
+
ahead_unverified: aheadUnverified,
|
|
577
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
382
578
|
mixed_versions: mixedVersions,
|
|
383
579
|
healthy: !needsRepair,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
580
|
+
automatic_repair_suppressed: automaticRepairSuppressed,
|
|
581
|
+
registry_verification_required: aheadUnverified,
|
|
582
|
+
exact_fix: automaticRepairSuppressed
|
|
583
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
584
|
+
: needsRepair ? INSTALLER_UPDATE_COMMAND : null,
|
|
585
|
+
restart_required: needsRepair && !automaticRepairSuppressed,
|
|
586
|
+
restart_instruction: needsRepair && !automaticRepairSuppressed
|
|
587
|
+
? INSTALLER_RESTART_INSTRUCTION
|
|
588
|
+
: null,
|
|
589
|
+
verification_command: needsRepair && !automaticRepairSuppressed ? INSTALLER_DOCTOR_COMMAND : null,
|
|
388
590
|
};
|
|
389
591
|
}
|
|
390
592
|
|
|
391
593
|
function inspectMcpConfigurations(detection, options = {}) {
|
|
392
594
|
const home = options.home || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
393
595
|
const configuredPaths = Array.isArray(options.paths) ? options.paths : [
|
|
596
|
+
detection?.paths?.agentsMd,
|
|
597
|
+
detection?.paths?.mcpJson,
|
|
394
598
|
detection?.paths?.claudeSettings,
|
|
599
|
+
detection?.paths?.codexHooks,
|
|
600
|
+
detection?.paths?.cursorHooks,
|
|
395
601
|
detection?.paths?.cursorMcp,
|
|
396
|
-
detection?.paths?.
|
|
602
|
+
detection?.paths?.clinePreToolUseHook,
|
|
603
|
+
detection?.paths?.clinePostToolUseHook,
|
|
604
|
+
detection?.paths?.clineTaskCancelHook,
|
|
605
|
+
detection?.paths?.windsurfHooks,
|
|
606
|
+
detection?.paths?.geminiSettings,
|
|
607
|
+
detection?.paths?.grokHooks,
|
|
397
608
|
path.join(home, '.claude', 'settings.json'),
|
|
398
609
|
path.join(home, '.claude.json'),
|
|
399
610
|
path.join(home, '.cursor', 'mcp.json'),
|
|
@@ -410,9 +621,11 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
410
621
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 2 * 1024 * 1024) continue;
|
|
411
622
|
filesChecked += 1;
|
|
412
623
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
624
|
+
const inspected = path.basename(filePath) === 'AGENTS.md'
|
|
625
|
+
? marrowManagedBlockInText(raw)
|
|
626
|
+
: raw;
|
|
627
|
+
const specs = mcpVersionsInText(inspected);
|
|
628
|
+
if (/@getmarrow\/mcp(?:@|["'\s])/.test(inspected)) {
|
|
416
629
|
configurationsFound += 1;
|
|
417
630
|
if (specs.length === 0) unknownVersionConfigurations += 1;
|
|
418
631
|
}
|
|
@@ -422,21 +635,36 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
422
635
|
}
|
|
423
636
|
}
|
|
424
637
|
const configuredVersions = [...new Set(versions)].sort();
|
|
425
|
-
const
|
|
638
|
+
const expectedVersion = expectedMcpInspectionVersion(options);
|
|
639
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(configuredVersions, expectedVersion);
|
|
640
|
+
const staleVersions = configuredVersions.filter((version) => version !== expectedVersion
|
|
641
|
+
&& !aheadUnverifiedVersions.includes(version));
|
|
426
642
|
const mixedVersions = configuredVersions.length > 1
|
|
427
643
|
|| (configuredVersions.length > 0 && unknownVersionConfigurations > 0);
|
|
428
|
-
const
|
|
644
|
+
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
645
|
+
const healthy = staleVersions.length === 0 && !mixedVersions
|
|
646
|
+
&& unknownVersionConfigurations === 0 && !aheadUnverified;
|
|
429
647
|
return {
|
|
430
|
-
expected_version:
|
|
648
|
+
expected_version: expectedVersion,
|
|
431
649
|
files_checked: filesChecked,
|
|
432
650
|
configurations_found: configurationsFound,
|
|
433
651
|
configured_versions: configuredVersions,
|
|
434
652
|
unknown_version_configurations: unknownVersionConfigurations,
|
|
435
653
|
stale_versions: staleVersions,
|
|
654
|
+
ahead_unverified: aheadUnverified,
|
|
655
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
436
656
|
mixed_versions: mixedVersions,
|
|
437
657
|
healthy,
|
|
438
|
-
|
|
439
|
-
|
|
658
|
+
automatic_repair_suppressed: aheadUnverified,
|
|
659
|
+
registry_verification_required: aheadUnverified,
|
|
660
|
+
exact_fix: healthy
|
|
661
|
+
? null
|
|
662
|
+
: aheadUnverified
|
|
663
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
664
|
+
: INSTALLER_UPDATE_COMMAND,
|
|
665
|
+
restart_required: !healthy && !aheadUnverified,
|
|
666
|
+
restart_instruction: !healthy && !aheadUnverified ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
667
|
+
verification_command: !healthy && !aheadUnverified ? INSTALLER_DOCTOR_COMMAND : null,
|
|
440
668
|
};
|
|
441
669
|
}
|
|
442
670
|
|
|
@@ -575,6 +803,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
575
803
|
if (options.activate && options.dryRun) {
|
|
576
804
|
throw new Error('activate cannot be combined with --dry-run; use --dry-run without activate to preview changes');
|
|
577
805
|
}
|
|
806
|
+
options.resolveMcpRegistry = Boolean(options.doctor || options.repair || options.update);
|
|
578
807
|
|
|
579
808
|
return options;
|
|
580
809
|
}
|
|
@@ -1486,6 +1715,75 @@ function grokNativeHookFingerprint(settings) {
|
|
|
1486
1715
|
})).digest('hex');
|
|
1487
1716
|
}
|
|
1488
1717
|
|
|
1718
|
+
function grokMarrowHookSubcommand(command) {
|
|
1719
|
+
const direct = marrowHookSubcommand(command);
|
|
1720
|
+
if (direct) return direct;
|
|
1721
|
+
if (typeof command !== 'string') return null;
|
|
1722
|
+
const guarded = command.match(
|
|
1723
|
+
/@getmarrow\/mcp@[^\s"',]+["'],["']marrow-mcp["'],["']grok-(pre-action-hook)["']/,
|
|
1724
|
+
);
|
|
1725
|
+
return guarded?.[1] || null;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function reconcileGrokHook(settings, eventName, subcommand, command, matcher, timeout) {
|
|
1729
|
+
const original = Array.isArray(settings?.hooks?.[eventName]) ? settings.hooks[eventName] : [];
|
|
1730
|
+
const retained = [];
|
|
1731
|
+
let preferredHandler = null;
|
|
1732
|
+
for (const entry of original) {
|
|
1733
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !Array.isArray(entry.hooks)) {
|
|
1734
|
+
retained.push(entry);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
const remaining = [];
|
|
1738
|
+
for (const handler of entry.hooks) {
|
|
1739
|
+
const detected = handler && typeof handler === 'object' && !Array.isArray(handler)
|
|
1740
|
+
&& handler.type === 'command' ? grokMarrowHookSubcommand(handler.command) : null;
|
|
1741
|
+
if (detected) {
|
|
1742
|
+
if (detected === subcommand && !preferredHandler) preferredHandler = handler;
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
remaining.push(handler);
|
|
1746
|
+
}
|
|
1747
|
+
if (remaining.length > 0) retained.push({ ...entry, hooks: remaining });
|
|
1748
|
+
}
|
|
1749
|
+
if (!command) return retained;
|
|
1750
|
+
const canonical = {
|
|
1751
|
+
...(matcher === undefined ? {} : { matcher }),
|
|
1752
|
+
hooks: [{ ...preferredHandler, type: 'command', command, timeout }],
|
|
1753
|
+
};
|
|
1754
|
+
return [...retained, canonical];
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function upsertGrokHooks(settingsPath) {
|
|
1758
|
+
const settings = parseJsonObject(settingsPath);
|
|
1759
|
+
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
1760
|
+
? settings.hooks
|
|
1761
|
+
: {};
|
|
1762
|
+
settings.hooks = {
|
|
1763
|
+
...hooks,
|
|
1764
|
+
UserPromptSubmit: reconcileGrokHook(settings, 'UserPromptSubmit', 'context-hook', GROK_CONTEXT_HOOK_COMMAND, undefined, 5),
|
|
1765
|
+
PreToolUse: reconcileGrokHook(settings, 'PreToolUse', 'pre-action-hook', GROK_PRE_ACTION_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 7),
|
|
1766
|
+
PostToolUse: reconcileGrokHook(settings, 'PostToolUse', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1767
|
+
PostToolUseFailure: reconcileGrokHook(settings, 'PostToolUseFailure', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1768
|
+
Stop: reconcileGrokHook(settings, 'Stop', 'session-hook', GROK_SESSION_END_HOOK_COMMAND, undefined, 3),
|
|
1769
|
+
SessionEnd: reconcileGrokHook(settings, 'SessionEnd', 'session-hook', null, undefined, 3),
|
|
1770
|
+
};
|
|
1771
|
+
return JSON.stringify(settings, null, 2) + '\n';
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function managedGrokHooksFile(filePath) {
|
|
1775
|
+
if (!exists(filePath)) return false;
|
|
1776
|
+
try {
|
|
1777
|
+
const stat = fs.lstatSync(filePath);
|
|
1778
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return true;
|
|
1779
|
+
if (stat.size > 2 * 1024 * 1024) return true;
|
|
1780
|
+
return /@getmarrow\/mcp@[^\s"']+[\s\S]{0,4096}marrow-mcp[\s\S]{0,512}grok-(?:context-hook|pre-action-hook|hook|session-hook)/
|
|
1781
|
+
.test(fs.readFileSync(filePath, 'utf8'));
|
|
1782
|
+
} catch {
|
|
1783
|
+
return true;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1489
1787
|
function upsertGeminiHooks(settingsPath) {
|
|
1490
1788
|
const settings = parseJsonObject(settingsPath);
|
|
1491
1789
|
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
@@ -1603,8 +1901,12 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1603
1901
|
const expectedHooks = capabilityLevel === 'sdk_passive_runtime'
|
|
1604
1902
|
? ['pre_action', 'action_result', 'outcome_closure']
|
|
1605
1903
|
: [...registry.automatic];
|
|
1904
|
+
const mcpTargetVersion = compatibleMcpTargetVersion(plan.mcp_target_version)
|
|
1905
|
+
? plan.mcp_target_version
|
|
1906
|
+
: MCP_ADAPTER_VERSION;
|
|
1907
|
+
const targetCommand = (command) => retargetMcpPackageSpec(command, mcpTargetVersion);
|
|
1606
1908
|
const adapterVersion = capabilityLevel === 'native_hooks' || capabilityLevel === 'mcp'
|
|
1607
|
-
?
|
|
1909
|
+
? mcpTargetVersion
|
|
1608
1910
|
: capabilityLevel === 'sdk_passive_runtime'
|
|
1609
1911
|
? SDK_ADAPTER_VERSION
|
|
1610
1912
|
: INSTALLER_ADAPTER_VERSION;
|
|
@@ -1616,71 +1918,71 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1616
1918
|
const geminiSettings = safeJsonObject(detection.paths.geminiSettings);
|
|
1617
1919
|
const grokSettings = safeJsonObject(detection.paths.grokHooks);
|
|
1618
1920
|
if (client === 'codex') {
|
|
1619
|
-
if (exactHookConfigured(codexSettings, 'UserPromptSubmit', CODEX_CONTEXT_HOOK_COMMAND)) observedHooks.push('prompt');
|
|
1620
|
-
if (exactHookConfigured(codexSettings, 'PreToolUse', CODEX_PRE_ACTION_HOOK_COMMAND, CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1621
|
-
if (exactHookConfigured(codexSettings, 'PostToolUse', CODEX_ACTION_RESULT_HOOK_COMMAND, CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1622
|
-
if (exactHookConfigured(codexSettings, 'SessionEnd', CODEX_SESSION_END_HOOK_COMMAND)) observedHooks.push('session_end');
|
|
1921
|
+
if (exactHookConfigured(codexSettings, 'UserPromptSubmit', targetCommand(CODEX_CONTEXT_HOOK_COMMAND))) observedHooks.push('prompt');
|
|
1922
|
+
if (exactHookConfigured(codexSettings, 'PreToolUse', targetCommand(CODEX_PRE_ACTION_HOOK_COMMAND), CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1923
|
+
if (exactHookConfigured(codexSettings, 'PostToolUse', targetCommand(CODEX_ACTION_RESULT_HOOK_COMMAND), CODEX_NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1924
|
+
if (exactHookConfigured(codexSettings, 'SessionEnd', targetCommand(CODEX_SESSION_END_HOOK_COMMAND))) observedHooks.push('session_end');
|
|
1623
1925
|
} else if (client === 'cursor' || client === 'composer') {
|
|
1624
|
-
if (exactCursorHookConfigured(cursorSettings, 'preToolUse', CURSOR_PRE_ACTION_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1926
|
+
if (exactCursorHookConfigured(cursorSettings, 'preToolUse', targetCommand(CURSOR_PRE_ACTION_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1625
1927
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS, failClosed: true, async: false,
|
|
1626
1928
|
})) observedHooks.push('pre_action');
|
|
1627
|
-
if (exactCursorHookConfigured(cursorSettings, 'postToolUse', CURSOR_ACTION_RESULT_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1929
|
+
if (exactCursorHookConfigured(cursorSettings, 'postToolUse', targetCommand(CURSOR_ACTION_RESULT_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1628
1930
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS,
|
|
1629
|
-
}) && exactCursorHookConfigured(cursorSettings, 'postToolUseFailure', CURSOR_ACTION_RESULT_HOOK_COMMAND, CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1931
|
+
}) && exactCursorHookConfigured(cursorSettings, 'postToolUseFailure', targetCommand(CURSOR_ACTION_RESULT_HOOK_COMMAND), CURSOR_NATIVE_HOOK_MATCHER, {
|
|
1630
1932
|
timeout: CODEX_HOOK_TIMEOUT_SECONDS,
|
|
1631
1933
|
})) observedHooks.push('action_result');
|
|
1632
|
-
if (exactCursorHookConfigured(cursorSettings, 'stop', CURSOR_SESSION_END_HOOK_COMMAND, undefined, {
|
|
1934
|
+
if (exactCursorHookConfigured(cursorSettings, 'stop', targetCommand(CURSOR_SESSION_END_HOOK_COMMAND), undefined, {
|
|
1633
1935
|
timeout: CODEX_SESSION_TIMEOUT_SECONDS,
|
|
1634
1936
|
})) observedHooks.push('outcome_closure');
|
|
1635
1937
|
} else if (client === 'cline') {
|
|
1636
1938
|
for (const hook of clineHookContract(detection)) {
|
|
1637
|
-
if (exactExecutableFile(hook.path, hook.content)) observedHooks.push(hook.stage);
|
|
1939
|
+
if (exactExecutableFile(hook.path, targetCommand(hook.content))) observedHooks.push(hook.stage);
|
|
1638
1940
|
}
|
|
1639
1941
|
} else if (client === 'windsurf') {
|
|
1640
1942
|
if (WINDSURF_PRE_EVENTS.every((event) => exactWindsurfHookConfigured(
|
|
1641
|
-
windsurfSettings, event, WINDSURF_PRE_ACTION_HOOK_COMMAND,
|
|
1943
|
+
windsurfSettings, event, targetCommand(WINDSURF_PRE_ACTION_HOOK_COMMAND),
|
|
1642
1944
|
))) observedHooks.push('pre_action');
|
|
1643
1945
|
if (WINDSURF_POST_EVENTS.every((event) => exactWindsurfHookConfigured(
|
|
1644
|
-
windsurfSettings, event, WINDSURF_ACTION_RESULT_HOOK_COMMAND,
|
|
1946
|
+
windsurfSettings, event, targetCommand(WINDSURF_ACTION_RESULT_HOOK_COMMAND),
|
|
1645
1947
|
))) observedHooks.push('action_result');
|
|
1646
1948
|
if (exactWindsurfHookConfigured(
|
|
1647
|
-
windsurfSettings, 'post_cascade_response', WINDSURF_SESSION_END_HOOK_COMMAND,
|
|
1949
|
+
windsurfSettings, 'post_cascade_response', targetCommand(WINDSURF_SESSION_END_HOOK_COMMAND),
|
|
1648
1950
|
)) observedHooks.push('response_closeout');
|
|
1649
1951
|
} else if (client === 'gemini' && !geminiHooksExplicitlyDisabled(geminiSettings)) {
|
|
1650
1952
|
if (exactGeminiHookConfigured(
|
|
1651
|
-
geminiSettings, 'BeforeTool', 'marrow-before-tool', GEMINI_PRE_ACTION_HOOK_COMMAND,
|
|
1953
|
+
geminiSettings, 'BeforeTool', 'marrow-before-tool', targetCommand(GEMINI_PRE_ACTION_HOOK_COMMAND),
|
|
1652
1954
|
GEMINI_NATIVE_HOOK_MATCHER, GEMINI_HOOK_TIMEOUT_MS,
|
|
1653
1955
|
)) observedHooks.push('pre_action');
|
|
1654
1956
|
if (exactGeminiHookConfigured(
|
|
1655
|
-
geminiSettings, 'AfterTool', 'marrow-after-tool', GEMINI_ACTION_RESULT_HOOK_COMMAND,
|
|
1957
|
+
geminiSettings, 'AfterTool', 'marrow-after-tool', targetCommand(GEMINI_ACTION_RESULT_HOOK_COMMAND),
|
|
1656
1958
|
GEMINI_NATIVE_HOOK_MATCHER, GEMINI_HOOK_TIMEOUT_MS,
|
|
1657
1959
|
)) observedHooks.push('action_result');
|
|
1658
1960
|
if (exactGeminiHookConfigured(
|
|
1659
|
-
geminiSettings, 'AfterAgent', 'marrow-after-agent', GEMINI_SESSION_END_HOOK_COMMAND,
|
|
1961
|
+
geminiSettings, 'AfterAgent', 'marrow-after-agent', targetCommand(GEMINI_SESSION_END_HOOK_COMMAND),
|
|
1660
1962
|
undefined, GEMINI_CLOSEOUT_TIMEOUT_MS,
|
|
1661
1963
|
)) observedHooks.push('turn_closeout');
|
|
1662
1964
|
} else if (client === 'grok') {
|
|
1663
1965
|
if (exactGrokHookConfigured(
|
|
1664
|
-
grokSettings, 'PreToolUse', GROK_PRE_ACTION_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 7,
|
|
1966
|
+
grokSettings, 'PreToolUse', targetCommand(GROK_PRE_ACTION_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 7,
|
|
1665
1967
|
)) observedHooks.push('pre_action');
|
|
1666
1968
|
if (exactGrokHookConfigured(
|
|
1667
|
-
grokSettings, 'PostToolUse', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1969
|
+
grokSettings, 'PostToolUse', targetCommand(GROK_ACTION_RESULT_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1668
1970
|
) && exactGrokHookConfigured(
|
|
1669
|
-
grokSettings, 'PostToolUseFailure', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1971
|
+
grokSettings, 'PostToolUseFailure', targetCommand(GROK_ACTION_RESULT_HOOK_COMMAND), GROK_NATIVE_HOOK_MATCHER, 5,
|
|
1670
1972
|
)) observedHooks.push('action_result');
|
|
1671
1973
|
if (exactGrokHookConfigured(
|
|
1672
|
-
grokSettings, 'Stop', GROK_SESSION_END_HOOK_COMMAND, undefined, 3,
|
|
1974
|
+
grokSettings, 'Stop', targetCommand(GROK_SESSION_END_HOOK_COMMAND), undefined, 3,
|
|
1673
1975
|
) && !grokHasDuplicateSessionEnd(grokSettings)) observedHooks.push('turn_closeout');
|
|
1674
1976
|
} else {
|
|
1675
1977
|
if (capabilityLevel === 'native_hooks'
|
|
1676
|
-
&& exactHookConfigured(claudeSettings, 'UserPromptSubmit', MCP_CONTEXT_HOOK_COMMAND)) observedHooks.push('prompt');
|
|
1978
|
+
&& exactHookConfigured(claudeSettings, 'UserPromptSubmit', targetCommand(MCP_CONTEXT_HOOK_COMMAND))) observedHooks.push('prompt');
|
|
1677
1979
|
if (capabilityLevel === 'native_hooks'
|
|
1678
|
-
&& exactHookConfigured(claudeSettings, 'PreToolUse', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1980
|
+
&& exactHookConfigured(claudeSettings, 'PreToolUse', targetCommand(MCP_PRE_ACTION_HOOK_COMMAND), NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
|
|
1679
1981
|
if (capabilityLevel === 'native_hooks'
|
|
1680
|
-
&& exactHookConfigured(claudeSettings, 'PostToolUse', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)
|
|
1681
|
-
&& exactHookConfigured(claudeSettings, 'PostToolUseFailure', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1982
|
+
&& exactHookConfigured(claudeSettings, 'PostToolUse', targetCommand(MCP_ACTION_RESULT_HOOK_COMMAND), NATIVE_HOOK_MATCHER)
|
|
1983
|
+
&& exactHookConfigured(claudeSettings, 'PostToolUseFailure', targetCommand(MCP_ACTION_RESULT_HOOK_COMMAND), NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
|
|
1682
1984
|
if (capabilityLevel === 'native_hooks'
|
|
1683
|
-
&& exactHookConfigured(claudeSettings, 'Stop', MCP_SESSION_END_HOOK_COMMAND)) observedHooks.push('session_end');
|
|
1985
|
+
&& exactHookConfigured(claudeSettings, 'Stop', targetCommand(MCP_SESSION_END_HOOK_COMMAND))) observedHooks.push('session_end');
|
|
1684
1986
|
}
|
|
1685
1987
|
const passiveRuntime = safeRead(detection.paths.passiveRuntime);
|
|
1686
1988
|
if (capabilityLevel === 'sdk_passive_runtime'
|
|
@@ -1696,7 +1998,7 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1696
1998
|
if (capabilityLevel === 'mcp' && mcpConfigs.some((config) => (
|
|
1697
1999
|
config?.mcpServers?.marrow?.command === 'npx'
|
|
1698
2000
|
&& Array.isArray(config.mcpServers.marrow.args)
|
|
1699
|
-
&& config.mcpServers.marrow.args.join(' ') === `-y --package
|
|
2001
|
+
&& config.mcpServers.marrow.args.join(' ') === `-y --package=@getmarrow/mcp@${mcpTargetVersion} marrow-mcp`
|
|
1700
2002
|
))) observedHooks.push('mcp_tool_calls');
|
|
1701
2003
|
const fingerprintMaterial = changes
|
|
1702
2004
|
.filter((change) => change.applied || change.already_present)
|
|
@@ -1737,7 +2039,7 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1737
2039
|
: client === 'gemini' && geminiHooksExplicitlyDisabled(geminiSettings)
|
|
1738
2040
|
? 'Hooks are explicitly disabled. After owner review, run /hooks enable-all, open /hooks panel, review and approve the project hook fingerprints, then restart Gemini CLI.'
|
|
1739
2041
|
: client === 'grok'
|
|
1740
|
-
? `Run npx -y --package
|
|
2042
|
+
? `Run npx -y --package=@getmarrow/mcp@${mcpTargetVersion} marrow-mcp setup, restart Grok, then inspect /hooks and confirm the global hooks are enabled.`
|
|
1741
2043
|
: 'npx @getmarrow/install --repair';
|
|
1742
2044
|
return {
|
|
1743
2045
|
adapter_version: adapterVersion,
|
|
@@ -2026,6 +2328,8 @@ function buildPlan(detection, options) {
|
|
|
2026
2328
|
const client = options.client || detectedClient(detection);
|
|
2027
2329
|
const agentId = String(options.agentId || '').trim() || stableAgentId(detection.root, client);
|
|
2028
2330
|
const baseUrl = String(options.baseUrl || DEFAULT_BASE_URL).trim() || DEFAULT_BASE_URL;
|
|
2331
|
+
const mcpTargetVersion = executableMcpTarget(options).version;
|
|
2332
|
+
const retarget = (value) => retargetMcpPackageSpec(value, mcpTargetVersion);
|
|
2029
2333
|
const mode = options.mode === 'auto'
|
|
2030
2334
|
? detection.node ? 'both' : 'mcp'
|
|
2031
2335
|
: options.mode;
|
|
@@ -2053,7 +2357,7 @@ function buildPlan(detection, options) {
|
|
|
2053
2357
|
type: 'json-transform',
|
|
2054
2358
|
path: detection.paths.claudeSettings,
|
|
2055
2359
|
label: 'Claude Code MCP passive hooks',
|
|
2056
|
-
transform: upsertClaudeHooks,
|
|
2360
|
+
transform: (filePath) => retarget(upsertClaudeHooks(filePath)),
|
|
2057
2361
|
});
|
|
2058
2362
|
}
|
|
2059
2363
|
if (detection.codex) {
|
|
@@ -2061,7 +2365,7 @@ function buildPlan(detection, options) {
|
|
|
2061
2365
|
type: 'json-transform',
|
|
2062
2366
|
path: detection.paths.codexHooks,
|
|
2063
2367
|
label: 'Codex native hooks',
|
|
2064
|
-
transform: upsertCodexHooks,
|
|
2368
|
+
transform: (filePath) => retarget(upsertCodexHooks(filePath)),
|
|
2065
2369
|
});
|
|
2066
2370
|
}
|
|
2067
2371
|
if (detection.cline) {
|
|
@@ -2070,7 +2374,7 @@ function buildPlan(detection, options) {
|
|
|
2070
2374
|
type: 'owned-executable',
|
|
2071
2375
|
path: hook.path,
|
|
2072
2376
|
label: hook.label,
|
|
2073
|
-
content: hook.content,
|
|
2377
|
+
content: retarget(hook.content),
|
|
2074
2378
|
mode: 0o755,
|
|
2075
2379
|
conflict_fix: 'Move or remove the existing owner-managed Cline hook after owner review, then run npx @getmarrow/install --repair.',
|
|
2076
2380
|
});
|
|
@@ -2081,7 +2385,7 @@ function buildPlan(detection, options) {
|
|
|
2081
2385
|
type: 'json-transform',
|
|
2082
2386
|
path: detection.paths.windsurfHooks,
|
|
2083
2387
|
label: 'Windsurf native hooks',
|
|
2084
|
-
transform: upsertWindsurfHooks,
|
|
2388
|
+
transform: (filePath) => retarget(upsertWindsurfHooks(filePath)),
|
|
2085
2389
|
});
|
|
2086
2390
|
}
|
|
2087
2391
|
if (detection.gemini) {
|
|
@@ -2089,27 +2393,36 @@ function buildPlan(detection, options) {
|
|
|
2089
2393
|
type: 'json-transform',
|
|
2090
2394
|
path: detection.paths.geminiSettings,
|
|
2091
2395
|
label: 'Gemini CLI native hooks',
|
|
2092
|
-
transform: upsertGeminiHooks,
|
|
2396
|
+
transform: (filePath) => retarget(upsertGeminiHooks(filePath)),
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
if (options.update && detection.grok && managedGrokHooksFile(detection.paths.grokHooks)) {
|
|
2400
|
+
writes.push({
|
|
2401
|
+
type: 'json-transform',
|
|
2402
|
+
path: detection.paths.grokHooks,
|
|
2403
|
+
root: path.resolve(detection.paths.grokHooks, '..', '..', '..'),
|
|
2404
|
+
label: 'Grok native hooks',
|
|
2405
|
+
transform: (filePath) => retarget(upsertGrokHooks(filePath)),
|
|
2093
2406
|
});
|
|
2094
2407
|
}
|
|
2095
2408
|
writes.push({
|
|
2096
2409
|
type: 'json-transform',
|
|
2097
2410
|
path: detection.paths.mcpJson,
|
|
2098
2411
|
label: 'Project MCP server config',
|
|
2099
|
-
transform: (filePath) => upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile }),
|
|
2412
|
+
transform: (filePath) => retarget(upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile })),
|
|
2100
2413
|
});
|
|
2101
2414
|
if (detection.cursor) {
|
|
2102
2415
|
writes.push({
|
|
2103
2416
|
type: 'json-transform',
|
|
2104
2417
|
path: detection.paths.cursorHooks,
|
|
2105
2418
|
label: 'Cursor native hooks',
|
|
2106
|
-
transform: upsertCursorHooks,
|
|
2419
|
+
transform: (filePath) => retarget(upsertCursorHooks(filePath)),
|
|
2107
2420
|
});
|
|
2108
2421
|
writes.push({
|
|
2109
2422
|
type: 'json-transform',
|
|
2110
2423
|
path: detection.paths.cursorMcp,
|
|
2111
2424
|
label: 'Cursor MCP server config',
|
|
2112
|
-
transform: (filePath) => upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile }),
|
|
2425
|
+
transform: (filePath) => retarget(upsertMcpServerConfig(filePath, { agentId, baseUrl, toolProfile: options.toolProfile })),
|
|
2113
2426
|
});
|
|
2114
2427
|
}
|
|
2115
2428
|
}
|
|
@@ -2119,7 +2432,7 @@ function buildPlan(detection, options) {
|
|
|
2119
2432
|
type: 'md-block',
|
|
2120
2433
|
path: detection.paths.agentsMd,
|
|
2121
2434
|
label: 'Agent instructions',
|
|
2122
|
-
block: passiveInstructions(),
|
|
2435
|
+
block: retarget(passiveInstructions()),
|
|
2123
2436
|
});
|
|
2124
2437
|
}
|
|
2125
2438
|
|
|
@@ -2128,11 +2441,11 @@ function buildPlan(detection, options) {
|
|
|
2128
2441
|
type: 'file',
|
|
2129
2442
|
path: detection.paths.cursorRules,
|
|
2130
2443
|
label: 'Cursor Marrow rule',
|
|
2131
|
-
content: passiveInstructions().replace(/<!--[^>]+-->/g, '').trim() + '\n',
|
|
2444
|
+
content: retarget(passiveInstructions()).replace(/<!--[^>]+-->/g, '').trim() + '\n',
|
|
2132
2445
|
});
|
|
2133
2446
|
}
|
|
2134
2447
|
|
|
2135
|
-
return { mode, root: detection.root, writes };
|
|
2448
|
+
return { mode, root: detection.root, writes, mcp_target_version: mcpTargetVersion };
|
|
2136
2449
|
}
|
|
2137
2450
|
|
|
2138
2451
|
function assertContainedManagedTarget(root, targetPath) {
|
|
@@ -2194,13 +2507,25 @@ function atomicWriteManagedFile(root, targetPath, contents) {
|
|
|
2194
2507
|
function applyPlan(plan, options) {
|
|
2195
2508
|
if (!Array.isArray(plan?.writes) || plan.writes.length === 0) return [];
|
|
2196
2509
|
const root = path.resolve(plan.root || path.dirname(plan.writes[0].path));
|
|
2197
|
-
for (const write of plan.writes)
|
|
2510
|
+
for (const write of plan.writes) {
|
|
2511
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2512
|
+
}
|
|
2198
2513
|
const prepared = plan.writes.map((write) => {
|
|
2199
2514
|
const fileExists = exists(write.path);
|
|
2200
2515
|
const before = safeRead(write.path);
|
|
2516
|
+
const inspectedBefore = write.type === 'md-block'
|
|
2517
|
+
? marrowManagedBlockInText(before)
|
|
2518
|
+
: before;
|
|
2519
|
+
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(
|
|
2520
|
+
mcpVersionsInText(inspectedBefore),
|
|
2521
|
+
plan.mcp_target_version,
|
|
2522
|
+
);
|
|
2523
|
+
const automaticRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
2201
2524
|
let after;
|
|
2202
2525
|
let hookConflict = false;
|
|
2203
|
-
if (
|
|
2526
|
+
if (automaticRepairSuppressed) {
|
|
2527
|
+
after = before;
|
|
2528
|
+
} else if (write.type === 'file') {
|
|
2204
2529
|
if (write.overwrite === false && before) {
|
|
2205
2530
|
after = before;
|
|
2206
2531
|
} else {
|
|
@@ -2223,28 +2548,50 @@ function applyPlan(plan, options) {
|
|
|
2223
2548
|
|
|
2224
2549
|
const beforeMode = fileExists ? fs.lstatSync(write.path).mode & 0o777 : null;
|
|
2225
2550
|
const modeChanged = !hookConflict && typeof write.mode === 'number' && beforeMode !== write.mode;
|
|
2226
|
-
return {
|
|
2551
|
+
return {
|
|
2552
|
+
write,
|
|
2553
|
+
before,
|
|
2554
|
+
after,
|
|
2555
|
+
hookConflict,
|
|
2556
|
+
modeChanged,
|
|
2557
|
+
automaticRepairSuppressed,
|
|
2558
|
+
aheadUnverifiedVersions,
|
|
2559
|
+
};
|
|
2227
2560
|
});
|
|
2228
2561
|
|
|
2229
2562
|
const changes = [];
|
|
2230
|
-
for (const {
|
|
2563
|
+
for (const {
|
|
2564
|
+
write,
|
|
2565
|
+
before,
|
|
2566
|
+
after,
|
|
2567
|
+
hookConflict,
|
|
2568
|
+
modeChanged,
|
|
2569
|
+
automaticRepairSuppressed,
|
|
2570
|
+
aheadUnverifiedVersions,
|
|
2571
|
+
} of prepared) {
|
|
2231
2572
|
const contentChanged = before !== after;
|
|
2232
|
-
const changed = !hookConflict && (contentChanged || modeChanged);
|
|
2233
|
-
const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor
|
|
2573
|
+
const changed = !automaticRepairSuppressed && !hookConflict && (contentChanged || modeChanged);
|
|
2574
|
+
const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor
|
|
2575
|
+
&& !hookConflict && !automaticRepairSuppressed);
|
|
2234
2576
|
changes.push({
|
|
2235
2577
|
path: write.path,
|
|
2236
2578
|
label: write.label,
|
|
2237
2579
|
changed,
|
|
2238
2580
|
applied: changed && writeApplied,
|
|
2239
|
-
already_present: !changed && !hookConflict,
|
|
2581
|
+
already_present: !changed && !hookConflict && !automaticRepairSuppressed,
|
|
2240
2582
|
hook_conflict: hookConflict,
|
|
2583
|
+
automatic_repair_suppressed: automaticRepairSuppressed,
|
|
2584
|
+
...(automaticRepairSuppressed ? {
|
|
2585
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
2586
|
+
exact_fix: mcpRegistryVerificationAction(aheadUnverifiedVersions),
|
|
2587
|
+
} : {}),
|
|
2241
2588
|
...(hookConflict ? { exact_fix: write.conflict_fix } : {}),
|
|
2242
2589
|
});
|
|
2243
2590
|
if (contentChanged && writeApplied) {
|
|
2244
|
-
atomicWriteManagedFile(root, write.path, after);
|
|
2591
|
+
atomicWriteManagedFile(path.resolve(write.root || root), write.path, after);
|
|
2245
2592
|
}
|
|
2246
2593
|
if (modeChanged && writeApplied) {
|
|
2247
|
-
assertContainedManagedTarget(root, write.path);
|
|
2594
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2248
2595
|
fs.chmodSync(write.path, write.mode);
|
|
2249
2596
|
}
|
|
2250
2597
|
}
|
|
@@ -2303,7 +2650,7 @@ async function runSelfTest(options) {
|
|
|
2303
2650
|
'x-marrow-package-version': INSTALLER_ADAPTER_VERSION,
|
|
2304
2651
|
'x-marrow-install-version': INSTALLER_ADAPTER_VERSION,
|
|
2305
2652
|
'x-marrow-sdk-version': SDK_ADAPTER_VERSION,
|
|
2306
|
-
'x-marrow-mcp-version':
|
|
2653
|
+
'x-marrow-mcp-version': executableMcpTarget(options).version,
|
|
2307
2654
|
};
|
|
2308
2655
|
if (options.agentId) headers['x-marrow-agent-id'] = options.agentId;
|
|
2309
2656
|
|
|
@@ -2668,8 +3015,13 @@ function printReport(report) {
|
|
|
2668
3015
|
|
|
2669
3016
|
process.stdout.write('\nPlanned changes:\n');
|
|
2670
3017
|
for (const change of report.changes) {
|
|
2671
|
-
const marker = change.
|
|
3018
|
+
const marker = change.automatic_repair_suppressed
|
|
3019
|
+
? 'preserved unverified-ahead surface; repair suppressed'
|
|
3020
|
+
: change.applied ? 'wrote' : change.changed ? 'would write' : 'unchanged';
|
|
2672
3021
|
process.stdout.write(`- ${marker}: ${change.label} (${change.path})\n`);
|
|
3022
|
+
if (change.automatic_repair_suppressed && change.exact_fix) {
|
|
3023
|
+
process.stdout.write(` exact verification: ${change.exact_fix}\n`);
|
|
3024
|
+
}
|
|
2673
3025
|
}
|
|
2674
3026
|
|
|
2675
3027
|
process.stdout.write('\nSelf-test:\n');
|
|
@@ -2752,12 +3104,12 @@ function printReport(report) {
|
|
|
2752
3104
|
}
|
|
2753
3105
|
}
|
|
2754
3106
|
|
|
2755
|
-
if (report.harnessReload) {
|
|
3107
|
+
if (report.harnessReload && report.writeMode !== 'doctor') {
|
|
2756
3108
|
process.stdout.write('\nHarness reload:\n');
|
|
2757
3109
|
process.stdout.write(`- required: ${report.harnessReload.required ? 'yes' : 'no'}\n`);
|
|
2758
3110
|
process.stdout.write(`- live in this process: ${report.harnessReload.live_in_this_process ? 'yes' : 'no'}\n`);
|
|
2759
|
-
if (report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
2760
|
-
if (report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
3111
|
+
if (report.harnessReload.required && report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
3112
|
+
if (report.harnessReload.required && report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
2761
3113
|
}
|
|
2762
3114
|
if (report.firstCapture) {
|
|
2763
3115
|
process.stdout.write('\nFirst capture:\n');
|
|
@@ -2804,7 +3156,9 @@ function printReport(report) {
|
|
|
2804
3156
|
if (report.controller?.exact_fix) process.stdout.write(`- exact fix: ${report.controller.exact_fix}\n`);
|
|
2805
3157
|
|
|
2806
3158
|
if (report.writeMode === 'doctor') {
|
|
2807
|
-
const liveHere = !report.harnessReload?.required
|
|
3159
|
+
const liveHere = !report.harnessReload?.required
|
|
3160
|
+
&& report.doctor.mcpProcesses?.healthy !== false
|
|
3161
|
+
&& report.doctor.mcpConfigurations?.healthy !== false;
|
|
2808
3162
|
const activeLabel = report.doctor.active && liveHere
|
|
2809
3163
|
? 'yes'
|
|
2810
3164
|
: report.doctor.active
|
|
@@ -2819,15 +3173,21 @@ function printReport(report) {
|
|
|
2819
3173
|
const processes = report.doctor.mcpProcesses;
|
|
2820
3174
|
process.stdout.write(`- MCP process versions: ${processes.active_versions.length ? processes.active_versions.join(', ') : processes.active_processes ? 'unknown' : 'none'}\n`);
|
|
2821
3175
|
process.stdout.write(`- stale/mixed/version-unknown MCP clients: ${processes.healthy ? 'no' : 'yes'}\n`);
|
|
2822
|
-
if (processes.
|
|
2823
|
-
if (processes.
|
|
3176
|
+
if (processes.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP clients: ${processes.ahead_unverified_versions.join(', ')}\n`);
|
|
3177
|
+
if (processes.automatic_repair_suppressed) process.stdout.write('- automatic MCP process repair: suppressed pending official registry verification\n');
|
|
2824
3178
|
}
|
|
2825
3179
|
if (report.doctor.mcpConfigurations) {
|
|
2826
3180
|
const configurations = report.doctor.mcpConfigurations;
|
|
2827
3181
|
process.stdout.write(`- configured MCP versions: ${configurations.configured_versions.length ? configurations.configured_versions.join(', ') : 'none pinned'}\n`);
|
|
2828
3182
|
process.stdout.write(`- stale/mixed/version-unknown MCP configuration: ${configurations.healthy ? 'no' : 'yes'}\n`);
|
|
3183
|
+
if (configurations.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP configuration: ${configurations.ahead_unverified_versions.join(', ')}\n`);
|
|
3184
|
+
if (configurations.automatic_repair_suppressed) process.stdout.write('- automatic MCP configuration repair: suppressed pending official registry verification\n');
|
|
2829
3185
|
}
|
|
2830
3186
|
if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
|
|
3187
|
+
if (report.doctor.mcp_update_required) {
|
|
3188
|
+
process.stdout.write(`- restart once after update: ${report.doctor.restart_instruction}\n`);
|
|
3189
|
+
process.stdout.write(`- verify after restart: ${report.doctor.verification_command}\n`);
|
|
3190
|
+
}
|
|
2831
3191
|
process.stdout.write(`- live health: ${report.doctor.healthCommand}\n`);
|
|
2832
3192
|
}
|
|
2833
3193
|
|
|
@@ -2863,10 +3223,23 @@ async function install(options) {
|
|
|
2863
3223
|
const client = detectedClient(detection);
|
|
2864
3224
|
options.client = client;
|
|
2865
3225
|
options.agentId = String(options.agentId || '').trim() || stableAgentId(detection.root, client);
|
|
3226
|
+
const observedMcpProcesses = inspectMcpProcesses({ commands: options.processCommands });
|
|
3227
|
+
const observedMcpConfigurations = inspectMcpConfigurations(detection, { paths: options.mcpConfigPaths });
|
|
3228
|
+
const registryMetadata = await readMcpRegistryMetadata(options);
|
|
3229
|
+
const latestTargetOperation = Boolean(options.doctor || options.repair || options.update);
|
|
3230
|
+
const mcpTarget = resolveMcpTargetVersion({
|
|
3231
|
+
currentVersions: latestTargetOperation ? [
|
|
3232
|
+
...observedMcpProcesses.active_versions,
|
|
3233
|
+
...observedMcpConfigurations.configured_versions,
|
|
3234
|
+
] : [],
|
|
3235
|
+
registryMetadata: latestTargetOperation ? registryMetadata : null,
|
|
3236
|
+
});
|
|
3237
|
+
options.mcpTarget = mcpTarget;
|
|
3238
|
+
options.mcpTargetVersion = mcpTarget.version;
|
|
2866
3239
|
const plan = buildPlan(detection, options);
|
|
2867
3240
|
const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
|
|
2868
3241
|
const changes = applyPlan(plan, options);
|
|
2869
|
-
|
|
3242
|
+
let profile = activationProfile(detection, plan, changes, client);
|
|
2870
3243
|
options.activation = options.activate ? {
|
|
2871
3244
|
harness: client,
|
|
2872
3245
|
agent_id: options.agentId,
|
|
@@ -2897,8 +3270,35 @@ async function install(options) {
|
|
|
2897
3270
|
? repairConfigDiagnostics(configDiagnostics)
|
|
2898
3271
|
: [];
|
|
2899
3272
|
const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
|
|
2900
|
-
const mcpProcesses = inspectMcpProcesses({
|
|
2901
|
-
|
|
3273
|
+
const mcpProcesses = inspectMcpProcesses({
|
|
3274
|
+
commands: options.processCommands,
|
|
3275
|
+
expectedTarget: mcpTarget,
|
|
3276
|
+
});
|
|
3277
|
+
const mcpConfigurations = inspectMcpConfigurations(detection, {
|
|
3278
|
+
paths: options.mcpConfigPaths,
|
|
3279
|
+
expectedTarget: mcpTarget,
|
|
3280
|
+
});
|
|
3281
|
+
const aheadUnverifiedVersions = [...new Set([
|
|
3282
|
+
...mcpProcesses.ahead_unverified_versions,
|
|
3283
|
+
...mcpConfigurations.ahead_unverified_versions,
|
|
3284
|
+
...changes.flatMap((change) => change.ahead_unverified_versions || []),
|
|
3285
|
+
])].sort((left, right) => compareMcpVersions(left, right));
|
|
3286
|
+
const automaticMcpRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
3287
|
+
const registryVerificationAction = automaticMcpRepairSuppressed
|
|
3288
|
+
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
3289
|
+
: null;
|
|
3290
|
+
const mcpUpdateRequired = !automaticMcpRepairSuppressed
|
|
3291
|
+
&& (!mcpConfigurations.healthy || (!options.update && !mcpProcesses.healthy));
|
|
3292
|
+
if (automaticMcpRepairSuppressed) {
|
|
3293
|
+
profile = {
|
|
3294
|
+
...profile,
|
|
3295
|
+
configuration_complete: false,
|
|
3296
|
+
complete: false,
|
|
3297
|
+
automatic_repair_suppressed: true,
|
|
3298
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3299
|
+
exact_fix: registryVerificationAction,
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
2902
3302
|
let selfTest;
|
|
2903
3303
|
try {
|
|
2904
3304
|
selfTest = await runSelfTest(options);
|
|
@@ -2912,10 +3312,49 @@ async function install(options) {
|
|
|
2912
3312
|
mcp_tool_profile: initialToolProfileReport(options.toolProfile),
|
|
2913
3313
|
};
|
|
2914
3314
|
}
|
|
3315
|
+
selfTest = alignMcpRecommendationVersions(
|
|
3316
|
+
selfTest,
|
|
3317
|
+
mcpTarget.version,
|
|
3318
|
+
'',
|
|
3319
|
+
registryVerificationAction,
|
|
3320
|
+
);
|
|
2915
3321
|
if (options.activate && !selfTest.activation_verified) {
|
|
2916
3322
|
throw new Error('Marrow activation failed: server confirmation was not returned');
|
|
2917
3323
|
}
|
|
2918
|
-
const
|
|
3324
|
+
const plannedHarnessReload = harnessReloadPlan(detection, options.doctor ? [] : changes);
|
|
3325
|
+
const updateNeedsRestart = Boolean(options.update
|
|
3326
|
+
&& !automaticMcpRepairSuppressed
|
|
3327
|
+
&& (plannedHarnessReload.required || (mcpConfigurations.healthy && !mcpProcesses.healthy)));
|
|
3328
|
+
const harnessReload = options.doctor && mcpUpdateRequired
|
|
3329
|
+
? {
|
|
3330
|
+
...plannedHarnessReload,
|
|
3331
|
+
required: true,
|
|
3332
|
+
live_in_this_process: false,
|
|
3333
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3334
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3335
|
+
}
|
|
3336
|
+
: updateNeedsRestart
|
|
3337
|
+
? {
|
|
3338
|
+
...plannedHarnessReload,
|
|
3339
|
+
required: true,
|
|
3340
|
+
live_in_this_process: false,
|
|
3341
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3342
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3343
|
+
}
|
|
3344
|
+
: plannedHarnessReload;
|
|
3345
|
+
const updateAwaitingRestart = Boolean(options.update
|
|
3346
|
+
&& updateNeedsRestart
|
|
3347
|
+
&& mcpConfigurations.healthy
|
|
3348
|
+
&& !mcpProcesses.healthy
|
|
3349
|
+
&& !automaticMcpRepairSuppressed);
|
|
3350
|
+
const reportedMcpProcesses = updateAwaitingRestart
|
|
3351
|
+
? {
|
|
3352
|
+
...mcpProcesses,
|
|
3353
|
+
update_completed: true,
|
|
3354
|
+
awaiting_restart: true,
|
|
3355
|
+
exact_fix: null,
|
|
3356
|
+
}
|
|
3357
|
+
: mcpProcesses;
|
|
2919
3358
|
if (harnessReload.required && selfTest.mcp_tool_profile) {
|
|
2920
3359
|
selfTest.mcp_tool_profile = {
|
|
2921
3360
|
...selfTest.mcp_tool_profile,
|
|
@@ -2972,7 +3411,12 @@ async function install(options) {
|
|
|
2972
3411
|
attempted: true,
|
|
2973
3412
|
fixedConfig: changedConfig,
|
|
2974
3413
|
selfTestPassed,
|
|
2975
|
-
|
|
3414
|
+
automaticMcpRepairSuppressed,
|
|
3415
|
+
message: automaticMcpRepairSuppressed
|
|
3416
|
+
? registryVerificationAction
|
|
3417
|
+
: options.update && harnessReload.required
|
|
3418
|
+
? 'Managed Marrow configuration is synchronized. Running MCP processes are unchanged; complete the Harness reload steps below before treating the update as live.'
|
|
3419
|
+
: selfTestPassed
|
|
2976
3420
|
? selfTest.health === 'healthy'
|
|
2977
3421
|
? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
|
|
2978
3422
|
: `I fixed Marrow passive config and self-test passed; status is ${selfTest.health || 'unknown'}${selfTest.next_action ? `. Next action: ${selfTest.next_action}` : ''}.`
|
|
@@ -2981,10 +3425,9 @@ async function install(options) {
|
|
|
2981
3425
|
: `Config repair ran, but self-test failed: ${selfTest.error || 'unknown error'}.`,
|
|
2982
3426
|
}
|
|
2983
3427
|
: null;
|
|
2984
|
-
|
|
2985
3428
|
return {
|
|
2986
3429
|
root: detection.root,
|
|
2987
|
-
adapterProvenance:
|
|
3430
|
+
adapterProvenance: adapterProvenanceForMcpTarget(mcpTarget),
|
|
2988
3431
|
mode: plan.mode,
|
|
2989
3432
|
writeMode,
|
|
2990
3433
|
toolProfile: selfTest.mcp_tool_profile || initialToolProfileReport(options.toolProfile),
|
|
@@ -3018,9 +3461,18 @@ async function install(options) {
|
|
|
3018
3461
|
missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
|
|
3019
3462
|
envHints,
|
|
3020
3463
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
3021
|
-
mcpProcesses,
|
|
3464
|
+
mcpProcesses: reportedMcpProcesses,
|
|
3022
3465
|
mcpConfigurations,
|
|
3023
|
-
|
|
3466
|
+
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3467
|
+
automatic_mcp_repair_suppressed: automaticMcpRepairSuppressed,
|
|
3468
|
+
registry_verification_action: registryVerificationAction,
|
|
3469
|
+
mcp_update_required: mcpUpdateRequired,
|
|
3470
|
+
restart_required: mcpUpdateRequired || updateAwaitingRestart,
|
|
3471
|
+
restart_instruction: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
3472
|
+
verification_command: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_DOCTOR_COMMAND : null,
|
|
3473
|
+
recommendedFix: registryVerificationAction
|
|
3474
|
+
|| (updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null)
|
|
3475
|
+
|| mcpProcesses.exact_fix || mcpConfigurations.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
3024
3476
|
? envHints.length
|
|
3025
3477
|
? `MARROW_API_KEY was found in a likely env file at ${envHints[0]}. Load that key from trusted secret storage, export only MARROW_API_KEY, then run npx @getmarrow/install --repair.`
|
|
3026
3478
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
@@ -3038,11 +3490,15 @@ async function install(options) {
|
|
|
3038
3490
|
...(options.keyFromArg
|
|
3039
3491
|
? ['Avoid --key in shared shells because command-line arguments can be visible in process listings. Prefer MARROW_API_KEY in your environment or secret manager.']
|
|
3040
3492
|
: []),
|
|
3041
|
-
...(
|
|
3042
|
-
? ['
|
|
3493
|
+
...(mcpProcesses.ahead_unverified
|
|
3494
|
+
? ['Registry-unverified ahead Marrow MCP clients are active. Automatic repair is suppressed; run the exact registry verification action before owner-directed replacement.']
|
|
3495
|
+
: !mcpProcesses.healthy && !options.update && !options.doctor
|
|
3496
|
+
? [`Stale, mixed, or version-unknown Marrow MCP clients are active. Run ${INSTALLER_UPDATE_COMMAND}; the installer will report the one required restart and later doctor verification.`]
|
|
3043
3497
|
: []),
|
|
3044
|
-
...(
|
|
3045
|
-
? ['
|
|
3498
|
+
...(mcpConfigurations.ahead_unverified || changes.some((change) => change.automatic_repair_suppressed)
|
|
3499
|
+
? ['Registry-unverified ahead Marrow MCP configuration was preserved. It was not copied into other managed targets, and automatic repair is suppressed for that surface.']
|
|
3500
|
+
: !mcpConfigurations.healthy && !options.update && !options.doctor
|
|
3501
|
+
? [`Stale, mixed, or version-unknown Marrow MCP versions remain in the detected workspace. Run ${INSTALLER_UPDATE_COMMAND} once to synchronize its managed surfaces.`]
|
|
3046
3502
|
: []),
|
|
3047
3503
|
],
|
|
3048
3504
|
};
|
|
@@ -3093,6 +3549,7 @@ module.exports = {
|
|
|
3093
3549
|
GROK_NATIVE_HOOK_MATCHER,
|
|
3094
3550
|
printReport,
|
|
3095
3551
|
buildMcpToolProfileReport,
|
|
3552
|
+
resolveMcpTargetVersion,
|
|
3096
3553
|
resolveToolProfile,
|
|
3097
3554
|
PRIMARY_TOOL_NAMES,
|
|
3098
3555
|
ADAPTER_PROVENANCE,
|