@getmarrow/mcp 3.1.4 → 3.2.0

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/dist/cli.js CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  const index_1 = require("./index");
14
+ const hook_1 = require("./hook");
14
15
  // Parse CLI args
15
16
  function parseArgs() {
16
17
  const args = process.argv.slice(2);
@@ -23,6 +24,9 @@ function parseArgs() {
23
24
  if (args[i] === 'setup' || args[i] === '--setup') {
24
25
  result.setup = true;
25
26
  }
27
+ if (args[i] === 'hook' || args[i] === '--hook') {
28
+ result.hook = true;
29
+ }
26
30
  }
27
31
  return result;
28
32
  }
@@ -46,7 +50,6 @@ Do NOT skip logging. Every decision you log compounds — failure rates drop, pa
46
50
 
47
51
  For one-shot logging: \`marrow_auto({ action: "did X", outcome: "result Y", success: true })\` — one call, done.
48
52
  ${MARROW_BLOCK_END}`;
49
- // Find CLAUDE.md — check current dir, then walk up
50
53
  let dir = process.cwd();
51
54
  let claudeMdPath = null;
52
55
  for (let i = 0; i < 5; i++) {
@@ -67,731 +70,743 @@ ${MARROW_BLOCK_END}`;
67
70
  let content = '';
68
71
  if (fs.existsSync(claudeMdPath)) {
69
72
  content = fs.readFileSync(claudeMdPath, 'utf8');
70
- // Check if already present
71
73
  if (content.includes(MARROW_BLOCK_START)) {
72
- // Replace existing block
73
74
  const startIdx = content.indexOf(MARROW_BLOCK_START);
74
75
  const endIdx = content.indexOf(MARROW_BLOCK_END);
75
76
  if (endIdx > startIdx) {
76
77
  content = content.slice(0, startIdx) + marrowInstructions + content.slice(endIdx + MARROW_BLOCK_END.length);
77
78
  fs.writeFileSync(claudeMdPath, content);
78
79
  process.stdout.write(`Updated Marrow instructions in ${claudeMdPath}\n`);
79
- process.exit(0);
80
- return;
81
80
  }
82
81
  }
82
+ else {
83
+ const separator = content.length > 0 && !content.endsWith('\n') ? '\n\n' : content.length > 0 ? '\n' : '';
84
+ fs.writeFileSync(claudeMdPath, content + separator + marrowInstructions + '\n');
85
+ process.stdout.write(`Added Marrow instructions to ${claudeMdPath}\n`);
86
+ }
87
+ }
88
+ else {
89
+ fs.writeFileSync(claudeMdPath, marrowInstructions + '\n');
90
+ process.stdout.write(`Added Marrow instructions to ${claudeMdPath}\n`);
91
+ }
92
+ const hookInstall = (0, hook_1.installPostToolUseHook)(process.cwd());
93
+ if (hookInstall.installed) {
94
+ process.stdout.write('Installed PostToolUse hook — your agent\'s tool calls now auto-log to Marrow. Set MARROW_AUTO_HOOK=false to disable.\n');
83
95
  }
84
- // Append
85
- const separator = content.length > 0 && !content.endsWith('\n') ? '\n\n' : content.length > 0 ? '\n' : '';
86
- fs.writeFileSync(claudeMdPath, content + separator + marrowInstructions + '\n');
87
- process.stdout.write(`Added Marrow instructions to ${claudeMdPath}\n`);
88
- process.stdout.write(`Your agent will now use Marrow automatically in every session.\n`);
96
+ else {
97
+ process.stdout.write('PostToolUse hook already installed your agent\'s tool calls already auto-log to Marrow. Set MARROW_AUTO_HOOK=false to disable.\n');
98
+ }
99
+ process.stdout.write(`Hook settings: ${hookInstall.settingsPath}\n`);
100
+ process.stdout.write('Your agent will now use Marrow automatically in every session.\n');
89
101
  process.exit(0);
90
102
  }
91
103
  const cliArgs = parseArgs();
92
- // Handle setup command before anything else
93
- if (cliArgs.setup) {
94
- runSetup();
104
+ if (cliArgs.hook) {
105
+ void (0, hook_1.runHookCommand)();
95
106
  }
96
- const API_KEY = cliArgs.apiKey || process.env.MARROW_API_KEY || '';
97
- // [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
98
- const rawBaseUrl = process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai';
99
- const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
100
- const SESSION_ID = process.env.MARROW_SESSION_ID || undefined;
101
- const FLEET_AGENT_ID = process.env.MARROW_FLEET_AGENT_ID || undefined; // V5: agent UUID for X-Marrow-Agent-Id header
102
- const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
103
- const AGENT_ID = process.env.MARROW_AGENT_ID || `${require('os').hostname()}-${Date.now().toString(36)}`;
104
- if (!API_KEY) {
105
- process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
106
- process.stderr.write('Usage: MARROW_API_KEY=mrw_yourkey npx @getmarrow/mcp\n');
107
- process.stderr.write(' or: npx @getmarrow/mcp --key mrw_yourkey\n');
108
- process.exit(1);
109
- }
110
- // [SECURITY #12] Warn if API key is visible in process args
111
- if (cliArgs.apiKey) {
112
- process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
107
+ else if (cliArgs.setup) {
108
+ runSetup();
113
109
  }
114
- // Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
115
- let cachedOrientWarnings = [];
116
- let thinkCallCount = 0;
117
- let orientCallCount = 0;
118
- let initialized = false;
119
- const pendingDecisions = new Map();
120
- const PENDING_TTL_MS = 30 * 60 * 1000; // 30 min TTL
121
- function actionHash(action) {
122
- const normalized = action.toLowerCase().trim().replace(/\s+/g, ' ');
123
- let h = 5381;
124
- for (let i = 0; i < normalized.length; i++) {
125
- h = ((h << 5) + h) ^ normalized.charCodeAt(i);
126
- h = h >>> 0;
110
+ else {
111
+ const API_KEY = cliArgs.apiKey || process.env.MARROW_API_KEY || '';
112
+ // [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
113
+ const rawBaseUrl = process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai';
114
+ const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
115
+ const SESSION_ID = process.env.MARROW_SESSION_ID || undefined;
116
+ const FLEET_AGENT_ID = process.env.MARROW_FLEET_AGENT_ID || undefined; // V5: agent UUID for X-Marrow-Agent-Id header
117
+ const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
118
+ const AGENT_ID = process.env.MARROW_AGENT_ID || `${require('os').hostname()}-${Date.now().toString(36)}`;
119
+ if (!API_KEY) {
120
+ process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
121
+ process.stderr.write('Usage: MARROW_API_KEY=mrw_yourkey npx @getmarrow/mcp\n');
122
+ process.stderr.write(' or: npx @getmarrow/mcp --key mrw_yourkey\n');
123
+ process.exit(1);
127
124
  }
128
- return h.toString(36) + '_' + normalized.slice(0, 32);
129
- }
130
- // [FIX #11] Actually call cleanupPending to prevent unbounded map growth
131
- function cleanupPending() {
132
- const now = Date.now();
133
- for (const [key, val] of pendingDecisions) {
134
- if (now - val.timestamp > PENDING_TTL_MS) {
135
- pendingDecisions.delete(key);
136
- }
125
+ // [SECURITY #12] Warn if API key is visible in process args
126
+ if (cliArgs.apiKey) {
127
+ process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
137
128
  }
138
- }
139
- function formatWarningActionably(w) {
140
- const pct = Math.round(w.failureRate * 100);
141
- return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
142
- }
143
- // [FIX #4] Log orient refresh failures instead of silently ignoring
144
- async function refreshOrientWarnings() {
145
- try {
146
- const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
147
- cachedOrientWarnings = r.warnings;
129
+ // Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
130
+ let cachedOrientWarnings = [];
131
+ let thinkCallCount = 0;
132
+ let orientCallCount = 0;
133
+ let initialized = false;
134
+ const pendingDecisions = new Map();
135
+ const PENDING_TTL_MS = 30 * 60 * 1000; // 30 min TTL
136
+ function actionHash(action) {
137
+ const normalized = action.toLowerCase().trim().replace(/\s+/g, ' ');
138
+ let h = 5381;
139
+ for (let i = 0; i < normalized.length; i++) {
140
+ h = ((h << 5) + h) ^ normalized.charCodeAt(i);
141
+ h = h >>> 0;
142
+ }
143
+ return h.toString(36) + '_' + normalized.slice(0, 32);
148
144
  }
149
- catch (err) {
150
- const msg = err instanceof Error ? err.message : String(err);
151
- process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
145
+ // [FIX #11] Actually call cleanupPending to prevent unbounded map growth
146
+ function cleanupPending() {
147
+ const now = Date.now();
148
+ for (const [key, val] of pendingDecisions) {
149
+ if (now - val.timestamp > PENDING_TTL_MS) {
150
+ pendingDecisions.delete(key);
151
+ }
152
+ }
152
153
  }
153
- }
154
- // Initial orient
155
- refreshOrientWarnings().then(() => {
156
- if (cachedOrientWarnings.some((w) => w.failureRate > 0.4)) {
157
- process.stderr.write(`[marrow] ⚠️ High failure rate detected on startup — call marrow_orient for details before acting\n`);
154
+ function formatWarningActionably(w) {
155
+ const pct = Math.round(w.failureRate * 100);
156
+ return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
158
157
  }
159
- });
160
- // Auto-commit tracking for session close
161
- let lastDecisionId = null;
162
- let lastCommitted = false;
163
- // [FIX #5] Log auto-commit failures instead of silently ignoring; remove broken AbortController
164
- async function autoCommitOnClose() {
165
- if (lastDecisionId && !lastCommitted) {
158
+ // [FIX #4] Log orient refresh failures instead of silently ignoring
159
+ async function refreshOrientWarnings() {
166
160
  try {
167
- await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
168
- decision_id: lastDecisionId,
169
- success: false,
170
- outcome: 'Session ended without explicit commit',
171
- }, SESSION_ID);
161
+ const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
162
+ cachedOrientWarnings = r.warnings;
172
163
  }
173
164
  catch (err) {
174
165
  const msg = err instanceof Error ? err.message : String(err);
175
- process.stderr.write(`[marrow] Warning: auto-commit on close failed: ${msg}\n`);
166
+ process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
176
167
  }
177
168
  }
178
- }
179
- // [FIX #10] Handle both SIGTERM and SIGINT for clean shutdown
180
- async function gracefulShutdown() {
181
- const forceExit = setTimeout(() => process.exit(0), 5000);
182
- forceExit.unref();
183
- await autoCommitOnClose();
184
- process.exit(0);
185
- }
186
- process.on('SIGTERM', gracefulShutdown);
187
- process.on('SIGINT', gracefulShutdown);
188
- function send(response) {
189
- process.stdout.write(JSON.stringify(response) + '\n');
190
- }
191
- function success(id, result) {
192
- send({ jsonrpc: '2.0', id, result });
193
- }
194
- function error(id, code, message) {
195
- send({ jsonrpc: '2.0', id, error: { code, message } });
196
- }
197
- // [FIX #9] Runtime validation helper for required string params
198
- function requireString(args, name) {
199
- const val = args[name];
200
- if (typeof val !== 'string' || !val.trim()) {
201
- throw new Error(`"${name}" is required and must be a non-empty string`);
169
+ // Initial orient
170
+ refreshOrientWarnings().then(() => {
171
+ if (cachedOrientWarnings.some((w) => w.failureRate > 0.4)) {
172
+ process.stderr.write(`[marrow] ⚠️ High failure rate detected on startup — call marrow_orient for details before acting\n`);
173
+ }
174
+ });
175
+ // Auto-commit tracking for session close
176
+ let lastDecisionId = null;
177
+ let lastCommitted = false;
178
+ // [FIX #5] Log auto-commit failures instead of silently ignoring; remove broken AbortController
179
+ async function autoCommitOnClose() {
180
+ if (lastDecisionId && !lastCommitted) {
181
+ try {
182
+ await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
183
+ decision_id: lastDecisionId,
184
+ success: false,
185
+ outcome: 'Session ended without explicit commit',
186
+ }, SESSION_ID);
187
+ }
188
+ catch (err) {
189
+ const msg = err instanceof Error ? err.message : String(err);
190
+ process.stderr.write(`[marrow] Warning: auto-commit on close failed: ${msg}\n`);
191
+ }
192
+ }
202
193
  }
203
- return val;
204
- }
205
- // [FIX #6 & #7] Safe JSON response helper for memory API functions
206
- async function safeMemoryResponse(res) {
207
- if (!res.ok) {
208
- let detail = '';
209
- try {
210
- detail = await res.text();
194
+ // [FIX #10] Handle both SIGTERM and SIGINT for clean shutdown
195
+ async function gracefulShutdown() {
196
+ const forceExit = setTimeout(() => process.exit(0), 5000);
197
+ forceExit.unref();
198
+ await autoCommitOnClose();
199
+ process.exit(0);
200
+ }
201
+ process.on('SIGTERM', gracefulShutdown);
202
+ process.on('SIGINT', gracefulShutdown);
203
+ function send(response) {
204
+ process.stdout.write(JSON.stringify(response) + '\n');
205
+ }
206
+ function success(id, result) {
207
+ send({ jsonrpc: '2.0', id, result });
208
+ }
209
+ function error(id, code, message) {
210
+ send({ jsonrpc: '2.0', id, error: { code, message } });
211
+ }
212
+ // [FIX #9] Runtime validation helper for required string params
213
+ function requireString(args, name) {
214
+ const val = args[name];
215
+ if (typeof val !== 'string' || !val.trim()) {
216
+ throw new Error(`"${name}" is required and must be a non-empty string`);
211
217
  }
212
- catch { /* ignore */ }
213
- throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
218
+ return val;
214
219
  }
215
- const json = await res.json();
216
- if (json.error) {
217
- throw new Error(json.error);
220
+ // [FIX #6 & #7] Safe JSON response helper for memory API functions
221
+ async function safeMemoryResponse(res) {
222
+ if (!res.ok) {
223
+ let detail = '';
224
+ try {
225
+ detail = await res.text();
226
+ }
227
+ catch { /* ignore */ }
228
+ throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
229
+ }
230
+ const json = await res.json();
231
+ if (json.error) {
232
+ throw new Error(json.error);
233
+ }
234
+ return json;
218
235
  }
219
- return json;
220
- }
221
- // Memory API functions — all patched with safeMemoryResponse and validatePathParam
222
- async function marrowListMemories(apiKey, baseUrl, params, sessionId) {
223
- const qs = new URLSearchParams();
224
- if (params?.status)
225
- qs.set('status', params.status);
226
- if (params?.query)
227
- qs.set('query', params.query);
228
- if (params?.limit)
229
- qs.set('limit', String(params.limit));
230
- if (params?.agentId)
231
- qs.set('agent_id', params.agentId);
232
- const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
233
- headers: {
234
- Authorization: `Bearer ${apiKey}`,
235
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
236
- },
237
- });
238
- const json = await safeMemoryResponse(res);
239
- return json.data?.memories || [];
240
- }
241
- async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
242
- const safeId = (0, index_1.validatePathParam)(id, 'id');
243
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
244
- headers: {
245
- Authorization: `Bearer ${apiKey}`,
246
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
247
- },
248
- });
249
- const json = await safeMemoryResponse(res);
250
- return json.data?.memory || null;
251
- }
252
- async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
253
- const safeId = (0, index_1.validatePathParam)(id, 'id');
254
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
255
- method: 'PATCH',
256
- headers: {
257
- Authorization: `Bearer ${apiKey}`,
258
- 'Content-Type': 'application/json',
259
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
260
- },
261
- body: JSON.stringify(patch),
262
- });
263
- const json = await safeMemoryResponse(res);
264
- return json.data?.memory;
265
- }
266
- async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
267
- const safeId = (0, index_1.validatePathParam)(id, 'id');
268
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
269
- method: 'DELETE',
270
- headers: {
271
- Authorization: `Bearer ${apiKey}`,
272
- 'Content-Type': 'application/json',
273
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
274
- },
275
- body: JSON.stringify(meta || {}),
276
- });
277
- const json = await safeMemoryResponse(res);
278
- return json.data?.memory;
279
- }
280
- async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
281
- const safeId = (0, index_1.validatePathParam)(id, 'id');
282
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
283
- method: 'POST',
284
- headers: {
285
- Authorization: `Bearer ${apiKey}`,
286
- 'Content-Type': 'application/json',
287
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
288
- },
289
- body: JSON.stringify(meta || {}),
290
- });
291
- const json = await safeMemoryResponse(res);
292
- return json.data?.memory;
293
- }
294
- async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
295
- const safeId = (0, index_1.validatePathParam)(id, 'id');
296
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
297
- method: 'POST',
298
- headers: {
299
- Authorization: `Bearer ${apiKey}`,
300
- 'Content-Type': 'application/json',
301
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
302
- },
303
- body: JSON.stringify(replacement),
304
- });
305
- const json = await safeMemoryResponse(res);
306
- return json.data;
307
- }
308
- async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
309
- const safeId = (0, index_1.validatePathParam)(id, 'id');
310
- const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
311
- method: 'POST',
312
- headers: {
313
- Authorization: `Bearer ${apiKey}`,
314
- 'Content-Type': 'application/json',
315
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
316
- },
317
- body: JSON.stringify({ agent_ids: agentIds, actor }),
318
- });
319
- const json = await safeMemoryResponse(res);
320
- return json.data?.memory;
321
- }
322
- async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
323
- const qs = new URLSearchParams();
324
- if (params?.format)
325
- qs.set('format', params.format);
326
- if (params?.status)
327
- qs.set('status', params.status);
328
- if (params?.tags)
329
- qs.set('tags', params.tags);
330
- const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
331
- headers: {
332
- Authorization: `Bearer ${apiKey}`,
333
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
334
- },
335
- });
336
- const json = await safeMemoryResponse(res);
337
- return json.data;
338
- }
339
- async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
340
- const res = await fetch(`${baseUrl}/v1/memories/import`, {
341
- method: 'POST',
342
- headers: {
343
- Authorization: `Bearer ${apiKey}`,
344
- 'Content-Type': 'application/json',
345
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
346
- },
347
- body: JSON.stringify({ memories, mode }),
348
- });
349
- const json = await safeMemoryResponse(res);
350
- return json.data;
351
- }
352
- async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
353
- const qs = new URLSearchParams();
354
- qs.set('q', query);
355
- if (params?.limit)
356
- qs.set('limit', String(params.limit));
357
- if (params?.from)
358
- qs.set('from', params.from);
359
- if (params?.to)
360
- qs.set('to', params.to);
361
- if (params?.tags)
362
- qs.set('tags', params.tags);
363
- if (params?.source)
364
- qs.set('source', params.source);
365
- if (params?.status)
366
- qs.set('status', params.status);
367
- if (params?.shared !== undefined)
368
- qs.set('shared', String(params.shared));
369
- const res = await fetch(`${baseUrl}/v1/memories/retrieve?${qs.toString()}`, {
370
- headers: {
371
- Authorization: `Bearer ${apiKey}`,
372
- ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
373
- },
374
- });
375
- const json = await safeMemoryResponse(res);
376
- return json.data;
377
- }
378
- // Tool definitions (unchanged)
379
- const TOOLS = [
380
- {
381
- name: 'marrow_orient',
382
- description: ' CALL THIS FIRST — every session, before any other tool. ' +
383
- 'Returns failure warnings from your history so you avoid known mistakes immediately. ' +
384
- 'If shouldPause=true, review lessons before acting. ' +
385
- 'orient() reads from the hive. think() writes to it. Both are required — this is what makes Marrow compound.',
386
- inputSchema: {
387
- type: 'object',
388
- properties: {
389
- taskType: {
390
- type: 'string',
391
- enum: ['implementation', 'security', 'architecture', 'process', 'general'],
392
- description: 'Optional: filter warnings to a specific task type you are about to perform',
393
- },
394
- autoWarn: {
395
- type: 'boolean',
396
- description: 'Enable active intervention: scans recent failures, returns HIGH/MEDIUM/LOW severity warnings with recommendations. Recommended: true.',
236
+ // Memory API functions — all patched with safeMemoryResponse and validatePathParam
237
+ async function marrowListMemories(apiKey, baseUrl, params, sessionId) {
238
+ const qs = new URLSearchParams();
239
+ if (params?.status)
240
+ qs.set('status', params.status);
241
+ if (params?.query)
242
+ qs.set('query', params.query);
243
+ if (params?.limit)
244
+ qs.set('limit', String(params.limit));
245
+ if (params?.agentId)
246
+ qs.set('agent_id', params.agentId);
247
+ const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
248
+ headers: {
249
+ Authorization: `Bearer ${apiKey}`,
250
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
251
+ },
252
+ });
253
+ const json = await safeMemoryResponse(res);
254
+ return json.data?.memories || [];
255
+ }
256
+ async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
257
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
258
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
259
+ headers: {
260
+ Authorization: `Bearer ${apiKey}`,
261
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
262
+ },
263
+ });
264
+ const json = await safeMemoryResponse(res);
265
+ return json.data?.memory || null;
266
+ }
267
+ async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
268
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
269
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
270
+ method: 'PATCH',
271
+ headers: {
272
+ Authorization: `Bearer ${apiKey}`,
273
+ 'Content-Type': 'application/json',
274
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
275
+ },
276
+ body: JSON.stringify(patch),
277
+ });
278
+ const json = await safeMemoryResponse(res);
279
+ return json.data?.memory;
280
+ }
281
+ async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
282
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
283
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
284
+ method: 'DELETE',
285
+ headers: {
286
+ Authorization: `Bearer ${apiKey}`,
287
+ 'Content-Type': 'application/json',
288
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
289
+ },
290
+ body: JSON.stringify(meta || {}),
291
+ });
292
+ const json = await safeMemoryResponse(res);
293
+ return json.data?.memory;
294
+ }
295
+ async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
296
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
297
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
298
+ method: 'POST',
299
+ headers: {
300
+ Authorization: `Bearer ${apiKey}`,
301
+ 'Content-Type': 'application/json',
302
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
303
+ },
304
+ body: JSON.stringify(meta || {}),
305
+ });
306
+ const json = await safeMemoryResponse(res);
307
+ return json.data?.memory;
308
+ }
309
+ async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
310
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
311
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
312
+ method: 'POST',
313
+ headers: {
314
+ Authorization: `Bearer ${apiKey}`,
315
+ 'Content-Type': 'application/json',
316
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
317
+ },
318
+ body: JSON.stringify(replacement),
319
+ });
320
+ const json = await safeMemoryResponse(res);
321
+ return json.data;
322
+ }
323
+ async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
324
+ const safeId = (0, index_1.validatePathParam)(id, 'id');
325
+ const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
326
+ method: 'POST',
327
+ headers: {
328
+ Authorization: `Bearer ${apiKey}`,
329
+ 'Content-Type': 'application/json',
330
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
331
+ },
332
+ body: JSON.stringify({ agent_ids: agentIds, actor }),
333
+ });
334
+ const json = await safeMemoryResponse(res);
335
+ return json.data?.memory;
336
+ }
337
+ async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
338
+ const qs = new URLSearchParams();
339
+ if (params?.format)
340
+ qs.set('format', params.format);
341
+ if (params?.status)
342
+ qs.set('status', params.status);
343
+ if (params?.tags)
344
+ qs.set('tags', params.tags);
345
+ const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
346
+ headers: {
347
+ Authorization: `Bearer ${apiKey}`,
348
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
349
+ },
350
+ });
351
+ const json = await safeMemoryResponse(res);
352
+ return json.data;
353
+ }
354
+ async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
355
+ const res = await fetch(`${baseUrl}/v1/memories/import`, {
356
+ method: 'POST',
357
+ headers: {
358
+ Authorization: `Bearer ${apiKey}`,
359
+ 'Content-Type': 'application/json',
360
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
361
+ },
362
+ body: JSON.stringify({ memories, mode }),
363
+ });
364
+ const json = await safeMemoryResponse(res);
365
+ return json.data;
366
+ }
367
+ async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
368
+ const qs = new URLSearchParams();
369
+ qs.set('q', query);
370
+ if (params?.limit)
371
+ qs.set('limit', String(params.limit));
372
+ if (params?.from)
373
+ qs.set('from', params.from);
374
+ if (params?.to)
375
+ qs.set('to', params.to);
376
+ if (params?.tags)
377
+ qs.set('tags', params.tags);
378
+ if (params?.source)
379
+ qs.set('source', params.source);
380
+ if (params?.status)
381
+ qs.set('status', params.status);
382
+ if (params?.shared !== undefined)
383
+ qs.set('shared', String(params.shared));
384
+ const res = await fetch(`${baseUrl}/v1/memories/retrieve?${qs.toString()}`, {
385
+ headers: {
386
+ Authorization: `Bearer ${apiKey}`,
387
+ ...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
388
+ },
389
+ });
390
+ const json = await safeMemoryResponse(res);
391
+ return json.data;
392
+ }
393
+ // Tool definitions (unchanged)
394
+ const TOOLS = [
395
+ {
396
+ name: 'marrow_orient',
397
+ description: '⚡ CALL THIS FIRST — every session, before any other tool. ' +
398
+ 'Returns failure warnings from your history so you avoid known mistakes immediately. ' +
399
+ 'If shouldPause=true, review lessons before acting. ' +
400
+ 'orient() reads from the hive. think() writes to it. Both are required — this is what makes Marrow compound.',
401
+ inputSchema: {
402
+ type: 'object',
403
+ properties: {
404
+ taskType: {
405
+ type: 'string',
406
+ enum: ['implementation', 'security', 'architecture', 'process', 'general'],
407
+ description: 'Optional: filter warnings to a specific task type you are about to perform',
408
+ },
409
+ autoWarn: {
410
+ type: 'boolean',
411
+ description: 'Enable active intervention: scans recent failures, returns HIGH/MEDIUM/LOW severity warnings with recommendations. Recommended: true.',
412
+ },
397
413
  },
414
+ required: [],
398
415
  },
399
- required: [],
400
416
  },
401
- },
402
- {
403
- name: 'marrow_think',
404
- description: 'Log intent and get collective intelligence before acting. ' +
405
- 'Call this before every meaningful action. ' +
406
- 'Returns pattern insights, similar past decisions, failure detection, and a recommendedNext field — follow it. ' +
407
- 'Pass previous_outcome to auto-commit the last decision and open a new one. ' +
408
- 'Response MAY include: onboarding_hint (new accounts), intelligence.collective (cross-account patterns), intelligence.team_context (recent decisions from other sessions).',
409
- inputSchema: {
410
- type: 'object',
411
- properties: {
412
- action: { type: 'string', description: 'What the agent is about to do' },
413
- type: {
414
- type: 'string',
415
- enum: ['implementation', 'security', 'architecture', 'process', 'general'],
416
- description: 'Type of action (default: general)',
417
+ {
418
+ name: 'marrow_think',
419
+ description: 'Log intent and get collective intelligence before acting. ' +
420
+ 'Call this before every meaningful action. ' +
421
+ 'Returns pattern insights, similar past decisions, failure detection, and a recommendedNext field — follow it. ' +
422
+ 'Pass previous_outcome to auto-commit the last decision and open a new one. ' +
423
+ 'Response MAY include: onboarding_hint (new accounts), intelligence.collective (cross-account patterns), intelligence.team_context (recent decisions from other sessions).',
424
+ inputSchema: {
425
+ type: 'object',
426
+ properties: {
427
+ action: { type: 'string', description: 'What the agent is about to do' },
428
+ type: {
429
+ type: 'string',
430
+ enum: ['implementation', 'security', 'architecture', 'process', 'general'],
431
+ description: 'Type of action (default: general)',
432
+ },
433
+ context: { type: 'object', description: 'Optional metadata about the current situation' },
434
+ previous_decision_id: { type: 'string', description: 'decision_id from previous think() call — auto-commits that session' },
435
+ previous_success: { type: 'boolean', description: 'Did the previous action succeed?' },
436
+ previous_outcome: { type: 'string', description: 'What happened in the previous action (required if previous_decision_id provided)' },
437
+ checkLoop: { type: 'boolean', description: 'Enable loop detection: warns if you are about to retry a failed approach. Recommended: true.' },
417
438
  },
418
- context: { type: 'object', description: 'Optional metadata about the current situation' },
419
- previous_decision_id: { type: 'string', description: 'decision_id from previous think() call — auto-commits that session' },
420
- previous_success: { type: 'boolean', description: 'Did the previous action succeed?' },
421
- previous_outcome: { type: 'string', description: 'What happened in the previous action (required if previous_decision_id provided)' },
422
- checkLoop: { type: 'boolean', description: 'Enable loop detection: warns if you are about to retry a failed approach. Recommended: true.' },
439
+ required: ['action'],
423
440
  },
424
- required: ['action'],
425
441
  },
426
- },
427
- {
428
- name: 'marrow_commit',
429
- description: 'Explicitly commit the result of an action to Marrow. ' +
430
- 'Optional marrow_think() auto-commits if you pass previous_outcome. ' +
431
- 'Use when you need explicit control over commit timing.',
432
- inputSchema: {
433
- type: 'object',
434
- properties: {
435
- decision_id: { type: 'string', description: 'decision_id from the marrow_think call' },
436
- success: { type: 'boolean', description: 'Did the action succeed?' },
437
- outcome: { type: 'string', description: 'What happened be specific, this trains the hive' },
438
- caused_by: { type: 'string', description: 'Optional: what caused this action' },
442
+ {
443
+ name: 'marrow_commit',
444
+ description: 'Explicitly commit the result of an action to Marrow. ' +
445
+ 'Optional marrow_think() auto-commits if you pass previous_outcome. ' +
446
+ 'Use when you need explicit control over commit timing.',
447
+ inputSchema: {
448
+ type: 'object',
449
+ properties: {
450
+ decision_id: { type: 'string', description: 'decision_id from the marrow_think call' },
451
+ success: { type: 'boolean', description: 'Did the action succeed?' },
452
+ outcome: { type: 'string', description: 'What happened — be specific, this trains the hive' },
453
+ caused_by: { type: 'string', description: 'Optional: what caused this action' },
454
+ },
455
+ required: ['decision_id', 'success', 'outcome'],
439
456
  },
440
- required: ['decision_id', 'success', 'outcome'],
441
457
  },
442
- },
443
- {
444
- name: 'marrow_run',
445
- description: 'Zero-ceremony memory logging. Single call handles orient think commit automatically. ' +
446
- 'Use this instead of chaining marrow_think + marrow_commit when you want Marrow to just work without managing the loop yourself.',
447
- inputSchema: {
448
- type: 'object',
449
- properties: {
450
- description: { type: 'string', description: 'What the agent did' },
451
- success: { type: 'boolean', description: 'Whether it succeeded' },
452
- outcome: { type: 'string', description: 'One-line summary of what happened' },
453
- type: {
454
- type: 'string',
455
- enum: ['implementation', 'security', 'architecture', 'process', 'general'],
456
- description: 'Type of action (default: general)',
458
+ {
459
+ name: 'marrow_run',
460
+ description: 'Zero-ceremony memory logging. Single call handles orient → think → commit automatically. ' +
461
+ 'Use this instead of chaining marrow_think + marrow_commit when you want Marrow to just work without managing the loop yourself.',
462
+ inputSchema: {
463
+ type: 'object',
464
+ properties: {
465
+ description: { type: 'string', description: 'What the agent did' },
466
+ success: { type: 'boolean', description: 'Whether it succeeded' },
467
+ outcome: { type: 'string', description: 'One-line summary of what happened' },
468
+ type: {
469
+ type: 'string',
470
+ enum: ['implementation', 'security', 'architecture', 'process', 'general'],
471
+ description: 'Type of action (default: general)',
472
+ },
457
473
  },
474
+ required: ['description', 'success', 'outcome'],
458
475
  },
459
- required: ['description', 'success', 'outcome'],
460
476
  },
461
- },
462
- {
463
- name: 'marrow_auto',
464
- description: 'Zero-friction Marrow logging. One call for any action Marrow handles everything in the background without blocking. ' +
465
- 'Pass what you are about to do. Optionally pass outcome if already done. ' +
466
- 'Use for ANY action: deploys, file writes, API calls, external sends. ' +
467
- 'If you only have time for one call: pass action + outcome + success together — done in one shot.',
468
- inputSchema: {
469
- type: 'object',
470
- properties: {
471
- action: { type: 'string', description: 'What you are about to do or just did' },
472
- outcome: { type: 'string', description: 'What happened (if already done). Omit to log intent only.' },
473
- success: { type: 'boolean', description: 'Did it succeed (default: true)' },
474
- type: {
475
- type: 'string',
476
- enum: ['implementation', 'security', 'architecture', 'process', 'general'],
477
- description: 'Type of action (default: general)',
477
+ {
478
+ name: 'marrow_auto',
479
+ description: 'Zero-friction Marrow logging. One call for any action — Marrow handles everything in the background without blocking. ' +
480
+ 'Pass what you are about to do. Optionally pass outcome if already done. ' +
481
+ 'Use for ANY action: deploys, file writes, API calls, external sends. ' +
482
+ 'If you only have time for one call: pass action + outcome + success together — done in one shot.',
483
+ inputSchema: {
484
+ type: 'object',
485
+ properties: {
486
+ action: { type: 'string', description: 'What you are about to do or just did' },
487
+ outcome: { type: 'string', description: 'What happened (if already done). Omit to log intent only.' },
488
+ success: { type: 'boolean', description: 'Did it succeed (default: true)' },
489
+ type: {
490
+ type: 'string',
491
+ enum: ['implementation', 'security', 'architecture', 'process', 'general'],
492
+ description: 'Type of action (default: general)',
493
+ },
478
494
  },
495
+ required: ['action'],
479
496
  },
480
- required: ['action'],
481
497
  },
482
- },
483
- {
484
- name: 'marrow_ask',
485
- description: 'Query the collective hive in plain English. ' +
486
- 'Ask about failure patterns, what worked, what broke, or get a recommendation before acting. ' +
487
- 'Returns direct answer + supporting evidence.',
488
- inputSchema: {
489
- type: 'object',
490
- properties: {
491
- query: { type: 'string', description: 'Plain English question about your decision history' },
498
+ {
499
+ name: 'marrow_ask',
500
+ description: 'Query the collective hive in plain English. ' +
501
+ 'Ask about failure patterns, what worked, what broke, or get a recommendation before acting. ' +
502
+ 'Returns direct answer + supporting evidence.',
503
+ inputSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ query: { type: 'string', description: 'Plain English question about your decision history' },
507
+ },
508
+ required: ['query'],
492
509
  },
493
- required: ['query'],
494
510
  },
495
- },
496
- {
497
- name: 'marrow_status',
498
- description: 'Check Marrow platform health and status.',
499
- inputSchema: { type: 'object', properties: {}, required: [] },
500
- },
501
- {
502
- name: 'marrow_list_memories',
503
- description: 'List memories with optional filters (status, query, limit, agent_id for shared memories).',
504
- inputSchema: {
505
- type: 'object',
506
- properties: {
507
- status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Filter by status' },
508
- query: { type: 'string', description: 'Search query' },
509
- limit: { type: 'number', description: 'Max results (default: 20)' },
510
- agentId: { type: 'string', description: 'Agent ID for shared memories' },
511
+ {
512
+ name: 'marrow_status',
513
+ description: 'Check Marrow platform health and status.',
514
+ inputSchema: { type: 'object', properties: {}, required: [] },
515
+ },
516
+ {
517
+ name: 'marrow_list_memories',
518
+ description: 'List memories with optional filters (status, query, limit, agent_id for shared memories).',
519
+ inputSchema: {
520
+ type: 'object',
521
+ properties: {
522
+ status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Filter by status' },
523
+ query: { type: 'string', description: 'Search query' },
524
+ limit: { type: 'number', description: 'Max results (default: 20)' },
525
+ agentId: { type: 'string', description: 'Agent ID for shared memories' },
526
+ },
527
+ required: [],
511
528
  },
512
- required: [],
513
529
  },
514
- },
515
- {
516
- name: 'marrow_get_memory',
517
- description: 'Get a single memory by ID.',
518
- inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Memory ID' } }, required: ['id'] },
519
- },
520
- {
521
- name: 'marrow_update_memory',
522
- description: 'Update memory text, tags, or metadata.',
523
- inputSchema: {
524
- type: 'object',
525
- properties: {
526
- id: { type: 'string', description: 'Memory ID' },
527
- text: { type: 'string', description: 'New text' },
528
- source: { type: 'string', description: 'Source' },
529
- tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
530
- actor: { type: 'string', description: 'Actor name' },
531
- note: { type: 'string', description: 'Audit note' },
530
+ {
531
+ name: 'marrow_get_memory',
532
+ description: 'Get a single memory by ID.',
533
+ inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Memory ID' } }, required: ['id'] },
534
+ },
535
+ {
536
+ name: 'marrow_update_memory',
537
+ description: 'Update memory text, tags, or metadata.',
538
+ inputSchema: {
539
+ type: 'object',
540
+ properties: {
541
+ id: { type: 'string', description: 'Memory ID' },
542
+ text: { type: 'string', description: 'New text' },
543
+ source: { type: 'string', description: 'Source' },
544
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
545
+ actor: { type: 'string', description: 'Actor name' },
546
+ note: { type: 'string', description: 'Audit note' },
547
+ },
548
+ required: ['id'],
532
549
  },
533
- required: ['id'],
534
550
  },
535
- },
536
- {
537
- name: 'marrow_delete_memory',
538
- description: 'Soft delete a memory.',
539
- inputSchema: {
540
- type: 'object',
541
- properties: {
542
- id: { type: 'string', description: 'Memory ID' },
543
- actor: { type: 'string', description: 'Actor name' },
544
- note: { type: 'string', description: 'Audit note' },
551
+ {
552
+ name: 'marrow_delete_memory',
553
+ description: 'Soft delete a memory.',
554
+ inputSchema: {
555
+ type: 'object',
556
+ properties: {
557
+ id: { type: 'string', description: 'Memory ID' },
558
+ actor: { type: 'string', description: 'Actor name' },
559
+ note: { type: 'string', description: 'Audit note' },
560
+ },
561
+ required: ['id'],
545
562
  },
546
- required: ['id'],
547
563
  },
548
- },
549
- {
550
- name: 'marrow_mark_outdated',
551
- description: 'Mark a memory as outdated.',
552
- inputSchema: {
553
- type: 'object',
554
- properties: {
555
- id: { type: 'string', description: 'Memory ID' },
556
- actor: { type: 'string', description: 'Actor name' },
557
- note: { type: 'string', description: 'Audit note' },
564
+ {
565
+ name: 'marrow_mark_outdated',
566
+ description: 'Mark a memory as outdated.',
567
+ inputSchema: {
568
+ type: 'object',
569
+ properties: {
570
+ id: { type: 'string', description: 'Memory ID' },
571
+ actor: { type: 'string', description: 'Actor name' },
572
+ note: { type: 'string', description: 'Audit note' },
573
+ },
574
+ required: ['id'],
558
575
  },
559
- required: ['id'],
560
576
  },
561
- },
562
- {
563
- name: 'marrow_supersede_memory',
564
- description: 'Atomically replace a memory with a new version.',
565
- inputSchema: {
566
- type: 'object',
567
- properties: {
568
- id: { type: 'string', description: 'Memory ID to supersede' },
569
- text: { type: 'string', description: 'New memory text' },
570
- source: { type: 'string', description: 'Source' },
571
- tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
572
- actor: { type: 'string', description: 'Actor name' },
573
- note: { type: 'string', description: 'Audit note' },
577
+ {
578
+ name: 'marrow_supersede_memory',
579
+ description: 'Atomically replace a memory with a new version.',
580
+ inputSchema: {
581
+ type: 'object',
582
+ properties: {
583
+ id: { type: 'string', description: 'Memory ID to supersede' },
584
+ text: { type: 'string', description: 'New memory text' },
585
+ source: { type: 'string', description: 'Source' },
586
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
587
+ actor: { type: 'string', description: 'Actor name' },
588
+ note: { type: 'string', description: 'Audit note' },
589
+ },
590
+ required: ['id', 'text'],
574
591
  },
575
- required: ['id', 'text'],
576
592
  },
577
- },
578
- {
579
- name: 'marrow_share_memory',
580
- description: 'Share a memory with specific agents.',
581
- inputSchema: {
582
- type: 'object',
583
- properties: {
584
- id: { type: 'string', description: 'Memory ID' },
585
- agentIds: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to share with' },
586
- actor: { type: 'string', description: 'Actor name' },
593
+ {
594
+ name: 'marrow_share_memory',
595
+ description: 'Share a memory with specific agents.',
596
+ inputSchema: {
597
+ type: 'object',
598
+ properties: {
599
+ id: { type: 'string', description: 'Memory ID' },
600
+ agentIds: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to share with' },
601
+ actor: { type: 'string', description: 'Actor name' },
602
+ },
603
+ required: ['id', 'agentIds'],
587
604
  },
588
- required: ['id', 'agentIds'],
589
605
  },
590
- },
591
- {
592
- name: 'marrow_export_memories',
593
- description: 'Export memories to JSON or CSV.',
594
- inputSchema: {
595
- type: 'object',
596
- properties: {
597
- format: { type: 'string', enum: ['json', 'csv'], description: 'Export format' },
598
- status: { type: 'string', enum: ['active', 'all'], description: 'Filter by status' },
599
- tags: { type: 'string', description: 'Comma-separated tags' },
606
+ {
607
+ name: 'marrow_export_memories',
608
+ description: 'Export memories to JSON or CSV.',
609
+ inputSchema: {
610
+ type: 'object',
611
+ properties: {
612
+ format: { type: 'string', enum: ['json', 'csv'], description: 'Export format' },
613
+ status: { type: 'string', enum: ['active', 'all'], description: 'Filter by status' },
614
+ tags: { type: 'string', description: 'Comma-separated tags' },
615
+ },
616
+ required: [],
600
617
  },
601
- required: [],
602
618
  },
603
- },
604
- {
605
- name: 'marrow_import_memories',
606
- description: 'Import memories with merge (dedup) or replace mode.',
607
- inputSchema: {
608
- type: 'object',
609
- properties: {
610
- memories: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, source: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } } }, description: 'Memories to import' },
611
- mode: { type: 'string', enum: ['merge', 'replace'], description: 'Import mode' },
619
+ {
620
+ name: 'marrow_import_memories',
621
+ description: 'Import memories with merge (dedup) or replace mode.',
622
+ inputSchema: {
623
+ type: 'object',
624
+ properties: {
625
+ memories: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, source: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } } }, description: 'Memories to import' },
626
+ mode: { type: 'string', enum: ['merge', 'replace'], description: 'Import mode' },
627
+ },
628
+ required: ['memories', 'mode'],
612
629
  },
613
- required: ['memories', 'mode'],
614
630
  },
615
- },
616
- {
617
- name: 'marrow_retrieve_memories',
618
- description: 'Full-text search memories with filters (from, to, tags, source, status, shared).',
619
- inputSchema: {
620
- type: 'object',
621
- properties: {
622
- query: { type: 'string', description: 'Search query' },
623
- limit: { type: 'number', description: 'Max results' },
624
- from: { type: 'string', description: 'From date (ISO-8601)' },
625
- to: { type: 'string', description: 'To date (ISO-8601)' },
626
- tags: { type: 'string', description: 'Comma-separated tags' },
627
- source: { type: 'string', description: 'Source filter' },
628
- status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Status filter' },
629
- shared: { type: 'boolean', description: 'Include shared memories' },
631
+ {
632
+ name: 'marrow_retrieve_memories',
633
+ description: 'Full-text search memories with filters (from, to, tags, source, status, shared).',
634
+ inputSchema: {
635
+ type: 'object',
636
+ properties: {
637
+ query: { type: 'string', description: 'Search query' },
638
+ limit: { type: 'number', description: 'Max results' },
639
+ from: { type: 'string', description: 'From date (ISO-8601)' },
640
+ to: { type: 'string', description: 'To date (ISO-8601)' },
641
+ tags: { type: 'string', description: 'Comma-separated tags' },
642
+ source: { type: 'string', description: 'Source filter' },
643
+ status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Status filter' },
644
+ shared: { type: 'boolean', description: 'Include shared memories' },
645
+ },
646
+ required: ['query'],
630
647
  },
631
- required: ['query'],
632
648
  },
633
- },
634
- {
635
- name: 'marrow_workflow',
636
- description: 'Interact with Marrow Workflow Registry. Register, start, and advance multi-step workflows. ' +
637
- 'Actions: register (create workflow template), list (show all), get (details), start (begin instance), ' +
638
- 'advance (complete a step), instances (list runs).',
639
- inputSchema: {
640
- type: 'object',
641
- properties: {
642
- action: { type: 'string', enum: ['register', 'list', 'get', 'update', 'start', 'advance', 'instances'], description: 'Workflow action to perform' },
643
- workflowId: { type: 'string', description: 'Workflow ID (required for get/start/advance/instances)' },
644
- instanceId: { type: 'string', description: 'Instance ID (required for advance)' },
645
- name: { type: 'string', description: 'Workflow name (for register)' },
646
- description: { type: 'string', description: 'Workflow description (for register/update)' },
647
- steps: { type: 'array', description: 'Step definitions (for register)', items: { type: 'object', properties: { step: { type: 'number', description: 'Step order (1, 2, 3...)' }, agent_role: { type: 'string', description: 'Expected agent role (e.g., "builder", "auditor")' }, action_type: { type: 'string', description: 'Action type (e.g., "build", "audit", "patch")' }, description: { type: 'string', description: 'Step description' } }, required: ['step', 'description'] } },
648
- tags: { type: 'array', items: { type: 'string' }, description: 'Tags (for register)' },
649
- agentId: { type: 'string', description: 'Agent ID starting the workflow (for start)' },
650
- context: { type: 'object', description: 'Workflow context (for start)' },
651
- inputs: { type: 'object', description: 'Workflow inputs (for start)' },
652
- stepCompleted: { type: 'number', description: 'Step number completed (for advance)' },
653
- outcome: { type: 'string', description: 'Step outcome (for advance)' },
654
- nextAgentId: { type: 'string', description: 'Next agent for the following step (for advance)' },
655
- contextUpdate: { type: 'object', description: 'Context changes (for advance)' },
656
- status: { type: 'string', enum: ['running', 'completed', 'failed', 'cancelled', 'active', 'archived'], description: 'Filter by status (for list/instances)' },
649
+ {
650
+ name: 'marrow_workflow',
651
+ description: 'Interact with Marrow Workflow Registry. Register, start, and advance multi-step workflows. ' +
652
+ 'Actions: register (create workflow template), list (show all), get (details), start (begin instance), ' +
653
+ 'advance (complete a step), instances (list runs).',
654
+ inputSchema: {
655
+ type: 'object',
656
+ properties: {
657
+ action: { type: 'string', enum: ['register', 'list', 'get', 'update', 'start', 'advance', 'instances'], description: 'Workflow action to perform' },
658
+ workflowId: { type: 'string', description: 'Workflow ID (required for get/start/advance/instances)' },
659
+ instanceId: { type: 'string', description: 'Instance ID (required for advance)' },
660
+ name: { type: 'string', description: 'Workflow name (for register)' },
661
+ description: { type: 'string', description: 'Workflow description (for register/update)' },
662
+ steps: { type: 'array', description: 'Step definitions (for register)', items: { type: 'object', properties: { step: { type: 'number', description: 'Step order (1, 2, 3...)' }, agent_role: { type: 'string', description: 'Expected agent role (e.g., "builder", "auditor")' }, action_type: { type: 'string', description: 'Action type (e.g., "build", "audit", "patch")' }, description: { type: 'string', description: 'Step description' } }, required: ['step', 'description'] } },
663
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags (for register)' },
664
+ agentId: { type: 'string', description: 'Agent ID starting the workflow (for start)' },
665
+ context: { type: 'object', description: 'Workflow context (for start)' },
666
+ inputs: { type: 'object', description: 'Workflow inputs (for start)' },
667
+ stepCompleted: { type: 'number', description: 'Step number completed (for advance)' },
668
+ outcome: { type: 'string', description: 'Step outcome (for advance)' },
669
+ nextAgentId: { type: 'string', description: 'Next agent for the following step (for advance)' },
670
+ contextUpdate: { type: 'object', description: 'Context changes (for advance)' },
671
+ status: { type: 'string', enum: ['running', 'completed', 'failed', 'cancelled', 'active', 'archived'], description: 'Filter by status (for list/instances)' },
672
+ },
673
+ required: ['action'],
657
674
  },
658
- required: ['action'],
659
675
  },
660
- },
661
- {
662
- name: 'marrow_dashboard',
663
- description: 'Get operator dashboard account health, top failures, workflow status, recent activity, Marrow\'s saves metric. ' +
664
- 'One call returns everything an operator needs to see.',
665
- inputSchema: { type: 'object', properties: {}, required: [] },
666
- },
667
- {
668
- name: 'marrow_digest',
669
- description: 'Get periodic summary of agent activity and Marrow impact (default 7-day period). ' +
670
- 'Shows decision counts, success rate trend vs previous period, saves, top improvements and risks.',
671
- inputSchema: {
672
- type: 'object',
673
- properties: {
674
- period: { type: 'string', description: 'Time period: 7d (default), 14d, or 30d' },
676
+ {
677
+ name: 'marrow_dashboard',
678
+ description: 'Get operator dashboard — account health, top failures, workflow status, recent activity, Marrow\'s saves metric. ' +
679
+ 'One call returns everything an operator needs to see.',
680
+ inputSchema: { type: 'object', properties: {}, required: [] },
681
+ },
682
+ {
683
+ name: 'marrow_digest',
684
+ description: 'Get periodic summary of agent activity and Marrow impact (default 7-day period). ' +
685
+ 'Shows decision counts, success rate trend vs previous period, saves, top improvements and risks.',
686
+ inputSchema: {
687
+ type: 'object',
688
+ properties: {
689
+ period: { type: 'string', description: 'Time period: 7d (default), 14d, or 30d' },
690
+ },
691
+ required: [],
675
692
  },
676
- required: [],
677
693
  },
678
- },
679
- {
680
- name: 'marrow_session_end',
681
- description: 'Explicitly end the current session. Optionally auto-commits any open decision. ' +
682
- 'Prevents orphaned decisions when an agent finishes a task.',
683
- inputSchema: {
684
- type: 'object',
685
- properties: {
686
- autoCommitOpen: { type: 'boolean', description: 'Whether to auto-commit any open decision (default: false)' },
694
+ {
695
+ name: 'marrow_session_end',
696
+ description: 'Explicitly end the current session. Optionally auto-commits any open decision. ' +
697
+ 'Prevents orphaned decisions when an agent finishes a task.',
698
+ inputSchema: {
699
+ type: 'object',
700
+ properties: {
701
+ autoCommitOpen: { type: 'boolean', description: 'Whether to auto-commit any open decision (default: false)' },
702
+ },
703
+ required: [],
687
704
  },
688
- required: [],
689
705
  },
690
- },
691
- {
692
- name: 'marrow_accept_detected',
693
- description: 'Convert a detected decision pattern into an enforced workflow. ' +
694
- 'The pattern ID comes from suggested_workflows in the orient() response.',
695
- inputSchema: {
696
- type: 'object',
697
- properties: {
698
- detectedId: { type: 'string', description: 'ID of the detected pattern to accept' },
706
+ {
707
+ name: 'marrow_accept_detected',
708
+ description: 'Convert a detected decision pattern into an enforced workflow. ' +
709
+ 'The pattern ID comes from suggested_workflows in the orient() response.',
710
+ inputSchema: {
711
+ type: 'object',
712
+ properties: {
713
+ detectedId: { type: 'string', description: 'ID of the detected pattern to accept' },
714
+ },
715
+ required: ['detectedId'],
699
716
  },
700
- required: ['detectedId'],
701
717
  },
702
- },
703
- {
704
- name: 'marrow_list_templates',
705
- description: 'Browse pre-built workflow templates. Filter by industry (insurance, healthcare, ecommerce, legal, saas, fintech, media, enterprise) or category. ' +
706
- 'Use to discover available workflows before installing.',
707
- inputSchema: {
708
- type: 'object',
709
- properties: {
710
- industry: { type: 'string', description: 'Filter by industry (e.g., insurance, healthcare, saas)' },
711
- category: { type: 'string', description: 'Filter by category (e.g., claims, engineering, support)' },
712
- limit: { type: 'number', description: 'Max results (default: 20)' },
718
+ {
719
+ name: 'marrow_list_templates',
720
+ description: 'Browse pre-built workflow templates. Filter by industry (insurance, healthcare, ecommerce, legal, saas, fintech, media, enterprise) or category. ' +
721
+ 'Use to discover available workflows before installing.',
722
+ inputSchema: {
723
+ type: 'object',
724
+ properties: {
725
+ industry: { type: 'string', description: 'Filter by industry (e.g., insurance, healthcare, saas)' },
726
+ category: { type: 'string', description: 'Filter by category (e.g., claims, engineering, support)' },
727
+ limit: { type: 'number', description: 'Max results (default: 20)' },
728
+ },
729
+ required: [],
713
730
  },
714
- required: [],
715
731
  },
716
- },
717
- {
718
- name: 'marrow_install_template',
719
- description: 'Install a workflow template into your fleet as an active workflow. ' +
720
- 'Use after marrow_list_templates to pick one.',
721
- inputSchema: {
722
- type: 'object',
723
- properties: {
724
- slug: { type: 'string', description: 'Template slug to install (e.g., code-review-deploy, claims-triage)' },
732
+ {
733
+ name: 'marrow_install_template',
734
+ description: 'Install a workflow template into your fleet as an active workflow. ' +
735
+ 'Use after marrow_list_templates to pick one.',
736
+ inputSchema: {
737
+ type: 'object',
738
+ properties: {
739
+ slug: { type: 'string', description: 'Template slug to install (e.g., code-review-deploy, claims-triage)' },
740
+ },
741
+ required: ['slug'],
725
742
  },
726
- required: ['slug'],
727
743
  },
728
- },
729
- ];
730
- // Request handler
731
- async function handleRequest(req) {
732
- const { id, method, params } = req;
733
- // [FIX #15] Enforce initialize-first per MCP spec
734
- if (!initialized && method !== 'initialize') {
735
- error(id, -32002, 'Server not initialized. Send initialize first.');
736
- return;
737
- }
738
- try {
739
- if (method === 'initialize') {
740
- initialized = true;
741
- success(id, {
742
- protocolVersion: '2024-11-05',
743
- capabilities: { tools: {}, prompts: {} },
744
- serverInfo: { name: 'marrow', version: '3.1.4' },
745
- });
746
- // Auto-enroll: emit enrollment notification on connection
747
- if (AUTO_ENROLL) {
748
- send({
749
- jsonrpc: '2.0',
750
- method: 'notifications/message',
751
- params: {
752
- level: 'info',
753
- logger: 'marrow',
754
- data: {
755
- type: 'auto_enroll',
756
- message: 'Marrow auto-enroll active. Call marrow_orient FIRST, then marrow_think before acting, marrow_commit after. Or use marrow_auto / marrow_run for one-call logging.',
757
- agentId: AGENT_ID || 'auto',
758
- },
759
- },
760
- });
761
- }
744
+ ];
745
+ // Request handler
746
+ async function handleRequest(req) {
747
+ const { id, method, params } = req;
748
+ // [FIX #15] Enforce initialize-first per MCP spec
749
+ if (!initialized && method !== 'initialize') {
750
+ error(id, -32002, 'Server not initialized. Send initialize first.');
762
751
  return;
763
752
  }
764
- if (method === 'prompts/list') {
765
- if (AUTO_ENROLL) {
753
+ try {
754
+ if (method === 'initialize') {
755
+ initialized = true;
766
756
  success(id, {
767
- prompts: [
768
- {
769
- name: 'marrow-always-on',
770
- description: 'Always-on Marrow memory loop. Instructs the agent to orient at session start, log intent before meaningful actions, and commit outcomes after completion. Install once — works automatically.',
771
- arguments: [],
772
- },
773
- ],
757
+ protocolVersion: '2024-11-05',
758
+ capabilities: { tools: {}, prompts: {} },
759
+ serverInfo: { name: 'marrow', version: '3.2.0' },
774
760
  });
761
+ // Auto-enroll: emit enrollment notification on connection
762
+ if (AUTO_ENROLL) {
763
+ send({
764
+ jsonrpc: '2.0',
765
+ method: 'notifications/message',
766
+ params: {
767
+ level: 'info',
768
+ logger: 'marrow',
769
+ data: {
770
+ type: 'auto_enroll',
771
+ message: 'Marrow auto-enroll active. Call marrow_orient FIRST, then marrow_think before acting, marrow_commit after. Or use marrow_auto / marrow_run for one-call logging.',
772
+ agentId: AGENT_ID || 'auto',
773
+ },
774
+ },
775
+ });
776
+ }
777
+ return;
775
778
  }
776
- else {
777
- success(id, { prompts: [] });
778
- }
779
- return;
780
- }
781
- if (method === 'prompts/get') {
782
- const promptName = params?.name;
783
- if (promptName !== 'marrow-always-on' || !AUTO_ENROLL) {
784
- error(id, -32602, 'Unknown prompt');
779
+ if (method === 'prompts/list') {
780
+ if (AUTO_ENROLL) {
781
+ success(id, {
782
+ prompts: [
783
+ {
784
+ name: 'marrow-always-on',
785
+ description: 'Always-on Marrow memory loop. Instructs the agent to orient at session start, log intent before meaningful actions, and commit outcomes after completion. Install once — works automatically.',
786
+ arguments: [],
787
+ },
788
+ ],
789
+ });
790
+ }
791
+ else {
792
+ success(id, { prompts: [] });
793
+ }
785
794
  return;
786
795
  }
787
- success(id, {
788
- description: 'Always-on Marrow memory loop — automatic, zero-config',
789
- messages: [
790
- {
791
- role: 'user',
792
- content: {
793
- type: 'text',
794
- text: `You have Marrow — a memory layer that tells you what went wrong last time before you do it again.
796
+ if (method === 'prompts/get') {
797
+ const promptName = params?.name;
798
+ if (promptName !== 'marrow-always-on' || !AUTO_ENROLL) {
799
+ error(id, -32602, 'Unknown prompt');
800
+ return;
801
+ }
802
+ success(id, {
803
+ description: 'Always-on Marrow memory loop automatic, zero-config',
804
+ messages: [
805
+ {
806
+ role: 'user',
807
+ content: {
808
+ type: 'text',
809
+ text: `You have Marrow — a memory layer that tells you what went wrong last time before you do it again.
795
810
 
796
811
  ## PRIMARY: Use marrow_auto for everything
797
812
 
@@ -832,427 +847,428 @@ Every decision you log compounds. Failure rates drop. Patterns emerge. The next
832
847
  Marrow tells you what went wrong last time before you do it again. But only if you logged it.
833
848
 
834
849
  This is not optional overhead — it's how you stop repeating the same failures.`,
850
+ },
835
851
  },
836
- },
837
- ],
838
- });
839
- return;
840
- }
841
- if (method === 'tools/list') {
842
- success(id, { tools: TOOLS });
843
- return;
844
- }
845
- if (method === 'tools/call') {
846
- const toolName = params?.name;
847
- const args = (params?.arguments || {});
848
- if (toolName === 'marrow_orient') {
849
- orientCallCount++;
850
- let result;
851
- const wantAutoWarn = args.autoWarn ?? true;
852
- try {
853
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: wantAutoWarn }, SESSION_ID);
854
- }
855
- catch (e) {
856
- // autoWarn endpoint may not be deployed yet — fall back to legacy orient
857
- if (wantAutoWarn) {
858
- process.stderr.write(`[marrow] autoWarn orient not available, falling back to legacy\n`);
859
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: false }, SESSION_ID);
852
+ ],
853
+ });
854
+ return;
855
+ }
856
+ if (method === 'tools/list') {
857
+ success(id, { tools: TOOLS });
858
+ return;
859
+ }
860
+ if (method === 'tools/call') {
861
+ const toolName = params?.name;
862
+ const args = (params?.arguments || {});
863
+ if (toolName === 'marrow_orient') {
864
+ orientCallCount++;
865
+ let result;
866
+ const wantAutoWarn = args.autoWarn ?? true;
867
+ try {
868
+ result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: wantAutoWarn }, SESSION_ID);
869
+ }
870
+ catch (e) {
871
+ // autoWarn endpoint may not be deployed yet — fall back to legacy orient
872
+ if (wantAutoWarn) {
873
+ process.stderr.write(`[marrow] autoWarn orient not available, falling back to legacy\n`);
874
+ result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: false }, SESSION_ID);
875
+ }
876
+ else {
877
+ throw e;
878
+ }
879
+ }
880
+ if (AUTO_ENROLL && orientCallCount === 1) {
881
+ const enrollmentText = `\n\n📋 **Marrow Auto-Enroll Active**\n\n` +
882
+ `You have Marrow — a collective memory that tells you what went wrong last time before you do it again.\n\n` +
883
+ `## How it works (2 tools max):\n\n` +
884
+ `1. **Session start:** Call marrow_orient FIRST. You just did — it surfaces failure warnings.\n` +
885
+ `2. **Before any action:** Call marrow_think({ action: "..." }) — returns pattern insights + recommendedNext.\n` +
886
+ `3. **After acting:** Call marrow_commit({ decision_id, success, outcome }) — or pass previous_outcome to auto-commit.\n\n` +
887
+ `## Quick mode (one tool):\n` +
888
+ `- marrow_run({ description, success, outcome }) — handles orient → think → commit in one call.\n` +
889
+ `- marrow_auto({ action, outcome?, success? }) — fire-and-forget background logging.\n\n` +
890
+ `## Why you must not skip this:\n` +
891
+ `Every decision you log compounds. Failure rates drop. The next session starts smarter.\n` +
892
+ `Marrow tells you what went wrong last time — but only if you logged it.\n`;
893
+ const orientText = JSON.stringify(result, null, 2);
894
+ success(id, {
895
+ content: [{ type: 'text', text: enrollmentText + orientText }],
896
+ });
860
897
  }
