@getmarrow/mcp 3.9.59 → 3.9.60
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 +15 -1
- package/dist/cli.js +112 -70
- package/dist/cli.js.map +1 -1
- package/dist/hook-context.d.ts +1 -1
- package/dist/hook-contract.d.ts +6 -6
- package/dist/hook-contract.js +1 -1
- package/dist/hook-session.d.ts +1 -1
- package/dist/hook.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -6
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-spool.d.ts +9 -0
- package/dist/lifecycle-spool.d.ts.map +1 -1
- package/dist/lifecycle-spool.js +63 -3
- package/dist/lifecycle-spool.js.map +1 -1
- package/dist/request-reliability.d.ts +4 -0
- package/dist/request-reliability.d.ts.map +1 -1
- package/dist/request-reliability.js +17 -4
- package/dist/request-reliability.js.map +1 -1
- package/dist/runtime-contract.d.ts +2 -1
- package/dist/runtime-contract.d.ts.map +1 -1
- package/dist/runtime-contract.js +124 -2
- package/dist/runtime-contract.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/control-path-canary.cjs +7 -4
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -104,7 +104,21 @@ npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
|
|
|
104
104
|
|
|
105
105
|
Detection and notification are automatic. After explicit installer activation, the local controller may restore only Marrow-managed hooks/configuration. Package upgrades, owner policy, credentials, and unrelated configuration remain explicit and subject to the operator's normal change policy.
|
|
106
106
|
|
|
107
|
-
## What's New in v3.9.
|
|
107
|
+
## What's New in v3.9.60
|
|
108
|
+
|
|
109
|
+
v3.9.60 restores the complete control-and-proof loop for ordinary MCP clients:
|
|
110
|
+
|
|
111
|
+
- cached guidance no longer cuts live status, ask, runtime, or handoff reads down to an impossible 500 ms deadline;
|
|
112
|
+
- `marrow_auto` waits for the bounded think-and-commit path and reports the live decision and proof result in-band;
|
|
113
|
+
- `marrow_commit` now shares the same abort and deadline contract as the other control calls;
|
|
114
|
+
- transient retries use a one-second delay so a slow edge path is not immediately hit again;
|
|
115
|
+
- spool status surfaces backlogs under older credential namespaces without replaying them across an unverified tenant boundary;
|
|
116
|
+
- a plan-gated handoff is reported as unavailable for the current plan, not as an API or authentication outage;
|
|
117
|
+
- the release canary runs with the customer's default client deadlines instead of silently overriding them.
|
|
118
|
+
|
|
119
|
+
MCP-only hosts, including Grok today, receive Marrow's control instructions but cannot expose native always-on lifecycle hooks unless the host implements them. Marrow reports that capability boundary explicitly instead of claiming hook coverage it cannot observe.
|
|
120
|
+
|
|
121
|
+
## Previous: v3.9.59
|
|
108
122
|
|
|
109
123
|
v3.9.59 makes the six-tool control path reliable and honest across ordinary edge and geographic latency:
|
|
110
124
|
|
package/dist/cli.js
CHANGED
|
@@ -127,11 +127,14 @@ async function runSpoolCommand(drain) {
|
|
|
127
127
|
const status = drain
|
|
128
128
|
? await (0, lifecycle_spool_1.drainLifecycleSpool)({ apiKey, baseUrl, agentId: resolved.agentId || undefined })
|
|
129
129
|
: (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey, agentId: resolved.agentId || undefined });
|
|
130
|
+
const namespaceAttention = status.other_namespaces.state === 'attention_required';
|
|
130
131
|
process.stdout.write(`${JSON.stringify({
|
|
131
|
-
ok: drain
|
|
132
|
+
ok: drain
|
|
133
|
+
? status.state === 'clear' && !namespaceAttention
|
|
134
|
+
: status.state !== 'attention_required' && !namespaceAttention,
|
|
132
135
|
lifecycle_spool: status,
|
|
133
136
|
}, null, 2)}\n`);
|
|
134
|
-
if (status.state === 'attention_required')
|
|
137
|
+
if (status.state === 'attention_required' || namespaceAttention)
|
|
135
138
|
process.exitCode = 2;
|
|
136
139
|
else if (drain && status.state !== 'clear')
|
|
137
140
|
process.exitCode = 1;
|
|
@@ -453,6 +456,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
453
456
|
...spool,
|
|
454
457
|
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
455
458
|
};
|
|
459
|
+
result.host_capability = mcpHostCapability();
|
|
456
460
|
if (infrastructureFailure && supportsStale) {
|
|
457
461
|
let cached = null;
|
|
458
462
|
try {
|
|
@@ -479,6 +483,24 @@ if (process.argv[2] !== 'keys') {
|
|
|
479
483
|
}
|
|
480
484
|
return result;
|
|
481
485
|
}
|
|
486
|
+
function mcpHostCapability() {
|
|
487
|
+
const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || '').trim().toLowerCase();
|
|
488
|
+
const host = ['grok', 'claude-code', 'codex', 'cursor', 'windsurf', 'gemini', 'kimi', 'qwen', 'deepseek']
|
|
489
|
+
.includes(raw) ? raw : 'mcp-client';
|
|
490
|
+
return {
|
|
491
|
+
transport: 'mcp_stdio',
|
|
492
|
+
host,
|
|
493
|
+
tools_available: true,
|
|
494
|
+
tool_invocation: 'on_demand',
|
|
495
|
+
passive_hooks: {
|
|
496
|
+
provided_by_mcp_transport: false,
|
|
497
|
+
external_host_hook_state: 'unverified',
|
|
498
|
+
observed_by_this_process: false,
|
|
499
|
+
},
|
|
500
|
+
always_on_state: 'host_dependent',
|
|
501
|
+
exact_next_action: 'Use the Marrow MCP tools on demand. Claim passive coverage only when the host separately configures trusted hooks and Marrow receives their lifecycle receipts.',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
482
504
|
function clientOperationalPayload(toolName, value) {
|
|
483
505
|
const payload = value && typeof value === 'object' && !Array.isArray(value)
|
|
484
506
|
? value
|
|
@@ -486,6 +508,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
486
508
|
const spool = (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey: API_KEY, agentId: FLEET_AGENT_ID });
|
|
487
509
|
return {
|
|
488
510
|
...payload,
|
|
511
|
+
host_capability: payload.host_capability || mcpHostCapability(),
|
|
489
512
|
client_update: payload.client_update || (0, request_reliability_1.localClientUpdate)(),
|
|
490
513
|
control_path: (0, control_path_state_1.controlPathStats)(toolName),
|
|
491
514
|
lifecycle_spool: {
|
|
@@ -512,21 +535,13 @@ if (process.argv[2] !== 'keys') {
|
|
|
512
535
|
function isHighRiskAction(action, type) {
|
|
513
536
|
return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
|
|
514
537
|
}
|
|
515
|
-
function cachedGuidance() {
|
|
516
|
-
try {
|
|
517
|
-
return (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
538
|
async function withControlDeadline(operation, options = {}) {
|
|
524
539
|
const toolName = options.toolName || 'marrow_control';
|
|
525
|
-
const hasCache = options.cacheAware !== false && Boolean(cachedGuidance());
|
|
526
540
|
const configuredTimeoutMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
|
|
541
|
+
const runtimeBudget = toolName === 'marrow_agent_runtime' || toolName === 'marrow_auto.runtime';
|
|
527
542
|
const timeoutMs = Number.isFinite(configuredTimeoutMs)
|
|
528
543
|
? Math.min(10_000, Math.max(150, Math.floor(configuredTimeoutMs)))
|
|
529
|
-
: options.highRisk
|
|
544
|
+
: options.highRisk || runtimeBudget ? 4_500 : 4_000;
|
|
530
545
|
const controller = new AbortController();
|
|
531
546
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
532
547
|
timer.unref?.();
|
|
@@ -1672,7 +1687,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
1672
1687
|
capabilities: { tools: {}, prompts: {} },
|
|
1673
1688
|
serverInfo: { name: 'marrow', version: hook_contract_1.MCP_ADAPTER_VERSION },
|
|
1674
1689
|
...(AUTO_ENROLL ? {
|
|
1675
|
-
instructions: 'Use marrow_agent_runtime before consequential actions, obey fresh allow/warn/review_required/block results, use marrow_ask for relevant prior lessons, and close meaningful outcomes with marrow_commit. Infrastructure failures are not policy denials; continue only low-risk reversible work from the returned outage-safe brief, and require a fresh gate for high-risk work.',
|
|
1690
|
+
instructions: 'Use marrow_agent_runtime before consequential actions, obey fresh allow/warn/review_required/block results, use marrow_ask for relevant prior lessons, and close meaningful outcomes with marrow_commit. Infrastructure failures are not policy denials; continue only low-risk reversible work from the returned outage-safe brief, and require a fresh gate for high-risk work. MCP tools are on demand; do not claim passive or prompt-injection coverage unless the host separately configured trusted hooks and Marrow observed their receipts.',
|
|
1676
1691
|
} : {}),
|
|
1677
1692
|
});
|
|
1678
1693
|
// Auto-enroll: emit enrollment notification on connection
|
|
@@ -1841,12 +1856,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
1841
1856
|
// [FIX #9] Validate required params
|
|
1842
1857
|
const decision_id = requireString(args, 'decision_id');
|
|
1843
1858
|
const outcome = requireString(args, 'outcome');
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
}
|
|
1847
|
-
const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1859
|
+
const commitSuccess = requireBoolean(args, 'success');
|
|
1860
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1848
1861
|
decision_id,
|
|
1849
|
-
success:
|
|
1862
|
+
success: commitSuccess,
|
|
1850
1863
|
outcome,
|
|
1851
1864
|
caused_by: args.caused_by,
|
|
1852
1865
|
proof: args.proof,
|
|
@@ -1858,10 +1871,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
1858
1871
|
surfaces: args.surfaces,
|
|
1859
1872
|
auto_gate: args.auto_gate,
|
|
1860
1873
|
model_usage: args.model_usage,
|
|
1861
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1874
|
+
}, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
|
|
1862
1875
|
const commitResult = { ...result, narrative: result.narrative ?? null };
|
|
1863
|
-
lastCommitted =
|
|
1864
|
-
lastDecisionId = null;
|
|
1876
|
+
lastCommitted = result.committed;
|
|
1877
|
+
lastDecisionId = result.committed ? null : decision_id;
|
|
1865
1878
|
success(id, {
|
|
1866
1879
|
content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
|
|
1867
1880
|
});
|
|
@@ -1953,18 +1966,43 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
1953
1966
|
}
|
|
1954
1967
|
const proofCanClose = !highRisk || (0, runtime_contract_1.highRiskRuntimeCanClose)(runtimeGate, suppliedProof, args.gate_receipt_id);
|
|
1955
1968
|
const acceptedSuccess = proofCanClose ? outcomeSuccess : undefined;
|
|
1969
|
+
const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
|
|
1970
|
+
action,
|
|
1971
|
+
outcome,
|
|
1972
|
+
success: acceptedSuccess,
|
|
1973
|
+
type,
|
|
1974
|
+
proof: suppliedProof,
|
|
1975
|
+
gate_receipt_id: typeof args.gate_receipt_id === 'string'
|
|
1976
|
+
? args.gate_receipt_id
|
|
1977
|
+
: runtimeGate?.gate_receipt?.id,
|
|
1978
|
+
}, SESSION_ID, FLEET_AGENT_ID, 8_000);
|
|
1979
|
+
let delivered = null;
|
|
1980
|
+
let deliveryFailure = null;
|
|
1981
|
+
try {
|
|
1982
|
+
delivered = await delivery();
|
|
1983
|
+
}
|
|
1984
|
+
catch (err) {
|
|
1985
|
+
deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
|
|
1986
|
+
}
|
|
1956
1987
|
const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
|
|
1957
1988
|
apiKey: API_KEY,
|
|
1958
1989
|
baseUrl: BASE_URL,
|
|
1959
|
-
deferDelivery:
|
|
1990
|
+
deferDelivery: false,
|
|
1960
1991
|
event: {
|
|
1961
|
-
event_type:
|
|
1992
|
+
event_type: delivered?.committed
|
|
1993
|
+
? 'outcome_committed'
|
|
1994
|
+
: !highRisk && acceptedSuccess === false
|
|
1995
|
+
? 'tool_failed'
|
|
1996
|
+
: !highRisk && acceptedSuccess === true
|
|
1997
|
+
? 'tool_completed'
|
|
1998
|
+
: 'goal_started',
|
|
1962
1999
|
harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
|
|
1963
2000
|
agent_id: FLEET_AGENT_ID,
|
|
1964
2001
|
session_id: SESSION_ID,
|
|
2002
|
+
decision_id: delivered?.decision_id,
|
|
1965
2003
|
action,
|
|
1966
|
-
outcome_state: 'pending',
|
|
1967
|
-
success: highRisk ? undefined : acceptedSuccess,
|
|
2004
|
+
outcome_state: delivered?.committed ? 'closed' : 'pending',
|
|
2005
|
+
success: delivered?.committed ? acceptedSuccess : highRisk ? undefined : acceptedSuccess,
|
|
1968
2006
|
adapter_version: hook_contract_1.MCP_ADAPTER_VERSION,
|
|
1969
2007
|
capability_level: 'mcp',
|
|
1970
2008
|
},
|
|
@@ -1973,48 +2011,29 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
1973
2011
|
action,
|
|
1974
2012
|
outcome: outcome || 'pending',
|
|
1975
2013
|
warnings: cachedOrientWarnings.map(formatWarningActionably),
|
|
1976
|
-
logging:
|
|
2014
|
+
logging: delivered?.committed
|
|
2015
|
+
? 'governed_commit_confirmed'
|
|
2016
|
+
: delivered
|
|
2017
|
+
? 'intent_confirmed'
|
|
2018
|
+
: 'durably_queued',
|
|
1977
2019
|
receipt,
|
|
1978
|
-
completion_state:
|
|
2020
|
+
completion_state: delivered?.committed
|
|
2021
|
+
? 'closed_with_proof'
|
|
2022
|
+
: !proofCanClose
|
|
2023
|
+
? 'pending_required_proof'
|
|
2024
|
+
: outcomeSuccess === undefined
|
|
2025
|
+
? 'pending_evidence'
|
|
2026
|
+
: 'delivery_pending',
|
|
2027
|
+
decision_id: delivered?.decision_id || null,
|
|
2028
|
+
live_delivery: {
|
|
2029
|
+
accepted: Boolean(delivered),
|
|
2030
|
+
committed: Boolean(delivered?.committed),
|
|
2031
|
+
...(deliveryFailure ? { failure: deliveryFailure } : {}),
|
|
2032
|
+
},
|
|
2033
|
+
host_capability: mcpHostCapability(),
|
|
1979
2034
|
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1980
|
-
...(runtimeGate ? {
|
|
1981
|
-
runtime_gate: runtimeGate,
|
|
1982
|
-
completion_state: proofCanClose ? 'governed_delivery_pending' : 'pending_required_proof',
|
|
1983
|
-
} : {}),
|
|
2035
|
+
...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
|
|
1984
2036
|
};
|
|
1985
|
-
const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
|
|
1986
|
-
action,
|
|
1987
|
-
outcome,
|
|
1988
|
-
success: acceptedSuccess,
|
|
1989
|
-
type,
|
|
1990
|
-
proof: suppliedProof,
|
|
1991
|
-
gate_receipt_id: typeof args.gate_receipt_id === 'string'
|
|
1992
|
-
? args.gate_receipt_id
|
|
1993
|
-
: runtimeGate?.gate_receipt?.id,
|
|
1994
|
-
}, SESSION_ID, FLEET_AGENT_ID, 1_500);
|
|
1995
|
-
if (highRisk && acceptedSuccess !== undefined) {
|
|
1996
|
-
const delivered = await delivery();
|
|
1997
|
-
if (!delivered.committed) {
|
|
1998
|
-
throw new request_reliability_1.MarrowRequestError({
|
|
1999
|
-
code: 'invalid_response',
|
|
2000
|
-
message: 'Marrow did not confirm the governed outcome commit',
|
|
2001
|
-
retryable: true,
|
|
2002
|
-
exactFix: 'Keep the outcome pending and retry marrow_commit with the fresh gate receipt and required proof.',
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
response.logging = 'governed_commit_confirmed';
|
|
2006
|
-
response.completion_state = 'closed_with_proof';
|
|
2007
|
-
response.decision_id = delivered.decision_id;
|
|
2008
|
-
}
|
|
2009
|
-
else if (!highRisk) {
|
|
2010
|
-
void delivery().catch((err) => {
|
|
2011
|
-
const failure = (0, request_reliability_1.structuredRequestFailure)(err);
|
|
2012
|
-
const errorCode = failure.error && typeof failure.error === 'object'
|
|
2013
|
-
? String(failure.error.code || 'request_failed')
|
|
2014
|
-
: 'request_failed';
|
|
2015
|
-
process.stderr.write(`[marrow] marrow_auto queued; live delivery unavailable (${errorCode})\n`);
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
2037
|
success(id, {
|
|
2019
2038
|
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
2020
2039
|
});
|
|
@@ -2431,12 +2450,35 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2431
2450
|
return;
|
|
2432
2451
|
}
|
|
2433
2452
|
if (toolName === 'marrow_handoff_status') {
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2453
|
+
try {
|
|
2454
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
|
|
2455
|
+
status: args.status,
|
|
2456
|
+
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2457
|
+
limit: args.limit,
|
|
2458
|
+
}, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
|
|
2459
|
+
toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
|
|
2460
|
+
}
|
|
2461
|
+
catch (error) {
|
|
2462
|
+
if (error instanceof request_reliability_1.MarrowRequestError && error.backendCode === 'MARROW_PLAN_UPGRADE_REQUIRED') {
|
|
2463
|
+
toolSuccess(id, clientOperationalPayload('marrow_handoff_status', {
|
|
2464
|
+
ok: true,
|
|
2465
|
+
available: false,
|
|
2466
|
+
state: 'not_entitled',
|
|
2467
|
+
failure_kind: 'entitlement',
|
|
2468
|
+
authorization_state: 'plan_limited',
|
|
2469
|
+
credential_valid: true,
|
|
2470
|
+
feature: 'fleet_handoff_status',
|
|
2471
|
+
current_plan: error.currentPlan,
|
|
2472
|
+
required_plan: 'team',
|
|
2473
|
+
required_feature: error.requiredFeature || 'fleet_learning',
|
|
2474
|
+
service_reachable: true,
|
|
2475
|
+
exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
|
|
2476
|
+
}));
|
|
2477
|
+
}
|
|
2478
|
+
else {
|
|
2479
|
+
throw error;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2440
2482
|
return;
|
|
2441
2483
|
}
|
|
2442
2484
|
if (toolName === 'marrow_session_end') {
|