@getmarrow/mcp 3.9.76 → 3.9.78

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
@@ -87,6 +87,18 @@ For most new installations, start with the universal installer instead:
87
87
  npx @getmarrow/install activate
88
88
  ```
89
89
 
90
+ ## Tool Profiles
91
+
92
+ Ordinary setup does not require `MARROW_TOOL_PROFILE`. When the variable is unset, Marrow uses the `primary` profile and exposes exactly the 17 tools in [Primary MCP Tools](#primary-mcp-tools).
93
+
94
+ - `MARROW_TOOL_PROFILE=primary` explicitly selects the same 17-tool primary surface.
95
+ - `MARROW_TOOL_PROFILE=core` preserves the seven-tool runtime, think, commit, ask, status, auto, and handoff-status surface.
96
+ - `MARROW_TOOL_PROFILE=full` exposes the complete advanced and legacy catalog for integrations that require it.
97
+
98
+ An invalid value returns a bounded configuration error with the exact allowed values; it never falls back to `full`. Restart the MCP process after changing the profile.
99
+
100
+ Local visibility does not grant paid access. Every tool call continues through Marrow's backend authentication, tenant, key-permission, plan, proof, and policy enforcement. MCP status responses include `mcp_tool_profile` with the configured and effective profile, visible tool names/count, and a backend primary-tool entitlement projection when fresh authenticated evidence is provided. Missing or cached entitlement evidence is labeled unavailable and cannot authorize a call.
101
+
90
102
  ## Keeping MCP Current
91
103
 
92
104
  Marrow's hosted API, website, and dashboard update automatically; local MCP hooks, configuration, and pinned package commands do not silently rewrite themselves. Keeping them current delivers new client-side features, compatibility improvements, and any published security fixes. During authenticated status/runtime activity, Marrow returns a `client_update` notice when the package is behind or unknown, and passive context shows the agent the exact update and verification commands.
@@ -104,7 +116,15 @@ npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
104
116
 
105
117
  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
118
 
107
- ## What's New in v3.9.76
119
+ ## What's New in v3.9.78
120
+
121
+ v3.9.78 separates durable post-action observation from action authorization. For outcome closure only, `marrow_commit` sends the existing `decision_id` to runtime and can use the backend's exact `outcome_observation_only` response to submit the already-completed result without forwarding its non-durable correlation ID as receipt evidence. That response never permits an action: it has `allow: false`, `durable: false`, and no authorization. The accepted result remains `committed: false`, `outcome_state: "observed_unverified"`, `authorization_granted: false`, and `trusted_learning_applied: false`; it is terminal delivery and is not retried from the local queue. Trusted promotion requires an explicit new commit attempt with the backend-required authorization and proof for the exact observed payload. Missing, malformed, conflicting, or cross-scope runtime truth still fails closed, and privacy-unsafe `instruction_ref` values such as dates and long numeric IDs now fail locally before any network call.
122
+
123
+ ## Previous: v3.9.77
124
+
125
+ v3.9.77 makes `primary` the ordinary MCP profile when `MARROW_TOOL_PROFILE` is unset. The default surface now matches the 17 documented Primary MCP Tools, while explicit `core` preserves the seven-tool control loop and explicit `full` preserves the complete catalog. Invalid values fail with an exact bounded repair instead of broadening visibility. Status responses report the effective profile, visible names/count, and fresh backend-projected entitlement states when provided; local visibility and cached evidence never authorize access. The exact-version 11-tool control-path canary remains pinned to `full`.
126
+
127
+ ## Previous: v3.9.76
108
128
 
109
129
  v3.9.76 fixes owner-approved `marrow_auto` closeout by binding an arbitrated operation to the exact server-created arbitration decision, rejecting decision mismatches before commit, and returning an honest terminal action for non-arbitrated `review_required` gates. Chat and proof text cannot substitute for a dashboard-issued approval receipt, and only a backend `committed: true` response closes the operation.
110
130
 
@@ -197,7 +217,7 @@ v3.9.62 integrates four model-neutral reliability and capability contracts:
197
217
  - `spool-status` and `drain-spool` report the active credential namespace separately from isolated legacy debt, and a clear active namespace exits successfully without replaying, merging, editing, or deleting old-key files;
198
218
  - initialize, prompt, setup, and tool responses qualify coverage by `host_capability`: MCP tools are on demand, while client-self-reported hook activity remains visible but never certifies coverage or control.
199
219
 
200
- The default surface is seven tools (runtime, think, commit, ask, status, auto, handoff status) and the prompt remains named `marrow-always-on`. Host and model labels are display-only and never change auth, tenant, plan, policy, proof, schema, or API behavior. Grok hook activity is client-self-reported and does not certify observed coverage; the governed wrapper remains an explicit bounded fallback.
220
+ In v3.9.62, the default surface was seven tools (runtime, think, commit, ask, status, auto, handoff status) and the prompt remained named `marrow-always-on`. Host and model labels are display-only and never change auth, tenant, plan, policy, proof, schema, or API behavior. Grok hook activity is client-self-reported and does not certify observed coverage; the governed wrapper remains an explicit bounded fallback.
201
221
 
202
222
  ## Previous: v3.9.61
203
223
 
@@ -251,7 +271,7 @@ The compact agent control path introduced in v3.9.57 remains the default:
251
271
  - `marrow_status`, `marrow_ask`, and `marrow_agent_runtime` use authenticated routes with bounded retries and typed failures;
252
272
  - transient read failures return an owner-only last-known brief when available, while cached guidance can never authorize high-risk work;
253
273
  - normal tool errors return structured `ok`, `error_code`, `exact_fix`, `stale_brief`, and `client_update` data instead of raw MCP `fetch failed` errors;
254
- - the default agent surface is seven tools: runtime, think, commit, ask, status, auto, and handoff status; set `MARROW_TOOL_PROFILE=full` only for legacy or advanced integrations;
274
+ - the v3.9.58 default agent surface was seven tools: runtime, think, commit, ask, status, auto, and handoff status; `MARROW_TOOL_PROFILE=full` selected legacy or advanced integrations;
255
275
  - risky `marrow_auto` calls obtain a fresh runtime gate automatically and cannot self-close as successful without required proof;
256
276
  - `marrow_run` requires an explicit outcome and never invents proof or a successful result;
257
277
  - the package includes an exact-version control-path canary covering every route reported in the production incident.
@@ -357,6 +377,8 @@ Configured hooks can provide cooperative telemetry and context, but they are not
357
377
 
358
378
  `marrow_agent_runtime` returns `runtime_authorization` with the authoritative gate receipt. Ordinary runtime checks do not create a decision, so they omit `decision_id`; call `marrow_think` (or use `marrow_auto`) when a decision must be created and closed. An arbitration runtime that actually creates a decision returns that server-created `decision_id`. This contract is identical across MCP-compatible hosts and SDK-owned runtimes.
359
379
 
380
+ A `review_required`, `block`, or `outcome_observation_only` result never permits the action. If the action already occurred and its real result must be preserved, `marrow_commit` can ask runtime to bind observation delivery to the existing decision, action, session, and agent. The exact backend `outcome_observation_only` correlation is non-durable and non-authorizing, so MCP never sends it as a gate, arbitration, or owner-approval receipt. An accepted observation reports `committed: false`, `outcome_state: "observed_unverified"`, `authorization_granted: false`, and `trusted_learning_applied: false`, plus the backend's `exact_next_action`. This is durable delivery, so do not retry or spool the same observation. To promote it into trusted learning, obtain the named authorization and proof, then make an explicit new commit attempt using the exact observed payload. Never synthesize a decision, receipt, approval, or authorization.
381
+
360
382
  `marrow_auto` returns an `operation_id`, phase, and resumable state. If a client deadline is reached before closure, retry with the same `operation_id`; Marrow reuses the same tenant-scoped runtime authorization, decision, and commit idempotency keys instead of opening another decision. When the phase is `proof_required`, add the requested proof and retry with that same operation ID and unchanged action/context/surfaces. An arbitrated runtime binds the operation directly to the arbitration response's server-created `decision_id`; `marrow_auto` does not open a separate decision. When an arbitrated result returns `owner_approval_required`, do not retry proof: approve that exact arbitration decision in an authenticated Marrow dashboard, then call `marrow_auto` once with the same operation ID, `arbitration_receipt_id`, and server-issued `owner_approval_receipt_id`. A non-arbitrated `review_required` result is terminal for that operation because the backend cannot issue a compatible dashboard receipt; start a new supported `marrow_arbitrate` review instead. Chat or proof text is never an approval receipt. Proof and receipts are validated and bound at commit; changing the tenant or action scope is rejected. A closed response reports `phase: "closed"`, `committed: true`, and `resumable: false`.
361
383
 
362
384
  Example pre-action request:
package/dist/cli.js CHANGED
@@ -544,6 +544,7 @@ if (process.argv[2] !== 'keys') {
544
544
  drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
545
545
  legacy_namespaces: spool.other_namespaces?.count || 0,
546
546
  },
547
+ mcp_tool_profile: mcpToolProfileStatus(payload),
547
548
  };
548
549
  }
549
550
  // [FIX #9] Runtime validation helper for required string params
@@ -840,7 +841,7 @@ if (process.argv[2] !== 'keys') {
840
841
  description: 'Optional provenance source. Defaults to agent_autonomous for MCP calls.',
841
842
  },
842
843
  human_directed: { type: 'boolean', description: 'True only when the action is directly requested by the owner/user.' },
843
- instruction_ref: { type: 'string', description: 'Optional opaque non-PII instruction reference.' },
844
+ instruction_ref: { type: 'string', description: 'Optional privacy-safe opaque reference. Dates, long digit runs, provider IDs, addresses, and domains are rejected locally; valid values are preserved exactly.' },
844
845
  source_meta: { type: 'object', description: 'Optional provenance metadata. PII and raw provider IDs are rejected by the API.' },
845
846
  },
846
847
  required: ['action'],
@@ -851,7 +852,8 @@ if (process.argv[2] !== 'keys') {
851
852
  description: 'Close a recorded action with success/failure, a specific outcome, and required proof. ' +
852
853
  'decision_id comes from marrow_think, marrow_auto, or an arbitration runtime that actually created a decision. ' +
853
854
  'Use the gate receipt from marrow_agent_runtime for consequential work. ' +
854
- 'Outcome closure is required for accountable fleet learning.',
855
+ 'The exact non-authorizing outcome_observation_only runtime correlation may submit an observed_unverified result, but is never sent as receipt evidence and never authorizes action or trusted learning. ' +
856
+ 'Only committed:true closes trusted outcome learning.',
855
857
  inputSchema: {
856
858
  type: 'object',
857
859
  properties: {
@@ -869,7 +871,7 @@ if (process.argv[2] !== 'keys') {
869
871
  action: { type: 'string', description: 'Optional original action. If provided and gate_receipt_id is omitted, MCP can fetch a matching runtime gate receipt before commit.' },
870
872
  type: { type: 'string', description: 'Optional original action type for auto gate lookup, e.g. deploy, publish, merge, handoff, implementation.' },
871
873
  surfaces: { type: 'array', items: { type: 'string' }, description: 'Optional surfaces for auto gate receipt, e.g. github, cloudflare, npm, production.' },
872
- auto_gate: { type: 'boolean', description: 'If true/default and action is provided, call marrow_agent_runtime to obtain gate_receipt_id before commit.' },
874
+ auto_gate: { type: 'boolean', description: 'If true/default and action is provided, fetch runtime truth bound to this existing decision. Authorizing receipts preserve normal closure; exact outcome_observation_only correlation can submit only an observed_unverified result and is omitted from receipt evidence.' },
873
875
  model_usage: { type: 'object', description: 'Optional compact token/cost/latency counts. Do not include raw prompts or completions.' },
874
876
  },
875
877
  required: ['decision_id', 'success', 'outcome'],
@@ -1718,15 +1720,168 @@ if (process.argv[2] !== 'keys') {
1718
1720
  'marrow_auto',
1719
1721
  'marrow_handoff_status',
1720
1722
  ]);
1723
+ const PRIMARY_TOOL_NAMES = new Set([
1724
+ 'marrow_agent_runtime',
1725
+ 'marrow_arbitrate',
1726
+ 'marrow_coordinate',
1727
+ 'marrow_replay_compare',
1728
+ 'marrow_decision_brief',
1729
+ 'marrow_think',
1730
+ 'marrow_commit',
1731
+ 'marrow_workflow_gate',
1732
+ 'marrow_completion_contracts',
1733
+ 'marrow_evaluate_completion_contract',
1734
+ 'marrow_agent_status',
1735
+ 'marrow_value_report',
1736
+ 'marrow_buyer_proof',
1737
+ 'marrow_governance_timeline',
1738
+ 'marrow_decision_trace',
1739
+ 'marrow_fleet_lessons',
1740
+ 'marrow_model_usage',
1741
+ ]);
1742
+ function activeToolProfile() {
1743
+ const configured = process.env.MARROW_TOOL_PROFILE;
1744
+ if (configured === undefined || configured === 'primary')
1745
+ return 'primary';
1746
+ if (configured === 'core' || configured === 'full')
1747
+ return configured;
1748
+ throw new Error('Invalid MARROW_TOOL_PROFILE value. Set MARROW_TOOL_PROFILE=primary, core, or full, '
1749
+ + 'or unset it for primary; then restart MCP.');
1750
+ }
1751
+ function toolsForProfile(profile) {
1752
+ if (profile === 'full')
1753
+ return TOOLS;
1754
+ const visibleNames = profile === 'primary' ? PRIMARY_TOOL_NAMES : CORE_TOOL_NAMES;
1755
+ return TOOLS.filter((tool) => visibleNames.has(tool.name));
1756
+ }
1721
1757
  function advertisedTools() {
1722
- return process.env.MARROW_TOOL_PROFILE === 'full'
1723
- ? TOOLS
1724
- : TOOLS.filter((tool) => CORE_TOOL_NAMES.has(tool.name));
1758
+ return toolsForProfile(activeToolProfile());
1725
1759
  }
1726
1760
  function toolAllowedByActiveProfile(toolName) {
1727
- return process.env.MARROW_TOOL_PROFILE === 'full'
1728
- ? Boolean(toolName && TOOLS.some((tool) => tool.name === toolName))
1729
- : Boolean(toolName && CORE_TOOL_NAMES.has(toolName));
1761
+ return Boolean(toolName && toolsForProfile(activeToolProfile()).some((tool) => tool.name === toolName));
1762
+ }
1763
+ function recordValue(value) {
1764
+ return value && typeof value === 'object' && !Array.isArray(value)
1765
+ ? value
1766
+ : null;
1767
+ }
1768
+ function nonNegativeInteger(value) {
1769
+ return Number.isInteger(value) && Number(value) >= 0;
1770
+ }
1771
+ function validatedPrimaryToolAvailability(value) {
1772
+ const projection = recordValue(value);
1773
+ const evidence = recordValue(projection?.entitlement_evidence);
1774
+ const counts = recordValue(projection?.counts);
1775
+ const tools = projection?.tools;
1776
+ const expectedNames = [...PRIMARY_TOOL_NAMES];
1777
+ if (!projection
1778
+ || projection.profile !== 'primary'
1779
+ || (projection.current_plan !== null && typeof projection.current_plan !== 'string')
1780
+ || typeof projection.owner_management_url !== 'string'
1781
+ || !evidence
1782
+ || (evidence.state !== 'available' && evidence.state !== 'unavailable')
1783
+ || typeof evidence.source !== 'string'
1784
+ || typeof evidence.authoritative !== 'boolean'
1785
+ || evidence.authorizing !== false
1786
+ || !counts
1787
+ || !nonNegativeInteger(counts.total)
1788
+ || !nonNegativeInteger(counts.entitled)
1789
+ || !nonNegativeInteger(counts.upgrade_required)
1790
+ || !nonNegativeInteger(counts.unavailable)
1791
+ || counts.total !== expectedNames.length
1792
+ || counts.entitled + counts.upgrade_required + counts.unavailable !== counts.total
1793
+ || !Array.isArray(tools)
1794
+ || tools.length !== expectedNames.length) {
1795
+ return null;
1796
+ }
1797
+ if ((evidence.state === 'available' && evidence.authoritative !== true)
1798
+ || (evidence.state === 'unavailable' && evidence.authoritative !== false)) {
1799
+ return null;
1800
+ }
1801
+ const derivedCounts = { entitled: 0, upgrade_required: 0, unavailable: 0 };
1802
+ for (let index = 0; index < tools.length; index++) {
1803
+ const tool = recordValue(tools[index]);
1804
+ const state = tool?.state;
1805
+ if (!tool
1806
+ || tool.name !== expectedNames[index]
1807
+ || (state !== 'entitled' && state !== 'upgrade_required' && state !== 'unavailable')
1808
+ || typeof tool.always_available !== 'boolean'
1809
+ || (tool.plan_feature !== null && typeof tool.plan_feature !== 'string')
1810
+ || (tool.minimum_plan !== null && typeof tool.minimum_plan !== 'string')
1811
+ || typeof tool.owner_management_url !== 'string') {
1812
+ return null;
1813
+ }
1814
+ derivedCounts[state]++;
1815
+ }
1816
+ if (derivedCounts.entitled !== counts.entitled
1817
+ || derivedCounts.upgrade_required !== counts.upgrade_required
1818
+ || derivedCounts.unavailable !== counts.unavailable) {
1819
+ return null;
1820
+ }
1821
+ return {
1822
+ projection,
1823
+ evidenceState: evidence.state,
1824
+ };
1825
+ }
1826
+ function backendPrimaryToolAvailability(value) {
1827
+ const payload = recordValue(value);
1828
+ if (!payload)
1829
+ return null;
1830
+ const data = recordValue(payload.data);
1831
+ const status = recordValue(payload.status);
1832
+ const entitlements = recordValue(payload.entitlements);
1833
+ const entitlementProjection = recordValue(payload.entitlement_projection);
1834
+ const statusEntitlements = recordValue(status?.entitlements);
1835
+ const statusProjection = recordValue(status?.entitlement_projection);
1836
+ const candidates = [
1837
+ payload.primary_tool_availability,
1838
+ data?.primary_tool_availability,
1839
+ status?.primary_tool_availability,
1840
+ entitlements?.primary_tool_availability,
1841
+ entitlementProjection?.primary_tool_availability,
1842
+ statusEntitlements?.primary_tool_availability,
1843
+ statusProjection?.primary_tool_availability,
1844
+ ];
1845
+ for (const candidate of candidates) {
1846
+ const validated = validatedPrimaryToolAvailability(candidate);
1847
+ if (validated)
1848
+ return validated;
1849
+ }
1850
+ return null;
1851
+ }
1852
+ function mcpToolProfileStatus(value) {
1853
+ const profile = activeToolProfile();
1854
+ const visibleToolNames = toolsForProfile(profile).map((tool) => tool.name);
1855
+ const payload = recordValue(value);
1856
+ const cachedOrStale = payload?.cached === true
1857
+ || payload?.stale === true
1858
+ || payload?.status_freshness === 'stale';
1859
+ const backendProjection = cachedOrStale ? null : backendPrimaryToolAvailability(payload);
1860
+ const backendProjectionAvailable = backendProjection?.evidenceState === 'available';
1861
+ return {
1862
+ configured_profile: process.env.MARROW_TOOL_PROFILE ?? 'unset',
1863
+ effective_profile: profile,
1864
+ visible_tool_count: visibleToolNames.length,
1865
+ visible_tool_names: visibleToolNames,
1866
+ local_visibility_grants_entitlement: false,
1867
+ backend_entitlement_projection: {
1868
+ evidence_state: backendProjectionAvailable ? 'available' : 'unavailable',
1869
+ source: cachedOrStale
1870
+ ? 'cached_or_stale_status'
1871
+ : backendProjection
1872
+ ? 'authenticated_backend'
1873
+ : 'backend_projection_not_provided',
1874
+ authorizes_calls: false,
1875
+ primary_tool_availability: backendProjectionAvailable ? backendProjection.projection : null,
1876
+ },
1877
+ };
1878
+ }
1879
+ function withMcpToolProfileStatus(value) {
1880
+ const payload = recordValue(value) || { data: value };
1881
+ return {
1882
+ ...payload,
1883
+ mcp_tool_profile: mcpToolProfileStatus(payload),
1884
+ };
1730
1885
  }
1731
1886
  // Request handler
1732
1887
  async function handleRequest(req) {
@@ -1847,7 +2002,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1847
2002
  const toolName = params?.name;
1848
2003
  const args = (params?.arguments || {});
1849
2004
  if (!toolAllowedByActiveProfile(toolName)) {
1850
- error(id, -32601, 'Tool is not available in the active Marrow tool profile. Set MARROW_TOOL_PROFILE=full and restart MCP for advanced operator tools.');
2005
+ const repair = toolName && PRIMARY_TOOL_NAMES.has(toolName)
2006
+ ? 'Unset MARROW_TOOL_PROFILE or set MARROW_TOOL_PROFILE=primary, then restart MCP.'
2007
+ : 'Set MARROW_TOOL_PROFILE=full, then restart MCP for the complete advanced/legacy catalog.';
2008
+ error(id, -32601, `Tool is not available in the active Marrow tool profile. ${repair}`);
1851
2009
  return;
1852
2010
  }
1853
2011
  if (toolName === 'marrow_orient') {
@@ -2304,13 +2462,24 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2304
2462
  return;
2305
2463
  }
2306
2464
  if (toolName === 'marrow_agent_status') {
2307
- const result = await (0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2308
- success(id, { content: [{ type: 'text', text: JSON.stringify({ ...result, local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)) }, null, 2) }] });
2465
+ const result = await withControlDeadline(async (signal) => {
2466
+ const [analyticsStatus, context] = await Promise.all([
2467
+ (0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID, signal),
2468
+ (0, index_1.marrowAgentContext)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal)
2469
+ .catch(() => null),
2470
+ ]);
2471
+ const contextProjection = recordValue(context)?.primary_tool_availability;
2472
+ return {
2473
+ ...analyticsStatus,
2474
+ ...(contextProjection === undefined ? {} : { primary_tool_availability: contextProjection }),
2475
+ };
2476
+ }, { cacheAware: false, toolName: 'marrow_agent_status' });
2477
+ success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus({ ...result, local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)) }), null, 2) }] });
2309
2478
  return;
2310
2479
  }
2311
2480
  if (toolName === 'marrow_runtime_status') {
2312
2481
  const result = await withControlDeadline((signal) => (0, index_1.marrowRuntimeStatus)(API_KEY, BASE_URL, args.fast !== false, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_runtime_status' });
2313
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2482
+ success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus(result), null, 2) }] });
2314
2483
  return;
2315
2484
  }
2316
2485
  if (toolName === 'marrow_value_report') {