861
898
  else {
862
- throw e;
899
+ success(id, {
900
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
901
+ });
863
902
  }
903
+ return;
864
904
  }
865
- if (AUTO_ENROLL && orientCallCount === 1) {
866
- const enrollmentText = `\n\n📋 **Marrow Auto-Enroll Active**\n\n` +
867
- `You have Marrow a collective memory that tells you what went wrong last time before you do it again.\n\n` +
868
- `## How it works (2 tools max):\n\n` +
869
- `1. **Session start:** Call marrow_orient FIRST. You just did — it surfaces failure warnings.\n` +
870
- `2. **Before any action:** Call marrow_think({ action: "..." }) — returns pattern insights + recommendedNext.\n` +
871
- `3. **After acting:** Call marrow_commit({ decision_id, success, outcome }) — or pass previous_outcome to auto-commit.\n\n` +
872
- `## Quick mode (one tool):\n` +
873
- `- marrow_run({ description, success, outcome }) — handles orient → think → commit in one call.\n` +
874
- `- marrow_auto({ action, outcome?, success? }) — fire-and-forget background logging.\n\n` +
875
- `## Why you must not skip this:\n` +
876
- `Every decision you log compounds. Failure rates drop. The next session starts smarter.\n` +
877
- `Marrow tells you what went wrong last time — but only if you logged it.\n`;
878
- const orientText = JSON.stringify(result, null, 2);
905
+ if (toolName === 'marrow_think') {
906
+ // [FIX #9] Validate required param
907
+ const action = requireString(args, 'action');
908
+ const result = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
909
+ action,
910
+ type: args.type,
911
+ context: args.context,
912
+ previous_decision_id: args.previous_decision_id,
913
+ previous_success: args.previous_success,
914
+ previous_outcome: args.previous_outcome,
915
+ checkLoop: args.checkLoop ?? true,
916
+ }, SESSION_ID);
917
+ // Refresh orient warnings every 5th think call
918
+ thinkCallCount++;
919
+ if (thinkCallCount % 5 === 0) {
920
+ refreshOrientWarnings();
921
+ }
922
+ // Inject cached orient warnings into intelligence.insights
923
+ if (cachedOrientWarnings.length > 0) {
924
+ const existingInsights = result.intelligence?.insights || [];
925
+ result.intelligence.insights = [
926
+ ...cachedOrientWarnings.map((w) => ({
927
+ type: 'failure_pattern',
928
+ summary: w.message,
929
+ action: `Review past ${w.type} failures before proceeding`,
930
+ severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
931
+ count: 0,
932
+ })),
933
+ ...existingInsights,
934
+ ];
935
+ }
936
+ lastDecisionId = result.decision_id;
937
+ lastCommitted = false;
879
938
  success(id, {
880
- content: [{ type: 'text', text: enrollmentText + orientText }],
939
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
881
940
  });
