@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/README.md +7 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/hook-contract.d.ts +2 -2
- package/dist/hook-contract.js +1 -1
- package/dist/index.d.ts +33 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +202 -23
- package/dist/index.js.map +1 -1
- package/dist/request-reliability.d.ts +4 -2
- package/dist/request-reliability.d.ts.map +1 -1
- package/dist/request-reliability.js +40 -8
- package/dist/request-reliability.js.map +1 -1
- package/package.json +1 -1
- package/scripts/control-path-canary.cjs +139 -6
- package/server.json +2 -2
package/package.json
CHANGED
|
@@ -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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
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' ? {
|
|
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.
|
|
9
|
+
"version": "3.9.85",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@getmarrow/mcp",
|
|
14
|
-
"version": "3.9.
|
|
14
|
+
"version": "3.9.85",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|