@getmarrow/mcp 3.8.3 → 3.9.1

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