@getmarrow/mcp 3.9.85 → 3.9.87
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 +7 -1
- package/dist/hook-contract.d.ts +2 -2
- package/dist/hook-contract.js +1 -1
- package/package.json +1 -1
- package/scripts/control-path-canary.cjs +56 -6
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -130,7 +130,13 @@ A direct `marrow_think` can receive a durable pending response before the backen
|
|
|
130
130
|
|
|
131
131
|
A saved `observed_unverified` outcome is terminal observation evidence, not a committed outcome. Receipt expiry cannot retroactively authorize completed work. Preserve the original decision, receipt, proof and key; an already authorized durable checkpoint may finish through its existing exact recovery path. Do not repeat the action merely to obtain a fresh receipt.
|
|
132
132
|
|
|
133
|
-
## What's New in v3.9.
|
|
133
|
+
## What's New in v3.9.87
|
|
134
|
+
|
|
135
|
+
v3.9.87 hardens the authenticated control-path canary against single-sample transport blips so monitoring stops flapping on a healthy service. The canary now retries transport-class delivery failures (`request_failed`, `service_unavailable`, `connection_reset`, `dns_unavailable`, `tls_failure`, `edge_access_denied`, `rate_limited`) in-run with the same idempotent operation before declaring a failure, gives the asynchronous `marrow_auto` and `marrow_first_value` calls a separate deadline ceiling (thirty seconds via `MARROW_MCP_CANARY_ASYNC_TOOL_TIMEOUT_MS`), and raises the default total canary budget to forty-five seconds so bounded retries fit. Results recovered by a retry are annotated with `recovered_on_retry: true`. Authentication, authorization, contract, and package-identity failures remain immediate hard failures with no retry. Tool surface, client contracts, and request deadlines are unchanged.
|
|
136
|
+
|
|
137
|
+
v3.9.86 was published with the adapter version constant still at `3.9.85`, so the strict canary identity check rejected it; it is deprecated — use `3.9.87`. SDK `3.7.62` and installer `0.1.56` are unchanged; install MCP `3.9.87`, reload the host, review hook trust, and verify before claiming the updated client is active.
|
|
138
|
+
|
|
139
|
+
### Previous release: v3.9.85
|
|
134
140
|
|
|
135
141
|
v3.9.85 keeps the existing Marrow Auto request and response deadlines active through response-body consumption and JSON parsing, so a server that sends headers and then stalls ends with the same typed bounded timeout as a stalled header response. The retry owner, four-second write-attempt ceiling, and eight-second Auto response budget are unchanged.
|
|
136
142
|
|
package/dist/hook-contract.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolveMarrowEnv, type ResolvedMarrowEnv } from './env';
|
|
2
|
-
export declare const MCP_ADAPTER_VERSION = "3.9.
|
|
2
|
+
export declare const MCP_ADAPTER_VERSION = "3.9.87";
|
|
3
3
|
export declare const NATIVE_HOOK_MATCHER = "Bash|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*";
|
|
4
4
|
export declare const GROK_NATIVE_HOOK_MATCHER = "run_terminal_command|search_replace|write|spawn_subagent|use_tool|workflow|image_gen|image_edit|image_to_video|reference_to_video";
|
|
5
|
-
export declare const MCP_PACKAGE_SPEC = "@getmarrow/mcp@3.9.
|
|
5
|
+
export declare const MCP_PACKAGE_SPEC = "@getmarrow/mcp@3.9.87";
|
|
6
6
|
export declare const CONTEXT_HOOK_COMMAND: string;
|
|
7
7
|
export declare const PRE_ACTION_HOOK_COMMAND: string;
|
|
8
8
|
export declare const ACTION_RESULT_HOOK_COMMAND: string;
|
package/dist/hook-contract.js
CHANGED
|
@@ -20,7 +20,7 @@ const node_fs_1 = require("node:fs");
|
|
|
20
20
|
const node_os_1 = require("node:os");
|
|
21
21
|
const node_path_1 = require("node:path");
|
|
22
22
|
const env_1 = require("./env");
|
|
23
|
-
exports.MCP_ADAPTER_VERSION = '3.9.
|
|
23
|
+
exports.MCP_ADAPTER_VERSION = '3.9.87';
|
|
24
24
|
exports.NATIVE_HOOK_MATCHER = 'Bash|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*';
|
|
25
25
|
exports.GROK_NATIVE_HOOK_MATCHER = 'run_terminal_command|search_replace|write|spawn_subagent|use_tool|workflow|image_gen|image_edit|image_to_video|reference_to_video';
|
|
26
26
|
exports.MCP_PACKAGE_SPEC = `@getmarrow/mcp@${exports.MCP_ADAPTER_VERSION}`;
|
package/package.json
CHANGED
|
@@ -16,14 +16,24 @@ try {
|
|
|
16
16
|
|
|
17
17
|
const CANARY_TOOL_TIMEOUT_MARGIN_MS = 2_000;
|
|
18
18
|
const CANARY_TOOL_TIMEOUT_MAX_MS = 10_000;
|
|
19
|
+
const CANARY_ASYNC_TOOL_TIMEOUT_MAX_MS = 30_000;
|
|
19
20
|
const CANARY_DEFAULT_TOOL_TIMEOUT_MS = Math.min(
|
|
20
21
|
CANARY_TOOL_TIMEOUT_MAX_MS,
|
|
21
22
|
(MARROW_AUTO_RESPONSE_BUDGET_MAX_MS || 8_000) + CANARY_TOOL_TIMEOUT_MARGIN_MS,
|
|
22
23
|
);
|
|
24
|
+
const CANARY_DEFAULT_ASYNC_TOOL_TIMEOUT_MS = Math.min(
|
|
25
|
+
CANARY_ASYNC_TOOL_TIMEOUT_MAX_MS,
|
|
26
|
+
(MARROW_AUTO_RESPONSE_BUDGET_MAX_MS || 8_000) + CANARY_TOOL_TIMEOUT_MARGIN_MS,
|
|
27
|
+
);
|
|
28
|
+
const CANARY_DEFAULT_TOTAL_TIMEOUT_MS = 45_000;
|
|
29
|
+
const TRANSPORT_RETRY_WAIT_MS = 1_000;
|
|
23
30
|
|
|
24
31
|
if (CANARY_DEFAULT_TOOL_TIMEOUT_MS < MARROW_AUTO_RESPONSE_BUDGET_MAX_MS + CANARY_TOOL_TIMEOUT_MARGIN_MS) {
|
|
25
32
|
configurationFailed = true;
|
|
26
33
|
}
|
|
34
|
+
if (CANARY_DEFAULT_ASYNC_TOOL_TIMEOUT_MS < MARROW_AUTO_RESPONSE_BUDGET_MAX_MS + CANARY_TOOL_TIMEOUT_MARGIN_MS) {
|
|
35
|
+
configurationFailed = true;
|
|
36
|
+
}
|
|
27
37
|
|
|
28
38
|
function canaryCases(operationId) {
|
|
29
39
|
return [
|
|
@@ -56,6 +66,15 @@ const HOT_PATH_TOOLS = new Set([
|
|
|
56
66
|
'marrow_agent_runtime',
|
|
57
67
|
'marrow_auto',
|
|
58
68
|
]);
|
|
69
|
+
const ASYNC_PATH_TOOLS = new Set([
|
|
70
|
+
'marrow_auto',
|
|
71
|
+
'marrow_first_value',
|
|
72
|
+
]);
|
|
73
|
+
const TRANSPORT_RETRY_ERROR_CLASSES = new Set(['tool_unavailable', 'unavailable503']);
|
|
74
|
+
const AUTO_TRANSPORT_RETRY_CATEGORIES = new Set([
|
|
75
|
+
'request_failed', 'service_unavailable', 'connection_reset', 'dns_unavailable',
|
|
76
|
+
'tls_failure', 'edge_access_denied', 'rate_limited',
|
|
77
|
+
]);
|
|
59
78
|
const OUTPUT_LIMIT_BYTES = 256 * 1024;
|
|
60
79
|
const TOOL_NAMES = new Set(canaryCases('').map(([name]) => name));
|
|
61
80
|
const errorEvidence = new WeakMap();
|
|
@@ -542,7 +561,14 @@ async function executeCanary(env, options, context) {
|
|
|
542
561
|
CANARY_TOOL_TIMEOUT_MAX_MS,
|
|
543
562
|
env,
|
|
544
563
|
);
|
|
545
|
-
const
|
|
564
|
+
const asyncToolTimeoutMs = boundedMs(
|
|
565
|
+
'MARROW_MCP_CANARY_ASYNC_TOOL_TIMEOUT_MS',
|
|
566
|
+
CANARY_DEFAULT_ASYNC_TOOL_TIMEOUT_MS,
|
|
567
|
+
250,
|
|
568
|
+
CANARY_ASYNC_TOOL_TIMEOUT_MAX_MS,
|
|
569
|
+
env,
|
|
570
|
+
);
|
|
571
|
+
const totalTimeoutMs = boundedMs('MARROW_MCP_CANARY_TOTAL_TIMEOUT_MS', CANARY_DEFAULT_TOTAL_TIMEOUT_MS, 2_000, 60_000, env);
|
|
546
572
|
const expectedVersion = env.MARROW_EXPECTED_MCP_VERSION || packageVersion;
|
|
547
573
|
const childPath = env.MARROW_MCP_CANARY_CHILD || resolve(__dirname, '../dist/cli.js');
|
|
548
574
|
const childEnv = {
|
|
@@ -598,9 +624,11 @@ async function executeCanary(env, options, context) {
|
|
|
598
624
|
const results = context.results;
|
|
599
625
|
for (const [name, args] of cases) {
|
|
600
626
|
const callStarted = performance.now();
|
|
627
|
+
const perToolTimeoutMs = ASYNC_PATH_TOOLS.has(name) ? asyncToolTimeoutMs : toolTimeoutMs;
|
|
601
628
|
let called;
|
|
602
629
|
let payload;
|
|
603
630
|
let attempts = 0;
|
|
631
|
+
let recoveredOnRetry = false;
|
|
604
632
|
context.retryWaitMs = 0;
|
|
605
633
|
context.autoMetrics = undefined;
|
|
606
634
|
context.autoPhase = undefined;
|
|
@@ -613,13 +641,13 @@ async function executeCanary(env, options, context) {
|
|
|
613
641
|
stage('tool_call', name, attempt + 1);
|
|
614
642
|
attempts += 1;
|
|
615
643
|
try {
|
|
616
|
-
called = await client.request('tools/call', { name, arguments: args });
|
|
644
|
+
called = await client.request('tools/call', { name, arguments: args }, perToolTimeoutMs);
|
|
617
645
|
if (client.fatalError) throw client.fatalError;
|
|
618
646
|
stage('tool_validate', name, attempt + 1);
|
|
619
647
|
payload = toolPayload(called, name);
|
|
620
648
|
} catch (error) {
|
|
649
|
+
const evidence = errorEvidence.get(error);
|
|
621
650
|
if (name === 'marrow_auto') {
|
|
622
|
-
const evidence = errorEvidence.get(error);
|
|
623
651
|
autoAttemptRecords.push({
|
|
624
652
|
attempt: attempt + 1,
|
|
625
653
|
duration_ms: safeMs(performance.now() - outerAttemptStarted),
|
|
@@ -631,6 +659,17 @@ async function executeCanary(env, options, context) {
|
|
|
631
659
|
actual_wait_ms: 0,
|
|
632
660
|
});
|
|
633
661
|
}
|
|
662
|
+
// One bounded in-run retry for transport-class failures: a single edge
|
|
663
|
+
// reset or 503 must not fail the canary when the very next call succeeds.
|
|
664
|
+
const remaining = totalTimeoutMs - (performance.now() - context.started);
|
|
665
|
+
const mayRetry = name === 'marrow_auto' ? attempt < 2 : attempt < 1;
|
|
666
|
+
if (evidence && TRANSPORT_RETRY_ERROR_CLASSES.has(evidence.error_class)
|
|
667
|
+
&& mayRetry && remaining > TRANSPORT_RETRY_WAIT_MS + 250) {
|
|
668
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, TRANSPORT_RETRY_WAIT_MS));
|
|
669
|
+
context.retryWaitMs += TRANSPORT_RETRY_WAIT_MS;
|
|
670
|
+
recoveredOnRetry = true;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
634
673
|
throw error;
|
|
635
674
|
}
|
|
636
675
|
if (name !== 'marrow_auto') break;
|
|
@@ -643,11 +682,17 @@ async function executeCanary(env, options, context) {
|
|
|
643
682
|
context.autoMetrics = autoTimings(payload);
|
|
644
683
|
const waitClass = autoWaitClass(payload);
|
|
645
684
|
context.autoPhase = waitClass ? payload.phase : undefined;
|
|
646
|
-
|
|
685
|
+
const autoFailureCategory = payload?.live_delivery?.failure?.error?.category;
|
|
686
|
+
const transportRetryable = waitClass == null
|
|
687
|
+
&& AUTO_TRANSPORT_RETRY_CATEGORIES.has(autoFailureCategory);
|
|
688
|
+
if (waitClass !== 'completion_pending' && !transportRetryable) break;
|
|
647
689
|
if (attempt < 2) {
|
|
648
690
|
const requested = payload.retry_after_ms;
|
|
649
|
-
const
|
|
650
|
-
? Math.ceil(requested) :
|
|
691
|
+
const serverWaitMs = typeof requested === 'number' && Number.isFinite(requested) && requested >= 0
|
|
692
|
+
? Math.ceil(requested) : null;
|
|
693
|
+
const delayMs = transportRetryable
|
|
694
|
+
? Math.max(TRANSPORT_RETRY_WAIT_MS, serverWaitMs ?? 0)
|
|
695
|
+
: serverWaitMs ?? 250;
|
|
651
696
|
const remaining = totalTimeoutMs - (performance.now() - context.started);
|
|
652
697
|
if (delayMs >= remaining) break;
|
|
653
698
|
const waitStarted = performance.now();
|
|
@@ -655,6 +700,7 @@ async function executeCanary(env, options, context) {
|
|
|
655
700
|
const actualWaitMs = Math.round(performance.now() - waitStarted);
|
|
656
701
|
outerAttemptRecord.actual_wait_ms = actualWaitMs;
|
|
657
702
|
context.retryWaitMs += actualWaitMs;
|
|
703
|
+
if (transportRetryable) recoveredOnRetry = true;
|
|
658
704
|
}
|
|
659
705
|
}
|
|
660
706
|
const latencyMs = Math.round(performance.now() - callStarted);
|
|
@@ -662,6 +708,7 @@ async function executeCanary(env, options, context) {
|
|
|
662
708
|
const live = payload.stale !== true && payload.source !== 'last_known' && payload.available !== false;
|
|
663
709
|
if (!live) throw canaryError('contract', `${name} returned cached or unavailable guidance`);
|
|
664
710
|
results.push({ tool: name, ok: true, live: true, latency_ms: latencyMs,
|
|
711
|
+
...(recoveredOnRetry ? { recovered_on_retry: true } : {}),
|
|
665
712
|
...(name === 'marrow_auto' ? {
|
|
666
713
|
...autoTimings(payload),
|
|
667
714
|
attempts,
|
|
@@ -709,7 +756,10 @@ if (require.main === module) {
|
|
|
709
756
|
|
|
710
757
|
module.exports = {
|
|
711
758
|
CANARY_DEFAULT_TOOL_TIMEOUT_MS,
|
|
759
|
+
CANARY_DEFAULT_ASYNC_TOOL_TIMEOUT_MS,
|
|
760
|
+
CANARY_DEFAULT_TOTAL_TIMEOUT_MS,
|
|
712
761
|
CANARY_TOOL_TIMEOUT_MARGIN_MS,
|
|
762
|
+
CANARY_ASYNC_TOOL_TIMEOUT_MAX_MS,
|
|
713
763
|
MARROW_AUTO_RESPONSE_BUDGET_MAX_MS,
|
|
714
764
|
PersistentMcpClient,
|
|
715
765
|
latencyGroup,
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/getmarrow/marrow-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.9.
|
|
9
|
+
"version": "3.9.87",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@getmarrow/mcp",
|
|
14
|
-
"version": "3.9.
|
|
14
|
+
"version": "3.9.87",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|