941
+ return;
882
942
  }
883
- else {
943
+ if (toolName === 'marrow_commit') {
944
+ // [FIX #9] Validate required params
945
+ const decision_id = requireString(args, 'decision_id');
946
+ const outcome = requireString(args, 'outcome');
947
+ if (typeof args.success !== 'boolean') {
948
+ throw new Error('"success" is required and must be a boolean');
949
+ }
950
+ const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
951
+ decision_id,
952
+ success: args.success,
953
+ outcome,
954
+ caused_by: args.caused_by,
955
+ }, SESSION_ID);
956
+ lastCommitted = true;
957
+ lastDecisionId = null;
884
958
  success(id, {
885
959
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
886
960
  });
961
+ return;
887
962
  }
888
- return;
889
- }
890
- if (toolName === 'marrow_think') {
891
- // [FIX #9] Validate required param
892
- const action = requireString(args, 'action');
893
- const result = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
894
- action,
895
- type: args.type,
896
- context: args.context,
897
- previous_decision_id: args.previous_decision_id,
898
- previous_success: args.previous_success,
899
- previous_outcome: args.previous_outcome,
900
- checkLoop: args.checkLoop ?? true,
901
- }, SESSION_ID);
902
- // Refresh orient warnings every 5th think call
903
- thinkCallCount++;
904
- if (thinkCallCount % 5 === 0) {
905
- refreshOrientWarnings();
906
- }
907
- // Inject cached orient warnings into intelligence.insights
908
- if (cachedOrientWarnings.length > 0) {
909
- const existingInsights = result.intelligence?.insights || [];
910
- result.intelligence.insights = [
911
- ...cachedOrientWarnings.map((w) => ({
912
- type: 'failure_pattern',
913
- summary: w.message,
914
- action: `Review past ${w.type} failures before proceeding`,
915
- severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
916
- count: 0,
917
- })),
918
- ...existingInsights,
919
- ];
920
- }
921
- lastDecisionId = result.decision_id;
922
- lastCommitted = false;
923
- success(id, {
924
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
925
- });
926
- return;
927
- }
928
- if (toolName === 'marrow_commit') {
929
- // [FIX #9] Validate required params
930
- const decision_id = requireString(args, 'decision_id');
931
- const outcome = requireString(args, 'outcome');
932
- if (typeof args.success !== 'boolean') {
933
- throw new Error('"success" is required and must be a boolean');
934
- }
935
- const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
936
- decision_id,
937
- success: args.success,
938
- outcome,
939
- caused_by: args.caused_by,
940
- }, SESSION_ID);
941
- lastCommitted = true;
942
- lastDecisionId = null;
943
- success(id, {
944
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
945
- });
946
- return;
947
- }
948
- if (toolName === 'marrow_run') {
949
- // [FIX #9] Validate required params
950
- const description = requireString(args, 'description');
951
- const outcome = requireString(args, 'outcome');
952
- // [FIX #16] Handle partial failures — return think result even if commit fails
953
- let thinkResult = null;
954
- try {
955
- await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
956
- }
957
- catch (err) {
958
- const msg = err instanceof Error ? err.message : String(err);
959
- process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
960
- }
961
- thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
962
- action: description,
963
- type: args.type || 'general',
964
- }, SESSION_ID);
965
- let commitResult = null;
966
- try {
967
- commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
968
- decision_id: thinkResult.decision_id,
969
- success: args.success ?? true,
970
- outcome,
963
+ if (toolName === 'marrow_run') {
964
+ // [FIX #9] Validate required params
965
+ const description = requireString(args, 'description');
966
+ const outcome = requireString(args, 'outcome');
967
+ // [FIX #16] Handle partial failures — return think result even if commit fails
968
+ let thinkResult = null;
969
+ try {
970
+ await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
971
+ }
972
+ catch (err) {
973
+ const msg = err instanceof Error ? err.message : String(err);
974
+ process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
975
+ }
976
+ thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
977
+ action: description,
978
+ type: args.type || 'general',
971
979
  }, SESSION_ID);
972
- }
973
- catch (err) {
974
- const msg = err instanceof Error ? err.message : String(err);
975
- process.stderr.write(`[marrow] marrow_run commit failed: ${msg}\n`);
980
+ let commitResult = null;
981
+ try {
982
+ commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
983
+ decision_id: thinkResult.decision_id,
984
+ success: args.success ?? true,
985
+ outcome,
986
+ }, SESSION_ID);
987
+ }
988
+ catch (err) {
989
+ const msg = err instanceof Error ? err.message : String(err);
990
+ process.stderr.write(`[marrow] marrow_run commit failed: ${msg}\n`);
991
+ success(id, {
992
+ content: [{
993
+ type: 'text',
994
+ text: JSON.stringify({
995
+ think: thinkResult,
996
+ commit: null,
997
+ commit_error: msg,
998
+ decision_id: thinkResult.decision_id,
999
+ }, null, 2),
1000
+ }],
1001
+ });
1002
+ return;
1003
+ }
976
1004
  success(id, {
977
1005
  content: [{
978
1006
  type: 'text',
979
- text: JSON.stringify({
980
- think: thinkResult,
981
- commit: null,
982
- commit_error: msg,
983
- decision_id: thinkResult.decision_id,
984
- }, null, 2),
1007
+ text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
985
1008
  }],
986
1009
  });
