@getmarrow/mcp 3.9.73 → 3.9.74
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 +24 -19
- package/dist/cli.js +28 -26
- package/dist/cli.js.map +1 -1
- package/dist/hook-context.d.ts +1 -1
- package/dist/hook-context.d.ts.map +1 -1
- package/dist/hook-context.js +5 -9
- package/dist/hook-context.js.map +1 -1
- package/dist/hook-contract.d.ts +28 -15
- package/dist/hook-contract.d.ts.map +1 -1
- package/dist/hook-contract.js +60 -25
- package/dist/hook-contract.js.map +1 -1
- package/dist/hook-pre-action.d.ts +2 -0
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +52 -12
- package/dist/hook-pre-action.js.map +1 -1
- package/dist/hook-session.d.ts +1 -1
- package/dist/hook-session.d.ts.map +1 -1
- package/dist/hook-session.js +4 -6
- package/dist/hook-session.js.map +1 -1
- package/dist/hook.d.ts +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +4 -6
- package/dist/hook.js.map +1 -1
- package/dist/host-capability.d.ts +23 -16
- package/dist/host-capability.d.ts.map +1 -1
- package/dist/host-capability.js +41 -61
- package/dist/host-capability.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -120
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-spool.d.ts +1 -5
- package/dist/lifecycle-spool.d.ts.map +1 -1
- package/dist/lifecycle-spool.js +11 -28
- package/dist/lifecycle-spool.js.map +1 -1
- package/package.json +1 -1
- package/scripts/control-path-canary.cjs +28 -2
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @getmarrow/mcp — API Functions
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MARROW_AUTO_RESPONSE_BUDGET_MAX_MS = void 0;
|
|
6
7
|
exports.validatePathParam = validatePathParam;
|
|
7
8
|
exports.validateBaseUrl = validateBaseUrl;
|
|
8
9
|
exports.marrowCreateKey = marrowCreateKey;
|
|
@@ -407,7 +408,7 @@ async function marrowThink(apiKey, baseUrl, params, sessionId, agentId, signal,
|
|
|
407
408
|
? await fetch(thinkUrl, thinkInit)
|
|
408
409
|
: await fetchWithRetryQueue(thinkUrl, thinkInit, true);
|
|
409
410
|
const json = await safeJsonResponse(res);
|
|
410
|
-
return json.data;
|
|
411
|
+
return markAutoResponseStatus(json.data, res.status);
|
|
411
412
|
}
|
|
412
413
|
/**
|
|
413
414
|
* Explicitly commit the result of an action to Marrow.
|
|
@@ -478,13 +479,20 @@ async function marrowCommit(apiKey, baseUrl, params, sessionId, agentId, signal,
|
|
|
478
479
|
? await fetch(`${baseUrl}/v1/agent/commit`, commitInit)
|
|
479
480
|
: await fetchWithRetryQueue(`${baseUrl}/v1/agent/commit`, commitInit, true);
|
|
480
481
|
const json = await safeJsonResponse(res);
|
|
482
|
+
if (res.status === 202
|
|
483
|
+
&& json.data
|
|
484
|
+
&& typeof json.data === 'object'
|
|
485
|
+
&& !Array.isArray(json.data)
|
|
486
|
+
&& (json.data.phase === undefined || json.data.phase === 'commit_pending' || json.data.resumable === true)) {
|
|
487
|
+
return markAutoResponseStatus({ ...json.data, committed: false }, res.status);
|
|
488
|
+
}
|
|
481
489
|
if (!json.data
|
|
482
490
|
|| typeof json.data !== 'object'
|
|
483
491
|
|| Array.isArray(json.data)
|
|
484
492
|
|| typeof json.data.committed !== 'boolean') {
|
|
485
493
|
throw (0, request_reliability_1.invalidResponseError)();
|
|
486
494
|
}
|
|
487
|
-
return { ...json.data, committed: json.data.committed, runtime_gate: runtimeGate };
|
|
495
|
+
return markAutoResponseStatus({ ...json.data, committed: json.data.committed, runtime_gate: runtimeGate }, res.status);
|
|
488
496
|
}
|
|
489
497
|
async function marrowModelUsage(apiKey, baseUrl, input, sessionId, agentId) {
|
|
490
498
|
const body = normalizeModelUsage({
|
|
@@ -520,6 +528,10 @@ function createTimeoutSignal(timeoutMs, startedAt) {
|
|
|
520
528
|
const SAFE_AUTO_OPERATION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,79}$/;
|
|
521
529
|
const AUTO_OPERATION_BINDING_TTL_MS = 30 * 60 * 1_000;
|
|
522
530
|
const AUTO_OPERATION_BINDING_LIMIT = 256;
|
|
531
|
+
const AUTO_RESPONSE_BUDGET_DEFAULT_MS = 8_000;
|
|
532
|
+
exports.MARROW_AUTO_RESPONSE_BUDGET_MAX_MS = 8_000;
|
|
533
|
+
const AUTO_RESPONSE_DEADLINE_MARGIN_MS = 75;
|
|
534
|
+
const AUTO_RESPONSE_STATUS = Symbol('marrowAutoResponseStatus');
|
|
523
535
|
const autoOperationBindings = new Map();
|
|
524
536
|
function canonicalAutoBindingValue(value) {
|
|
525
537
|
if (Array.isArray(value))
|
|
@@ -589,6 +601,36 @@ function resolveAutoOperationId(value) {
|
|
|
589
601
|
function autoIdempotencyKey(operationId, phase) {
|
|
590
602
|
return `mcp-auto:${operationId}:${phase}`;
|
|
591
603
|
}
|
|
604
|
+
function autoResponseBudget(timeoutMs) {
|
|
605
|
+
const requested = Number(timeoutMs);
|
|
606
|
+
return Number.isFinite(requested) && requested > 0
|
|
607
|
+
? Math.min(exports.MARROW_AUTO_RESPONSE_BUDGET_MAX_MS, Math.max(500, Math.floor(requested)))
|
|
608
|
+
: AUTO_RESPONSE_BUDGET_DEFAULT_MS;
|
|
609
|
+
}
|
|
610
|
+
function markAutoResponseStatus(value, status) {
|
|
611
|
+
if (value && typeof value === 'object') {
|
|
612
|
+
Object.defineProperty(value, AUTO_RESPONSE_STATUS, { value: status });
|
|
613
|
+
}
|
|
614
|
+
return value;
|
|
615
|
+
}
|
|
616
|
+
function isAutoPendingResponse(value, phase) {
|
|
617
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
618
|
+
return false;
|
|
619
|
+
const pending = value;
|
|
620
|
+
return pending[AUTO_RESPONSE_STATUS] === 202
|
|
621
|
+
&& (pending.phase === phase || (pending.phase === undefined && pending.resumable === true));
|
|
622
|
+
}
|
|
623
|
+
async function waitForAutoContinuation(pending, startedAt, responseBudgetMs) {
|
|
624
|
+
const remaining = responseBudgetMs - (Date.now() - startedAt) - AUTO_RESPONSE_DEADLINE_MARGIN_MS;
|
|
625
|
+
if (remaining <= 0)
|
|
626
|
+
return false;
|
|
627
|
+
const requestedDelay = Number(pending.retry_after_ms);
|
|
628
|
+
const delayMs = Number.isFinite(requestedDelay)
|
|
629
|
+
? Math.min(500, Math.max(25, Math.floor(requestedDelay)))
|
|
630
|
+
: 50;
|
|
631
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(delayMs, remaining)));
|
|
632
|
+
return responseBudgetMs - (Date.now() - startedAt) > AUTO_RESPONSE_DEADLINE_MARGIN_MS;
|
|
633
|
+
}
|
|
592
634
|
function autoPartial(input) {
|
|
593
635
|
return {
|
|
594
636
|
operation_id: input.operationId,
|
|
@@ -605,12 +647,15 @@ function autoPartial(input) {
|
|
|
605
647
|
};
|
|
606
648
|
}
|
|
607
649
|
/**
|
|
608
|
-
*
|
|
609
|
-
*
|
|
650
|
+
* Bounded outcome logging helper for tool hooks and simple integrations.
|
|
651
|
+
* One outer invocation logs intent and, when an outcome is supplied, continues
|
|
652
|
+
* resumable server phases so the outcome normally closes in-band. If the
|
|
653
|
+
* caller's deadline is reached, the same operation ID resumes without opening
|
|
654
|
+
* another decision.
|
|
610
655
|
*/
|
|
611
656
|
async function marrowAuto(apiKey, baseUrl, params, sessionId, agentId, timeoutMs) {
|
|
612
657
|
const startedAt = Date.now();
|
|
613
|
-
const responseBudgetMs =
|
|
658
|
+
const responseBudgetMs = autoResponseBudget(timeoutMs);
|
|
614
659
|
const operationId = resolveAutoOperationId(params.operation_id);
|
|
615
660
|
const operationBinding = boundAutoOperation(operationId, autoOperationSignature({
|
|
616
661
|
apiKey,
|
|
@@ -638,36 +683,36 @@ async function marrowAuto(apiKey, baseUrl, params, sessionId, agentId, timeoutMs
|
|
|
638
683
|
timings.runtime = 0;
|
|
639
684
|
}
|
|
640
685
|
else {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
686
|
+
while (!runtimeGate) {
|
|
687
|
+
const runtimeTimeout = createTimeoutSignal(responseBudgetMs, startedAt);
|
|
688
|
+
try {
|
|
689
|
+
runtimeGate = await marrowAgentRuntime(apiKey, baseUrl, {
|
|
690
|
+
action: (0, redact_1.redactSensitiveText)(params.action_for_gate || params.action),
|
|
691
|
+
type: params.type || 'general',
|
|
692
|
+
agent_id: agentId,
|
|
693
|
+
session_id: sessionId,
|
|
694
|
+
surfaces: params.surfaces,
|
|
695
|
+
// Proof is commit evidence, not part of immutable runtime authorization.
|
|
696
|
+
// Keeping it out makes missing -> supplied proof a monotonic continuation.
|
|
697
|
+
context: { source: 'mcp_auto_risk_upgrade', operation_id: operationId },
|
|
698
|
+
}, sessionId, agentId, runtimeTimeout.signal, autoIdempotencyKey(operationId, 'runtime'));
|
|
699
|
+
operationBinding.runtimeGate = runtimeGate;
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code !== 'request_timeout'
|
|
703
|
+
|| !await waitForAutoContinuation({}, startedAt, responseBudgetMs)) {
|
|
704
|
+
timings.runtime = Date.now() - phaseStarted;
|
|
705
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code === 'request_timeout') {
|
|
706
|
+
return autoPartial({ operationId, phase: 'runtime_pending', timings, startedAt });
|
|
707
|
+
}
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
finally {
|
|
712
|
+
runtimeTimeout.cancel();
|
|
665
713
|
}
|
|
666
|
-
throw error;
|
|
667
|
-
}
|
|
668
|
-
finally {
|
|
669
|
-
runtimeTimeout.cancel();
|
|
670
714
|
}
|
|
715
|
+
timings.runtime = Date.now() - phaseStarted;
|
|
671
716
|
}
|
|
672
717
|
gateReceiptId = (0, runtime_contract_1.runtimeAuthorizationReceiptId)(runtimeGate) || gateReceiptId;
|
|
673
718
|
if (!gateReceiptId) {
|
|
@@ -677,56 +722,59 @@ async function marrowAuto(apiKey, baseUrl, params, sessionId, agentId, timeoutMs
|
|
|
677
722
|
|| (0, runtime_contract_1.highRiskRuntimeCanContinueWithProof)(runtimeGate, params.proof, gateReceiptId);
|
|
678
723
|
}
|
|
679
724
|
const thinkStarted = Date.now();
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
catch (error) {
|
|
703
|
-
timings.think = Date.now() - thinkStarted;
|
|
704
|
-
if ((0, request_reliability_1.normalizeRequestError)(error).code === 'request_timeout') {
|
|
705
|
-
return autoPartial({
|
|
706
|
-
operationId,
|
|
707
|
-
phase: 'think_pending',
|
|
708
|
-
runtimeGate,
|
|
709
|
-
timings,
|
|
710
|
-
startedAt,
|
|
725
|
+
let decisionId = operationBinding.decisionId || null;
|
|
726
|
+
const reusedDecision = Boolean(decisionId);
|
|
727
|
+
while (!decisionId) {
|
|
728
|
+
const thinkTimeout = createTimeoutSignal(responseBudgetMs, startedAt);
|
|
729
|
+
let thinkResult;
|
|
730
|
+
try {
|
|
731
|
+
thinkResult = await marrowThink(apiKey, baseUrl, {
|
|
732
|
+
action: params.action,
|
|
733
|
+
type: params.type || 'general',
|
|
734
|
+
context: params.context,
|
|
735
|
+
source_kind: 'agent_autonomous',
|
|
736
|
+
source_confidence: 0.9,
|
|
737
|
+
human_directed: false,
|
|
738
|
+
source_meta: {
|
|
739
|
+
channel: 'mcp',
|
|
740
|
+
client: defaultSourceClient(),
|
|
741
|
+
user_intent: 'operate',
|
|
742
|
+
...(params.source_meta || {}),
|
|
743
|
+
},
|
|
744
|
+
}, sessionId, agentId, thinkTimeout.signal, {
|
|
745
|
+
idempotencyKey: autoIdempotencyKey(operationId, 'think'),
|
|
746
|
+
responseMode: 'ack',
|
|
711
747
|
});
|
|
712
748
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
749
|
+
catch (error) {
|
|
750
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code !== 'request_timeout'
|
|
751
|
+
|| !await waitForAutoContinuation({}, startedAt, responseBudgetMs)) {
|
|
752
|
+
timings.think = Date.now() - thinkStarted;
|
|
753
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code === 'request_timeout') {
|
|
754
|
+
return autoPartial({ operationId, phase: 'think_pending', runtimeGate, timings, startedAt });
|
|
755
|
+
}
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
finally {
|
|
761
|
+
thinkTimeout.cancel();
|
|
762
|
+
}
|
|
763
|
+
decisionId = typeof thinkResult.decision_id === 'string' && thinkResult.decision_id.trim()
|
|
764
|
+
? thinkResult.decision_id.trim()
|
|
765
|
+
: null;
|
|
766
|
+
if (decisionId) {
|
|
767
|
+
operationBinding.decisionId = decisionId;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
if (!isAutoPendingResponse(thinkResult, 'think_pending'))
|
|
771
|
+
throw (0, request_reliability_1.invalidResponseError)();
|
|
772
|
+
if (!await waitForAutoContinuation(thinkResult, startedAt, responseBudgetMs)) {
|
|
773
|
+
timings.think = Date.now() - thinkStarted;
|
|
774
|
+
return autoPartial({ operationId, phase: 'think_pending', runtimeGate, timings, startedAt });
|
|
775
|
+
}
|
|
729
776
|
}
|
|
777
|
+
timings.think = reusedDecision ? 0 : Date.now() - thinkStarted;
|
|
730
778
|
if (params.outcome === undefined || typeof params.success !== 'boolean') {
|
|
731
779
|
return autoPartial({
|
|
732
780
|
operationId,
|
|
@@ -760,49 +808,48 @@ async function marrowAuto(apiKey, baseUrl, params, sessionId, agentId, timeoutMs
|
|
|
760
808
|
});
|
|
761
809
|
}
|
|
762
810
|
const commitStarted = Date.now();
|
|
763
|
-
const commitTimeout = createTimeoutSignal(responseBudgetMs, startedAt);
|
|
764
811
|
let commitResult;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
812
|
+
while (true) {
|
|
813
|
+
const commitTimeout = createTimeoutSignal(responseBudgetMs, startedAt);
|
|
814
|
+
try {
|
|
815
|
+
commitResult = await marrowCommit(apiKey, baseUrl, {
|
|
816
|
+
decision_id: decisionId,
|
|
817
|
+
success: params.success,
|
|
818
|
+
outcome: params.outcome,
|
|
819
|
+
proof: params.proof,
|
|
820
|
+
gate_receipt_id: gateReceiptId,
|
|
821
|
+
action: params.action_for_gate || params.action,
|
|
822
|
+
type: params.type || 'general',
|
|
823
|
+
surfaces: params.surfaces,
|
|
824
|
+
auto_gate: false,
|
|
825
|
+
}, sessionId, agentId, commitTimeout.signal, autoIdempotencyKey(operationId, 'commit'));
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code !== 'request_timeout'
|
|
829
|
+
|| !await waitForAutoContinuation({}, startedAt, responseBudgetMs)) {
|
|
830
|
+
timings.commit = Date.now() - commitStarted;
|
|
831
|
+
if ((0, request_reliability_1.normalizeRequestError)(error).code === 'request_timeout') {
|
|
832
|
+
return autoPartial({ operationId, decisionId, phase: 'commit_pending', runtimeGate, timings, startedAt });
|
|
833
|
+
}
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
finally {
|
|
839
|
+
commitTimeout.cancel();
|
|
840
|
+
}
|
|
841
|
+
if (commitResult.committed)
|
|
842
|
+
break;
|
|
843
|
+
if (!isAutoPendingResponse(commitResult, 'commit_pending')) {
|
|
844
|
+
timings.commit = Date.now() - commitStarted;
|
|
845
|
+
return autoPartial({ operationId, decisionId, phase: 'commit_pending', runtimeGate, timings, startedAt });
|
|
846
|
+
}
|
|
847
|
+
if (!await waitForAutoContinuation(commitResult, startedAt, responseBudgetMs)) {
|
|
848
|
+
timings.commit = Date.now() - commitStarted;
|
|
849
|
+
return autoPartial({ operationId, decisionId, phase: 'commit_pending', runtimeGate, timings, startedAt });
|
|
790
850
|
}
|
|
791
|
-
throw error;
|
|
792
|
-
}
|
|
793
|
-
finally {
|
|
794
|
-
commitTimeout.cancel();
|
|
795
|
-
}
|
|
796
|
-
if (!commitResult.committed) {
|
|
797
|
-
return autoPartial({
|
|
798
|
-
operationId,
|
|
799
|
-
decisionId,
|
|
800
|
-
phase: 'commit_pending',
|
|
801
|
-
runtimeGate,
|
|
802
|
-
timings,
|
|
803
|
-
startedAt,
|
|
804
|
-
});
|
|
805
851
|
}
|
|
852
|
+
timings.commit = Date.now() - commitStarted;
|
|
806
853
|
return {
|
|
807
854
|
operation_id: operationId,
|
|
808
855
|
decision_id: decisionId,
|