@getmarrow/mcp 3.9.85 → 3.9.86

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/mcp",
3
- "version": "3.9.85",
3
+ "version": "3.9.86",
4
4
  "mcpName": "io.github.getmarrow/marrow",
5
5
  "description": "Runtime control, proof, and fleet intelligence for MCP-compatible AI agents.",
6
6
  "bin": {
@@ -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 totalTimeoutMs = boundedMs('MARROW_MCP_CANARY_TOTAL_TIMEOUT_MS', 30_000, 2_000, 60_000, env);
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
- if (waitClass !== 'completion_pending') break;
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 delayMs = typeof requested === 'number' && Number.isFinite(requested) && requested >= 0
650
- ? Math.ceil(requested) : 250;
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,