@getmarrow/mcp 3.9.84 → 3.9.85

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.84",
3
+ "version": "3.9.85",
4
4
  "mcpName": "io.github.getmarrow/marrow",
5
5
  "description": "Runtime control, proof, and fleet intelligence for MCP-compatible AI agents.",
6
6
  "bin": {
@@ -112,6 +112,103 @@ function autoTimings(payload) {
112
112
  return result;
113
113
  }
114
114
 
115
+ const AUTO_HTTP_ERROR_CATEGORIES = new Set([
116
+ 'authentication_required', 'permission_denied', 'proof_required', 'rate_limited',
117
+ 'request_timeout', 'dns_unavailable', 'connection_reset', 'tls_failure',
118
+ 'edge_access_denied', 'service_unavailable', 'invalid_response', 'request_failed',
119
+ ]);
120
+ const AUTO_TRACE_CODES = new Set([
121
+ 'runtime_pending', 'think_pending', 'commit_pending', 'pending', 'created',
122
+ 'committed', 'closed', 'replayed', 'idempotent_replay', 'lost_ack_recovered',
123
+ 'duplicate', 'original', 'new',
124
+ ]);
125
+
126
+ function safeAutoHttpTrace(payload) {
127
+ const source = payload?.http_attempt_trace;
128
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
129
+ return { attempts: [], dropped_count: 0 };
130
+ }
131
+ const attempts = [];
132
+ let rejected = Array.isArray(source.attempts) ? Math.max(0, source.attempts.length - 12) : 0;
133
+ for (const value of Array.isArray(source.attempts) ? source.attempts.slice(0, 12) : []) {
134
+ if (!value || typeof value !== 'object' || Array.isArray(value)
135
+ || !['think', 'commit'].includes(value.route_phase)) {
136
+ rejected += 1;
137
+ continue;
138
+ }
139
+ const serverTimings = {};
140
+ const spans = value.server_timings_ms;
141
+ if (spans && typeof spans === 'object' && !Array.isArray(spans)) {
142
+ for (const name of ['auth_ms', 'parse_ms']) {
143
+ const duration = spans[name];
144
+ if (typeof duration === 'number' && Number.isFinite(duration) && duration >= 0 && duration <= 60_000) {
145
+ serverTimings[name] = Math.round(duration);
146
+ }
147
+ }
148
+ }
149
+ const status = Number.isInteger(value.status) && value.status >= 100 && value.status <= 599
150
+ ? value.status : null;
151
+ const errorCategory = AUTO_HTTP_ERROR_CATEGORIES.has(value.error_category)
152
+ ? value.error_category : null;
153
+ attempts.push({
154
+ route_phase: value.route_phase,
155
+ duration_ms: safeMs(value.duration_ms),
156
+ status,
157
+ error_category: errorCategory,
158
+ typed_timeout: errorCategory === 'request_timeout' && value.typed_timeout === true,
159
+ pending_code: AUTO_TRACE_CODES.has(value.pending_code) ? value.pending_code : null,
160
+ replay_code: AUTO_TRACE_CODES.has(value.replay_code) ? value.replay_code : null,
161
+ server_timings_ms: serverTimings,
162
+ server_timing_coverage: Object.keys(serverTimings).length ? 'partial' : 'unavailable',
163
+ requested_wait_ms: value.requested_wait_ms == null ? null : safeMs(value.requested_wait_ms),
164
+ actual_wait_ms: safeMs(value.actual_wait_ms),
165
+ });
166
+ }
167
+ const reportedDropped = Number.isInteger(source.dropped_count) && source.dropped_count >= 0
168
+ ? Math.min(999, source.dropped_count) : 0;
169
+ return { attempts, dropped_count: Math.min(999, reportedDropped + rejected) };
170
+ }
171
+
172
+ function autoOuterAttempt(payload, attempt, durationMs) {
173
+ const phase = ['runtime_pending', 'think_pending', 'decision_created', 'proof_required',
174
+ 'review_required', 'owner_approval_required', 'commit_pending', 'closed'].includes(payload?.phase)
175
+ ? payload.phase : null;
176
+ const deliveryFailure = payload?.live_delivery?.failure?.error;
177
+ const errorCategory = AUTO_HTTP_ERROR_CATEGORIES.has(deliveryFailure?.category)
178
+ ? deliveryFailure.category : null;
179
+ return {
180
+ attempt,
181
+ duration_ms: safeMs(durationMs),
182
+ phase,
183
+ status: errorCategory ? 'failed' : payload?.live_delivery?.committed === true
184
+ ? 'committed' : payload?.live_delivery?.accepted === true ? 'pending' : 'unconfirmed',
185
+ error_category: errorCategory,
186
+ ...autoTimings(payload),
187
+ http_attempt_trace: safeAutoHttpTrace(payload),
188
+ requested_wait_ms: payload?.retry_after_ms == null ? null : safeMs(payload.retry_after_ms),
189
+ actual_wait_ms: 0,
190
+ };
191
+ }
192
+
193
+ function safeAutoOuterAttempts(source) {
194
+ if (!Array.isArray(source)) return [];
195
+ return source.slice(0, 3).map((record, index) => ({
196
+ attempt: index + 1,
197
+ duration_ms: safeMs(record?.duration_ms),
198
+ phase: ['runtime_pending', 'think_pending', 'decision_created', 'proof_required',
199
+ 'review_required', 'owner_approval_required', 'commit_pending', 'closed'].includes(record?.phase)
200
+ ? record.phase : null,
201
+ status: ['committed', 'pending', 'failed', 'unconfirmed'].includes(record?.status)
202
+ ? record.status : 'unconfirmed',
203
+ error_category: AUTO_HTTP_ERROR_CATEGORIES.has(record?.error_category)
204
+ ? record.error_category : null,
205
+ ...autoTimings(record),
206
+ http_attempt_trace: safeAutoHttpTrace({ http_attempt_trace: record?.http_attempt_trace }),
207
+ requested_wait_ms: record?.requested_wait_ms == null ? null : safeMs(record.requested_wait_ms),
208
+ actual_wait_ms: safeMs(record?.actual_wait_ms),
209
+ }));
210
+ }
211
+
115
212
  function autoWaitClass(payload) {
116
213
  if (payload?.live_delivery?.committed !== false
117
214
  || typeof payload.live_delivery.accepted !== 'boolean'
@@ -131,6 +228,7 @@ function failureRecord(error, context = {}) {
131
228
  tool: row.tool, ok: true, live: true, latency_ms: safeMs(row.latency_ms),
132
229
  ...(row.tool === 'marrow_auto' ? {
133
230
  ...autoTimings(row), attempts: row.attempts, retry_wait_ms: safeMs(row.retry_wait_ms),
231
+ auto_attempt_records: safeAutoOuterAttempts(row.auto_attempt_records),
134
232
  } : {}),
135
233
  }));