987
1010
  return;
988
1011
  }
989
- success(id, {
990
- content: [{
991
- type: 'text',
992
- text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
993
- }],
994
- });
995
- return;
996
- }
997
- if (toolName === 'marrow_auto') {
998
- // [FIX #9] Validate required param
999
- const action = requireString(args, 'action');
1000
- const outcome = args.outcome;
1001
- const outcomeSuccess = args.success ?? true;
1002
- const type = args.type || 'general';
1003
- // [FIX #11] Cleanup pending decisions on each auto call
1004
- cleanupPending();
1005
- // [FIX #8] Include pending flag so agent knows logging is deferred
1006
- const response = {
1007
- action,
1008
- outcome: outcome || 'pending',
1009
- warnings: cachedOrientWarnings.map(formatWarningActionably),
1010
- logging: 'deferred',
1011
- };
1012
- // Fire-and-forget the actual API calls
1013
- (async () => {
1014
- try {
1015
- if (!outcome) {
1016
- await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1012
+ if (toolName === 'marrow_auto') {
1013
+ // [FIX #9] Validate required param
1014
+ const action = requireString(args, 'action');
1015
+ const outcome = args.outcome;
1016
+ const outcomeSuccess = args.success ?? true;
1017
+ const type = args.type || 'general';
1018
+ // [FIX #11] Cleanup pending decisions on each auto call
1019
+ cleanupPending();
1020
+ // [FIX #8] Include pending flag so agent knows logging is deferred
1021
+ const response = {
1022
+ action,
1023
+ outcome: outcome || 'pending',
1024
+ warnings: cachedOrientWarnings.map(formatWarningActionably),
1025
+ logging: 'deferred',
1026
+ };
1027
+ // Fire-and-forget the actual API calls
1028
+ (async () => {
1029
+ try {
1030
+ if (!outcome) {
1031
+ await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1032
+ }
1033
+ else {
1034
+ const thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1035
+ await (0, index_1.marrowCommit)(API_KEY, BASE_URL, { decision_id: thinkResult.decision_id, success: outcomeSuccess, outcome }, SESSION_ID);
1036
+ }
1017
1037
  }
1018
- else {
1019
- const thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1020
- await (0, index_1.marrowCommit)(API_KEY, BASE_URL, { decision_id: thinkResult.decision_id, success: outcomeSuccess, outcome }, SESSION_ID);
1038
+ catch (err) {
1039
+ const msg = err instanceof Error ? err.message : String(err);
1040
+ process.stderr.write(`[marrow] marrow_auto background logging failed: ${msg}\n`);
1021
1041
  }
1042
+ })();
1043
+ success(id, {
1044
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1045
+ });
1046
+ return;
1047
+ }
1048
+ if (toolName === 'marrow_ask') {
1049
+ const query = requireString(args, 'query');
1050
+ const result = await (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID);
1051
+ success(id, {
1052
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1053
+ });
1054
+ return;
1055
+ }
1056
+ if (toolName === 'marrow_status') {
1057
+ const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1058
+ success(id, {
1059
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1060
+ });
1061
+ return;
1062
+ }
1063
+ // Memory control tools — all use requireString for id validation
1064
+ if (toolName === 'marrow_list_memories') {
1065
+ const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
1066
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1067
+ return;
1068
+ }
1069
+ if (toolName === 'marrow_get_memory') {
1070
+ const memId = requireString(args, 'id');
1071
+ const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
1072
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1073
+ return;
1074
+ }
1075
+ if (toolName === 'marrow_update_memory') {
1076
+ const memId = requireString(args, 'id');
1077
+ const result = await marrowUpdateMemory(API_KEY, BASE_URL, memId, { text: args.text, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
1078
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1079
+ return;
1080
+ }
1081
+ if (toolName === 'marrow_delete_memory') {
1082
+ const memId = requireString(args, 'id');
1083
+ const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
1084
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1085
+ return;
1086
+ }
1087
+ if (toolName === 'marrow_mark_outdated') {
1088
+ const memId = requireString(args, 'id');
1089
+ const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
1090
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1091
+ return;
1092
+ }
1093
+ if (toolName === 'marrow_supersede_memory') {
1094
+ const memId = requireString(args, 'id');
1095
+ const newText = requireString(args, 'text');
1096
+ const result = await marrowSupersedeMemory(API_KEY, BASE_URL, memId, { text: newText, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
1097
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1098
+ return;
1099
+ }
1100
+ if (toolName === 'marrow_share_memory') {
1101
+ const memId = requireString(args, 'id');
1102
+ const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
1103
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1104
+ return;
1105
+ }
1106
+ if (toolName === 'marrow_export_memories') {
1107
+ const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
1108
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1109
+ return;
1110
+ }
1111
+ if (toolName === 'marrow_import_memories') {
1112
+ const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
1113
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1114
+ return;
1115
+ }
1116
+ if (toolName === 'marrow_retrieve_memories') {
1117
+ const query = requireString(args, 'query');
1118
+ const result = await marrowRetrieveMemories(API_KEY, BASE_URL, query, { limit: args.limit, from: args.from, to: args.to, tags: args.tags, source: args.source, status: args.status, shared: args.shared }, SESSION_ID);
1119
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1120
+ return;
1121
+ }
1122
+ if (toolName === 'marrow_workflow') {
1123
+ const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
1124
+ action: args.action,
1125
+ workflowId: args.workflowId,
1126
+ instanceId: args.instanceId,
1127
+ name: args.name,
1128
+ description: args.description,
1129
+ steps: args.steps,
1130
+ tags: args.tags,
1131
+ agentId: args.agentId,
1132
+ context: args.context,
1133
+ inputs: args.inputs,
1134
+ stepCompleted: args.stepCompleted,
1135
+ outcome: args.outcome,
1136
+ nextAgentId: args.nextAgentId,
1137
+ contextUpdate: args.contextUpdate,
1138
+ status: args.status,
1139
+ }, SESSION_ID, FLEET_AGENT_ID);
1140
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1141
+ return;
1142
+ }
1143
+ if (toolName === 'marrow_dashboard') {
1144
+ const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1145
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1146
+ return;
1147
+ }
1148
+ if (toolName === 'marrow_digest') {
1149
+ const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
1150
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1151
+ return;
1152
+ }
1153
+ if (toolName === 'marrow_session_end') {
1154
+ const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, Boolean(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
1155
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1156
+ return;
1157
+ }
1158
+ if (toolName === 'marrow_accept_detected') {
1159
+ const detectedId = args.detectedId;
1160
+ if (!detectedId) {
1161
+ error(id, -32602, 'detectedId is required');
1162
+ return;
1022
1163
  }
1023
- catch (err) {
1024
- const msg = err instanceof Error ? err.message : String(err);
1025
- process.stderr.write(`[marrow] marrow_auto background logging failed: ${msg}\n`);
1026
- }
1027
- })();
1028
- success(id, {
1029
- content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1030
- });
1031
- return;
1032
- }
1033
- if (toolName === 'marrow_ask') {
1034
- const query = requireString(args, 'query');
1035
- const result = await (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID);
1036
- success(id, {
1037
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1038
- });
1039
- return;
1040
- }
1041
- if (toolName === 'marrow_status') {
1042
- const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1043
- success(id, {
1044
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1045
- });
1046
- return;
1047
- }
1048
- // Memory control tools — all use requireString for id validation
1049
- if (toolName === 'marrow_list_memories') {
1050
- const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
1051
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1052
- return;
1053
- }
1054
- if (toolName === 'marrow_get_memory') {
1055
- const memId = requireString(args, 'id');
1056
- const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
1057
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1058
- return;
1059
- }
1060
- if (toolName === 'marrow_update_memory') {
1061
- const memId = requireString(args, 'id');
1062
- const result = await marrowUpdateMemory(API_KEY, BASE_URL, memId, { text: args.text, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
1063
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1064
- return;
1065
- }
1066
- if (toolName === 'marrow_delete_memory') {
1067
- const memId = requireString(args, 'id');
1068
- const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
1069
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1070
- return;
1071
- }
1072
- if (toolName === 'marrow_mark_outdated') {
1073
- const memId = requireString(args, 'id');
1074
- const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
1075
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1076
- return;
1077
- }
1078
- if (toolName === 'marrow_supersede_memory') {
1079
- const memId = requireString(args, 'id');
1080
- const newText = requireString(args, 'text');
1081
- const result = await marrowSupersedeMemory(API_KEY, BASE_URL, memId, { text: newText, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
1082
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1083
- return;
1084
- }
1085
- if (toolName === 'marrow_share_memory') {
1086
- const memId = requireString(args, 'id');
1087
- const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
1088
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1089
- return;
1090
- }
1091
- if (toolName === 'marrow_export_memories') {
1092
- const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
1093
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1094
- return;
1095
- }
1096
- if (toolName === 'marrow_import_memories') {
1097
- const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
1098
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1099
- return;
1100
- }
1101
- if (toolName === 'marrow_retrieve_memories') {
1102
- const query = requireString(args, 'query');
1103
- const result = await marrowRetrieveMemories(API_KEY, BASE_URL, query, { limit: args.limit, from: args.from, to: args.to, tags: args.tags, source: args.source, status: args.status, shared: args.shared }, SESSION_ID);
1104
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1105
- return;
1106
- }
1107
- if (toolName === 'marrow_workflow') {
1108
- const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
1109
- action: args.action,
1110
- workflowId: args.workflowId,
1111
- instanceId: args.instanceId,
1112
- name: args.name,
1113
- description: args.description,
1114
- steps: args.steps,
1115
- tags: args.tags,
1116
- agentId: args.agentId,
1117
- context: args.context,
1118
- inputs: args.inputs,
1119
- stepCompleted: args.stepCompleted,
1120
- outcome: args.outcome,
1121
- nextAgentId: args.nextAgentId,
1122
- contextUpdate: args.contextUpdate,
1123
- status: args.status,
1124
- }, SESSION_ID, FLEET_AGENT_ID);
1125
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1126
- return;
1127
- }
1128
- if (toolName === 'marrow_dashboard') {
1129
- const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1130
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1131
- return;
1132
- }
1133
- if (toolName === 'marrow_digest') {
1134
- const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
1135
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1136
- return;
1137
- }
1138
- if (toolName === 'marrow_session_end') {
1139
- const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, Boolean(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
1140
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1141
- return;
1142
- }
1143
- if (toolName === 'marrow_accept_detected') {
1144
- const detectedId = args.detectedId;
1145
- if (!detectedId) {
1146
- error(id, -32602, 'detectedId is required');
1164
+ const result = await (0, index_1.marrowAcceptDetected)(API_KEY, BASE_URL, detectedId, SESSION_ID, FLEET_AGENT_ID);
1165
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1147
1166
  return;
1148
1167
  }
1149
- const result = await (0, index_1.marrowAcceptDetected)(API_KEY, BASE_URL, detectedId, SESSION_ID, FLEET_AGENT_ID);
1150
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1151
- return;
1152
- }
1153
- if (toolName === 'marrow_list_templates') {
1154
- const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
1155
- industry: args.industry,
1156
- category: args.category,
1157
- limit: args.limit,
1158
- }, SESSION_ID, FLEET_AGENT_ID);
1159
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1160
- return;
1161
- }
1162
- if (toolName === 'marrow_install_template') {
1163
- const slug = args.slug;
1164
- if (!slug) {
1165
- error(id, -32602, 'slug is required');
1168
+ if (toolName === 'marrow_list_templates') {
1169
+ const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
1170
+ industry: args.industry,
1171
+ category: args.category,
1172
+ limit: args.limit,
1173
+ }, SESSION_ID, FLEET_AGENT_ID);
1174
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1166
1175
  return;
1167
1176
  }
1168
- const result = await (0, index_1.marrowInstallTemplate)(API_KEY, BASE_URL, slug, SESSION_ID, FLEET_AGENT_ID);
1169
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1177
+ if (toolName === 'marrow_install_template') {
1178
+ const slug = args.slug;
1179
+ if (!slug) {
1180
+ error(id, -32602, 'slug is required');
1181
+ return;
1182
+ }
1183
+ const result = await (0, index_1.marrowInstallTemplate)(API_KEY, BASE_URL, slug, SESSION_ID, FLEET_AGENT_ID);
1184
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1185
+ return;
1186
+ }
1187
+ error(id, -32601, `Method not found: ${toolName}`);
1170
1188
  return;
1171
1189
  }
1172
- error(id, -32601, `Method not found: ${toolName}`);
1173
- return;
1190
+ error(id, -32601, `Method not found: ${method}`);
1174
1191
  }
1175
- error(id, -32601, `Method not found: ${method}`);
1176
- }
1177
- catch (err) {
1178
- const message = err instanceof Error ? err.message : String(err);
1179
- error(id, -32000, message);
1180
- }
1181
- }
1182
- // MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
1183
- let buffer = '';
1184
- let pendingRequests = 0;
1185
- let stdinEnded = false;
1186
- function checkExit() {
1187
- if (stdinEnded && pendingRequests === 0) {
1188
- autoCommitOnClose().then(() => process.exit(0));
1189
- }
1190
- }
1191
- process.stdin.setEncoding('utf8');
1192
- process.stdin.on('data', (chunk) => {
1193
- buffer += chunk;
1194
- const lines = buffer.split('\n');
1195
- buffer = lines.pop() || ''; // keep incomplete line in buffer
1196
- for (const line of lines) {
1197
- const trimmed = line.trim();
1198
- if (!trimmed)
1199
- continue;
1200
- // [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
1201
- let msg;
1202
- try {
1203
- msg = JSON.parse(trimmed);
1192
+ catch (err) {
1193
+ const message = err instanceof Error ? err.message : String(err);
1194
+ error(id, -32000, message);
1204
1195
  }
1205
- catch (parseErr) {
1206
- process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
1207
- send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
1208
- continue;
1196
+ }
1197
+ // MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
1198
+ let buffer = '';
1199
+ let pendingRequests = 0;
1200
+ let stdinEnded = false;
1201
+ function checkExit() {
1202
+ if (stdinEnded && pendingRequests === 0) {
1203
+ autoCommitOnClose().then(() => process.exit(0));
1209
1204
  }
1210
- // MCP notifications (no id) must be silently ignored per spec
1211
- if (msg.id === undefined || msg.id === null)
1212
- continue;
1213
- pendingRequests++;
1214
- handleRequest(msg)
1215
- .catch((err) => {
1216
- process.stderr.write(`[marrow] Handler error: ${err}\n`);
1217
- })
1218
- .finally(() => {
1219
- pendingRequests--;
1220
- checkExit();
1221
- });
1222
1205
  }
1223
- });
1224
- process.stdin.on('end', () => {
1225
- stdinEnded = true;
1226
- if (buffer.trim()) {
1227
- let msg;
1228
- try {
1229
- msg = JSON.parse(buffer.trim());
1206
+ process.stdin.setEncoding('utf8');
1207
+ process.stdin.on('data', (chunk) => {
1208
+ buffer += chunk;
1209
+ const lines = buffer.split('\n');
1210
+ buffer = lines.pop() || ''; // keep incomplete line in buffer
1211
+ for (const line of lines) {
1212
+ const trimmed = line.trim();
1213
+ if (!trimmed)
1214
+ continue;
1215
+ // [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
1216
+ let msg;
1217
+ try {
1218
+ msg = JSON.parse(trimmed);
1219
+ }
1220
+ catch (parseErr) {
1221
+ process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
1222
+ send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
1223
+ continue;
1224
+ }
1225
+ // MCP notifications (no id) must be silently ignored per spec
1226
+ if (msg.id === undefined || msg.id === null)
1227
+ continue;
1228
+ pendingRequests++;
1229
+ handleRequest(msg)
1230
+ .catch((err) => {
1231
+ process.stderr.write(`[marrow] Handler error: ${err}\n`);
1232
+ })
1233
+ .finally(() => {
1234
+ pendingRequests--;
1235
+ checkExit();
1236
+ });
1230
1237
  }
1231
- catch (err) {
1232
- process.stderr.write(`[marrow] JSON parse error on remaining buffer: ${err}\n`);
1233
- checkExit();
1234
- return;
1238
+ });
1239
+ process.stdin.on('end', () => {
1240
+ stdinEnded = true;
1241
+ if (buffer.trim()) {
1242
+ let msg;
1243
+ try {
1244
+ msg = JSON.parse(buffer.trim());
1245
+ }
1246
+ catch (err) {
1247
+ process.stderr.write(`[marrow] JSON parse error on remaining buffer: ${err}\n`);
1248
+ checkExit();
1249
+ return;
1250
+ }
1251
+ if (msg.id === undefined || msg.id === null) {
1252
+ checkExit();
1253
+ return;
1254
+ }
1255
+ pendingRequests++;
1256
+ handleRequest(msg)
1257
+ .catch((err) => {
1258
+ process.stderr.write(`[marrow] Handler error on remaining: ${err}\n`);
1259
+ })
1260
+ .finally(() => {
1261
+ pendingRequests--;
1262
+ checkExit();
1263
+ });
1235
1264
  }
1236
- if (msg.id === undefined || msg.id === null) {
1265
+ else {
1237
1266
  checkExit();
1238
- return;
1239
1267
  }
1240
- pendingRequests++;
1241
- handleRequest(msg)
1242
- .catch((err) => {
1243
- process.stderr.write(`[marrow] Handler error on remaining: ${err}\n`);
1244
- })
1245
- .finally(() => {
1246
- pendingRequests--;
1247
- checkExit();
1248
- });
1249
- }
1250
- else {
1251
- checkExit();
1252
- }
1253
- });
1254
- process.stdin.on('error', (err) => {
1255
- process.stderr.write(`[marrow] stdin error: ${err}\n`);
1256
- process.exit(1);
1257
- });
1268
+ });
1269
+ process.stdin.on('error', (err) => {
1270
+ process.stderr.write(`[marrow] stdin error: ${err}\n`);
1271
+ process.exit(1);
1272
+ });
1273
+ }
1258
1274
  //# sourceMappingURL=cli.js.map