@getmarrow/mcp 3.9.59 → 3.9.61

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,30 @@ 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.61
108
+
109
+ v3.9.61 keeps an authoritative proof-pack rejection distinct from a control-path outage:
110
+
111
+ - backend `MARROW_PROOF_PACK_INCOMPLETE` responses are reported as `validation` / `proof_required`, not infrastructure failures;
112
+ - the exact missing proof fields and backend repair instruction remain visible to the agent;
113
+ - live proof validation does not return a stale outage brief or unavailable authorization state;
114
+ - proof enforcement remains fail-closed, and successful commit behavior is unchanged.
115
+
116
+ ## Previous: v3.9.60
117
+
118
+ v3.9.60 restores the complete control-and-proof loop for ordinary MCP clients:
119
+
120
+ - cached guidance no longer cuts live status, ask, runtime, or handoff reads down to an impossible 500 ms deadline;
121
+ - `marrow_auto` waits for the bounded think-and-commit path and reports the live decision and proof result in-band;
122
+ - `marrow_commit` now shares the same abort and deadline contract as the other control calls;
123
+ - transient retries use a one-second delay so a slow edge path is not immediately hit again;
124
+ - spool status surfaces backlogs under older credential namespaces without replaying them across an unverified tenant boundary;
125
+ - a plan-gated handoff is reported as unavailable for the current plan, not as an API or authentication outage;
126
+ - the release canary runs with the customer's default client deadlines instead of silently overriding them.
127
+
128
+ 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.
129
+
130
+ ## Previous: v3.9.59
108
131
 
109
132
  v3.9.59 makes the six-tool control path reliable and honest across ordinary edge and geographic latency:
110
133
 
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;
@@ -444,15 +447,21 @@ if (process.argv[2] !== 'keys') {
444
447
  }
445
448
  function toolFailure(toolName, failure) {
446
449
  const result = (0, request_reliability_1.structuredRequestFailure)(failure);
447
- const infrastructureFailure = !['authentication_required', 'permission_denied'].includes(failure.code);
450
+ const proofValidation = failure.code === 'proof_required';
451
+ const infrastructureFailure = !proofValidation && !['authentication_required', 'permission_denied'].includes(failure.code);
448
452
  const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_handoff_status', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
449
453
  const spool = (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey: API_KEY, agentId: FLEET_AGENT_ID });
450
- result.failure_kind = infrastructureFailure ? 'infrastructure' : 'authorization';
454
+ result.failure_kind = proofValidation ? 'validation' : infrastructureFailure ? 'infrastructure' : 'authorization';
451
455
  result.control_path = (0, control_path_state_1.controlPathStats)(toolName || 'marrow_control');
452
456
  result.lifecycle_spool = {
453
457
  ...spool,
454
458
  drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
455
459
  };
460
+ result.host_capability = mcpHostCapability();
461
+ if (proofValidation) {
462
+ result.proof_required = true;
463
+ result.exact_next_action = failure.exactFix;
464
+ }
456
465
  if (infrastructureFailure && supportsStale) {
457
466
  let cached = null;
458
467
  try {
@@ -479,6 +488,24 @@ if (process.argv[2] !== 'keys') {
479
488
  }
480
489
  return result;
481
490
  }
491
+ function mcpHostCapability() {
492
+ const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || '').trim().toLowerCase();
493
+ const host = ['grok', 'claude-code', 'codex', 'cursor', 'windsurf', 'gemini', 'kimi', 'qwen', 'deepseek']
494
+ .includes(raw) ? raw : 'mcp-client';
495
+ return {
496
+ transport: 'mcp_stdio',
497
+ host,
498
+ tools_available: true,
499
+ tool_invocation: 'on_demand',
500
+ passive_hooks: {
501
+ provided_by_mcp_transport: false,
502
+ external_host_hook_state: 'unverified',
503
+ observed_by_this_process: false,
504
+ },
505
+ always_on_state: 'host_dependent',
506
+ 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.',
507
+ };
508
+ }
482
509
  function clientOperationalPayload(toolName, value) {
483
510
  const payload = value && typeof value === 'object' && !Array.isArray(value)
484
511
  ? value
@@ -486,6 +513,7 @@ if (process.argv[2] !== 'keys') {
486
513
  const spool = (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey: API_KEY, agentId: FLEET_AGENT_ID });
487
514
  return {
488
515
  ...payload,
516
+ host_capability: payload.host_capability || mcpHostCapability(),
489
517
  client_update: payload.client_update || (0, request_reliability_1.localClientUpdate)(),
490
518
  control_path: (0, control_path_state_1.controlPathStats)(toolName),
491
519
  lifecycle_spool: {
@@ -512,21 +540,13 @@ if (process.argv[2] !== 'keys') {
512
540
  function isHighRiskAction(action, type) {
513
541
  return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
514
542
  }
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
543
  async function withControlDeadline(operation, options = {}) {
524
544
  const toolName = options.toolName || 'marrow_control';
525
- const hasCache = options.cacheAware !== false && Boolean(cachedGuidance());
526
545
  const configuredTimeoutMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
546
+ const runtimeBudget = toolName === 'marrow_agent_runtime' || toolName === 'marrow_auto.runtime';
527
547
  const timeoutMs = Number.isFinite(configuredTimeoutMs)
528
548
  ? Math.min(10_000, Math.max(150, Math.floor(configuredTimeoutMs)))
529
- : options.highRisk ? 4_500 : hasCache ? 500 : 4_000;
549
+ : options.highRisk || runtimeBudget ? 4_500 : 4_000;
530
550
  const controller = new AbortController();
531
551
  const timer = setTimeout(() => controller.abort(), timeoutMs);
532
552
  timer.unref?.();
@@ -1672,7 +1692,7 @@ if (process.argv[2] !== 'keys') {
1672
1692
  capabilities: { tools: {}, prompts: {} },
1673
1693
  serverInfo: { name: 'marrow', version: hook_contract_1.MCP_ADAPTER_VERSION },
1674
1694
  ...(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.',
1695
+ 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
1696
  } : {}),
1677
1697
  });
1678
1698
  // Auto-enroll: emit enrollment notification on connection
@@ -1841,12 +1861,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1841
1861
  // [FIX #9] Validate required params
1842
1862
  const decision_id = requireString(args, 'decision_id');
1843
1863
  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, {
1864
+ const commitSuccess = requireBoolean(args, 'success');
1865
+ const result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1848
1866
  decision_id,
1849
- success: args.success,
1867
+ success: commitSuccess,
1850
1868
  outcome,
1851
1869
  caused_by: args.caused_by,
1852
1870
  proof: args.proof,
@@ -1858,10 +1876,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1858
1876
  surfaces: args.surfaces,
1859
1877
  auto_gate: args.auto_gate,
1860
1878
  model_usage: args.model_usage,
1861
- }, SESSION_ID, FLEET_AGENT_ID);
1879
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
1862
1880
  const commitResult = { ...result, narrative: result.narrative ?? null };
1863
- lastCommitted = true;
1864
- lastDecisionId = null;
1881
+ lastCommitted = result.committed;
1882
+ lastDecisionId = result.committed ? null : decision_id;
1865
1883
  success(id, {
1866
1884
  content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
1867
1885
  });
@@ -1953,18 +1971,43 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1953
1971
  }
1954
1972
  const proofCanClose = !highRisk || (0, runtime_contract_1.highRiskRuntimeCanClose)(runtimeGate, suppliedProof, args.gate_receipt_id);
1955
1973
  const acceptedSuccess = proofCanClose ? outcomeSuccess : undefined;
1974
+ const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
1975
+ action,
1976
+ outcome,
1977
+ success: acceptedSuccess,
1978
+ type,
1979
+ proof: suppliedProof,
1980
+ gate_receipt_id: typeof args.gate_receipt_id === 'string'
1981
+ ? args.gate_receipt_id
1982
+ : runtimeGate?.gate_receipt?.id,
1983
+ }, SESSION_ID, FLEET_AGENT_ID, 8_000);
1984
+ let delivered = null;
1985
+ let deliveryFailure = null;
1986
+ try {
1987
+ delivered = await delivery();
1988
+ }
1989
+ catch (err) {
1990
+ deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
1991
+ }
1956
1992
  const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
1957
1993
  apiKey: API_KEY,
1958
1994
  baseUrl: BASE_URL,
1959
- deferDelivery: true,
1995
+ deferDelivery: false,
1960
1996
  event: {
1961
- event_type: !highRisk && acceptedSuccess === false ? 'tool_failed' : !highRisk && acceptedSuccess === true ? 'tool_completed' : 'goal_started',
1997
+ event_type: delivered?.committed
1998
+ ? 'outcome_committed'
1999
+ : !highRisk && acceptedSuccess === false
2000
+ ? 'tool_failed'
2001
+ : !highRisk && acceptedSuccess === true
2002
+ ? 'tool_completed'
2003
+ : 'goal_started',
1962
2004
  harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
1963
2005
  agent_id: FLEET_AGENT_ID,
1964
2006
  session_id: SESSION_ID,
2007
+ decision_id: delivered?.decision_id,
1965
2008
  action,
1966
- outcome_state: 'pending',
1967
- success: highRisk ? undefined : acceptedSuccess,
2009
+ outcome_state: delivered?.committed ? 'closed' : 'pending',
2010
+ success: delivered?.committed ? acceptedSuccess : highRisk ? undefined : acceptedSuccess,
1968
2011
  adapter_version: hook_contract_1.MCP_ADAPTER_VERSION,
1969
2012
  capability_level: 'mcp',
1970
2013
  },
@@ -1973,48 +2016,29 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1973
2016
  action,
1974
2017
  outcome: outcome || 'pending',
1975
2018
  warnings: cachedOrientWarnings.map(formatWarningActionably),
1976
- logging: 'durably_queued',
2019
+ logging: delivered?.committed
2020
+ ? 'governed_commit_confirmed'
2021
+ : delivered
2022
+ ? 'intent_confirmed'
2023
+ : 'durably_queued',
1977
2024
  receipt,
1978
- completion_state: outcomeSuccess === undefined ? 'pending_evidence' : 'delivery_pending',
2025
+ completion_state: delivered?.committed
2026
+ ? 'closed_with_proof'
2027
+ : !proofCanClose
2028
+ ? 'pending_required_proof'
2029
+ : outcomeSuccess === undefined
2030
+ ? 'pending_evidence'
2031
+ : 'delivery_pending',
2032
+ decision_id: delivered?.decision_id || null,
2033
+ live_delivery: {
2034
+ accepted: Boolean(delivered),
2035
+ committed: Boolean(delivered?.committed),
2036
+ ...(deliveryFailure ? { failure: deliveryFailure } : {}),
2037
+ },
2038
+ host_capability: mcpHostCapability(),
1979
2039
  client_update: (0, request_reliability_1.localClientUpdate)(),
1980
- ...(runtimeGate ? {
1981
- runtime_gate: runtimeGate,
1982
- completion_state: proofCanClose ? 'governed_delivery_pending' : 'pending_required_proof',
1983
- } : {}),
2040
+ ...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
1984
2041
  };
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
2042
  success(id, {
2019
2043
  content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
2020
2044
  });
@@ -2431,12 +2455,35 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2431
2455
  return;
2432
2456
  }
2433
2457
  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));
2458
+ try {
2459
+ const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2460
+ status: args.status,
2461
+ agentId: args.agentId || FLEET_AGENT_ID,
2462
+ limit: args.limit,
2463
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
2464
+ toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
2465
+ }
2466
+ catch (error) {
2467
+ if (error instanceof request_reliability_1.MarrowRequestError && error.backendCode === 'MARROW_PLAN_UPGRADE_REQUIRED') {
2468
+ toolSuccess(id, clientOperationalPayload('marrow_handoff_status', {
2469
+ ok: true,
2470
+ available: false,
2471
+ state: 'not_entitled',
2472
+ failure_kind: 'entitlement',
2473
+ authorization_state: 'plan_limited',
2474
+ credential_valid: true,
2475
+ feature: 'fleet_handoff_status',
2476
+ current_plan: error.currentPlan,
2477
+ required_plan: 'team',
2478
+ required_feature: error.requiredFeature || 'fleet_learning',
2479
+ service_reachable: true,
2480
+ exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
2481
+ }));
2482
+ }
2483
+ else {
2484
+ throw error;
2485
+ }
2486
+ }
2440
2487
  return;
2441
2488
  }
2442
2489
  if (toolName === 'marrow_session_end') {