@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 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.59
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 ? status.state === 'clear' : status.state !== 'attention_required',
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 ? 4_500 : hasCache ? 500 : 4_000;
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
- if (typeof args.success !== 'boolean') {
1845
- throw new Error('"success" is required and must be a boolean');
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: args.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 = true;
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: true,
1990
+ deferDelivery: false,
1960
1991
  event: {
1961
- event_type: !highRisk && acceptedSuccess === false ? 'tool_failed' : !highRisk && acceptedSuccess === true ? 'tool_completed' : 'goal_started',
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: 'durably_queued',
2014
+ logging: delivered?.committed
2015
+ ? 'governed_commit_confirmed'
2016
+ : delivered
2017
+ ? 'intent_confirmed'
2018
+ : 'durably_queued',
1977
2019
  receipt,
1978
- completion_state: outcomeSuccess === undefined ? 'pending_evidence' : 'delivery_pending',
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
- const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2435
- status: args.status,
2436
- agentId: args.agentId || FLEET_AGENT_ID,
2437
- limit: args.limit,
2438
- }, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
2439
- toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
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') {