136
234
  return Object.freeze({
@@ -150,6 +248,7 @@ function failureRecord(error, context = {}) {
150
248
  results: Object.freeze(results),
151
249
  ...(context.tool === 'marrow_auto' ? {
152
250
  ...autoTimings(context.autoMetrics), retry_wait_ms: safeMs(context.retryWaitMs),
251
+ auto_attempt_records: safeAutoOuterAttempts(context.autoAttempts),
153
252
  ...(context.autoPhase ? { auto_phase: context.autoPhase } : {}),
154
253
  } : {}),
155
254
  ...(evidence.http_status === undefined ? {} : { http_status: evidence.http_status }),
@@ -505,15 +604,42 @@ async function executeCanary(env, options, context) {
505
604
  context.retryWaitMs = 0;
506
605
  context.autoMetrics = undefined;
507
606
  context.autoPhase = undefined;
607
+ context.autoAttempts = undefined;
608
+ const autoAttemptRecords = [];
609
+ if (name === 'marrow_auto') context.autoAttempts = autoAttemptRecords;
508
610
  for (let attempt = 0; attempt < 3; attempt += 1) {
611
+ const outerAttemptStarted = performance.now();
509
612
  if (client.fatalError) throw client.fatalError;
510
613
  stage('tool_call', name, attempt + 1);
511
614
  attempts += 1;
512
- called = await client.request('tools/call', { name, arguments: args });
513
- if (client.fatalError) throw client.fatalError;
514
- stage('tool_validate', name, attempt + 1);
515
- payload = toolPayload(called, name);
615
+ try {
616
+ called = await client.request('tools/call', { name, arguments: args });
617
+ if (client.fatalError) throw client.fatalError;
618
+ stage('tool_validate', name, attempt + 1);
619
+ payload = toolPayload(called, name);
620
+ } catch (error) {
621
+ if (name === 'marrow_auto') {
622
+ const evidence = errorEvidence.get(error);
623
+ autoAttemptRecords.push({
624
+ attempt: attempt + 1,
625
+ duration_ms: safeMs(performance.now() - outerAttemptStarted),
626
+ phase: null,
627
+ status: 'failed',
628
+ error_category: evidence?.error_class === 'request_timeout' ? 'request_timeout' : null,
629
+ http_attempt_trace: { attempts: [], dropped_count: 0 },
630
+ requested_wait_ms: null,
631
+ actual_wait_ms: 0,
632
+ });
633
+ }
634
+ throw error;
635
+ }
516
636
  if (name !== 'marrow_auto') break;
637
+ const outerAttemptRecord = autoOuterAttempt(
638
+ payload,
639
+ attempt + 1,
640
+ performance.now() - outerAttemptStarted,
641
+ );
642
+ autoAttemptRecords.push(outerAttemptRecord);
517
643
  context.autoMetrics = autoTimings(payload);
518
644
  const waitClass = autoWaitClass(payload);
519
645
  context.autoPhase = waitClass ? payload.phase : undefined;
@@ -526,7 +652,9 @@ async function executeCanary(env, options, context) {
526
652
  if (delayMs >= remaining) break;
527
653
  const waitStarted = performance.now();
528
654
  await new Promise((resolveDelay) => setTimeout(resolveDelay, delayMs));
529
- context.retryWaitMs += Math.round(performance.now() - waitStarted);
655
+ const actualWaitMs = Math.round(performance.now() - waitStarted);
656
+ outerAttemptRecord.actual_wait_ms = actualWaitMs;
657
+ context.retryWaitMs += actualWaitMs;
530
658
  }
531
659
  }
532
660
  const latencyMs = Math.round(performance.now() - callStarted);
@@ -534,7 +662,12 @@ async function executeCanary(env, options, context) {
534
662
  const live = payload.stale !== true && payload.source !== 'last_known' && payload.available !== false;
535
663
  if (!live) throw canaryError('contract', `${name} returned cached or unavailable guidance`);
536
664
  results.push({ tool: name, ok: true, live: true, latency_ms: latencyMs,
537
- ...(name === 'marrow_auto' ? { ...autoTimings(payload), attempts, retry_wait_ms: context.retryWaitMs } : {}),
665
+ ...(name === 'marrow_auto' ? {
666
+ ...autoTimings(payload),
667
+ attempts,
668
+ retry_wait_ms: context.retryWaitMs,
669
+ auto_attempt_records: safeAutoOuterAttempts(autoAttemptRecords),
670
+ } : {}),
538
671
  });
539
672
  }
540
673
  const hotPath = results.filter((row) => HOT_PATH_TOOLS.has(row.tool));
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.84",
9
+ "version": "3.9.85",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@getmarrow/mcp",
14
- "version": "3.9.84",
14
+ "version": "3.9.85",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },