@j0hanz/cortex-mcp 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/engine/config.d.ts +4 -4
  2. package/dist/engine/config.js +6 -4
  3. package/dist/engine/context.d.ts +0 -1
  4. package/dist/engine/context.js +0 -3
  5. package/dist/engine/events.d.ts +1 -1
  6. package/dist/engine/events.js +2 -3
  7. package/dist/engine/heuristics.d.ts +4 -0
  8. package/dist/engine/heuristics.js +65 -0
  9. package/dist/engine/reasoner.d.ts +1 -1
  10. package/dist/engine/reasoner.js +22 -98
  11. package/dist/engine/session-store.d.ts +7 -1
  12. package/dist/engine/session-store.js +68 -27
  13. package/dist/lib/concurrency.d.ts +5 -0
  14. package/dist/lib/concurrency.js +17 -0
  15. package/dist/lib/errors.d.ts +23 -7
  16. package/dist/lib/errors.js +47 -5
  17. package/dist/lib/formatting.d.ts +13 -0
  18. package/dist/lib/formatting.js +18 -0
  19. package/dist/lib/prompt-contracts.d.ts +1 -9
  20. package/dist/lib/prompt-contracts.js +33 -122
  21. package/dist/lib/tool-contracts.d.ts +1 -1
  22. package/dist/lib/tool-contracts.js +85 -90
  23. package/dist/lib/tool-response.d.ts +4 -0
  24. package/dist/lib/tool-response.js +3 -0
  25. package/dist/lib/types.d.ts +17 -0
  26. package/dist/lib/types.js +8 -1
  27. package/dist/lib/validators.d.ts +12 -1
  28. package/dist/lib/validators.js +48 -7
  29. package/dist/prompts/index.js +136 -47
  30. package/dist/prompts/templates.d.ts +2 -0
  31. package/dist/prompts/templates.js +227 -0
  32. package/dist/resources/index.js +34 -60
  33. package/dist/resources/instructions.js +55 -64
  34. package/dist/resources/tool-catalog.js +10 -9
  35. package/dist/resources/tool-info.js +1 -1
  36. package/dist/resources/workflows.js +43 -42
  37. package/dist/schemas/inputs.d.ts +0 -1
  38. package/dist/schemas/inputs.js +14 -31
  39. package/dist/schemas/outputs.d.ts +29 -25
  40. package/dist/schemas/outputs.js +18 -22
  41. package/dist/server.js +11 -3
  42. package/dist/tools/reasoning-think.js +142 -158
  43. package/package.json +1 -1
@@ -1,51 +1,38 @@
1
1
  import { getLevelDescriptionString } from '../engine/config.js';
2
2
  import { reason, sessionStore } from '../engine/reasoner.js';
3
3
  import { ReasoningThinkInputSchema, } from '../schemas/inputs.js';
4
- import { ReasoningThinkToolOutputSchema } from '../schemas/outputs.js';
5
- import { createErrorResponse, getErrorMessage } from '../lib/errors.js';
6
- import { formatThoughtsToMarkdown } from '../lib/formatting.js';
7
- import { createToolResponse } from '../lib/tool-response.js';
4
+ import { ReasoningThinkToolOutputSchema, } from '../schemas/outputs.js';
5
+ import { createTaskLimiter } from '../lib/concurrency.js';
6
+ import { createErrorResponse, getErrorMessage, InsufficientThoughtsError, InvalidRunModeArgsError, isObjectRecord, ReasoningAbortedError, ReasoningError, ServerBusyError, SessionNotFoundError, } from '../lib/errors.js';
7
+ import { formatProgressMessage, formatThoughtsToMarkdown, } from '../lib/formatting.js';
8
+ import { createToolResponse, withIconMeta } from '../lib/tool-response.js';
9
+ import { parseBooleanEnv, parsePositiveIntEnv } from '../lib/validators.js';
8
10
  const DEFAULT_MAX_ACTIVE_REASONING_TASKS = 32;
9
- // Use explicit server busy error code for better client handling
10
- const TASK_OVERLOAD_MESSAGE = 'Server busy: too many active reasoning tasks';
11
+ const REDACTED_THOUGHT_CONTENT = '[REDACTED]';
12
+ function shouldRedactTraceContent() {
13
+ return parseBooleanEnv('CORTEX_REDACT_TRACE_CONTENT', false);
14
+ }
11
15
  function buildTraceResource(session) {
16
+ const sessionView = shouldRedactTraceContent()
17
+ ? {
18
+ ...session,
19
+ thoughts: session.thoughts.map((thought) => ({
20
+ index: thought.index,
21
+ content: REDACTED_THOUGHT_CONTENT,
22
+ revision: thought.revision,
23
+ ...(thought.stepSummary !== undefined
24
+ ? { stepSummary: REDACTED_THOUGHT_CONTENT }
25
+ : {}),
26
+ })),
27
+ }
28
+ : session;
12
29
  return {
13
30
  uri: `file:///cortex/sessions/${session.id}/trace.md`,
14
31
  mimeType: 'text/markdown',
15
- text: formatThoughtsToMarkdown(session),
32
+ text: formatThoughtsToMarkdown(sessionView),
16
33
  };
17
34
  }
18
- function parsePositiveInt(rawValue, fallbackValue) {
19
- if (rawValue === undefined) {
20
- return fallbackValue;
21
- }
22
- const parsed = Number.parseInt(rawValue, 10);
23
- if (!Number.isInteger(parsed) || parsed < 1) {
24
- return fallbackValue;
25
- }
26
- return parsed;
27
- }
28
- function createTaskLimiter(maxActiveTasks) {
29
- let activeTasks = 0;
30
- return {
31
- tryAcquire() {
32
- if (activeTasks >= maxActiveTasks) {
33
- return false;
34
- }
35
- activeTasks += 1;
36
- return true;
37
- },
38
- release() {
39
- if (activeTasks > 0) {
40
- activeTasks -= 1;
41
- }
42
- },
43
- };
44
- }
45
- const reasoningTaskLimiter = createTaskLimiter(parsePositiveInt(process.env.CORTEX_MAX_ACTIVE_REASONING_TASKS, DEFAULT_MAX_ACTIVE_REASONING_TASKS));
46
- function isObjectRecord(value) {
47
- return typeof value === 'object' && value !== null;
48
- }
35
+ const reasoningTaskLimiter = createTaskLimiter(parsePositiveIntEnv('CORTEX_MAX_ACTIVE_REASONING_TASKS', DEFAULT_MAX_ACTIVE_REASONING_TASKS));
49
36
  function isTaskStoreLike(value) {
50
37
  if (!isObjectRecord(value)) {
51
38
  return false;
@@ -134,27 +121,11 @@ function assertCallToolResult(value) {
134
121
  throw new Error('Stored task result is not a valid CallToolResult.');
135
122
  }
136
123
  }
137
- function mapReasoningErrorCode(message) {
138
- switch (true) {
139
- case message === 'Reasoning aborted':
140
- case message === 'Reasoning task cancelled':
141
- return 'E_ABORTED';
142
- case message.startsWith('targetThoughts must be'):
143
- case message.startsWith('Cannot change targetThoughts'):
144
- return 'E_INVALID_THOUGHT_COUNT';
145
- case message.startsWith('Session not found:'):
146
- return 'E_SESSION_NOT_FOUND';
147
- case message.startsWith('Session level mismatch:'):
148
- return 'E_SESSION_LEVEL_MISMATCH';
149
- case message.startsWith('run_to_completion requires at least'):
150
- return 'E_INSUFFICIENT_THOUGHTS';
151
- case message.startsWith('targetThoughts is required for run_to_completion when sessionId is not provided'):
152
- return 'E_INVALID_RUN_MODE_ARGS';
153
- case message === TASK_OVERLOAD_MESSAGE:
154
- return 'E_SERVER_BUSY';
155
- default:
156
- return 'E_REASONING';
124
+ function getReasoningErrorCode(error) {
125
+ if (error instanceof ReasoningError) {
126
+ return error.code;
157
127
  }
128
+ return 'E_REASONING';
158
129
  }
159
130
  function shouldEmitProgress(progress, total, level) {
160
131
  if (progress <= 1 || progress >= total) {
@@ -167,8 +138,22 @@ function shouldEmitProgress(progress, total, level) {
167
138
  // Basic/Normal: emit every step
168
139
  return true;
169
140
  }
170
- function resolveRunMode(params) {
171
- return params.runMode ?? 'step';
141
+ async function notifyProgress(args) {
142
+ const { server, progressToken, progress, total, message } = args;
143
+ try {
144
+ await server.server.notification({
145
+ method: 'notifications/progress',
146
+ params: {
147
+ progressToken,
148
+ progress,
149
+ total,
150
+ message,
151
+ },
152
+ });
153
+ }
154
+ catch {
155
+ // Ignore notification errors
156
+ }
172
157
  }
173
158
  function buildThoughtInputs(params) {
174
159
  const primary = Array.isArray(params.thought)
@@ -176,7 +161,7 @@ function buildThoughtInputs(params) {
176
161
  : params.thought
177
162
  ? [params.thought]
178
163
  : [];
179
- return [...primary, ...(params.thoughts ?? [])];
164
+ return primary;
180
165
  }
181
166
  function getStartingThoughtCount(sessionId) {
182
167
  if (sessionId === undefined) {
@@ -203,6 +188,20 @@ async function executeReasoningSteps(args) {
203
188
  rollbackToStep !== undefined)) {
204
189
  maxSteps = 1;
205
190
  }
191
+ // Build first-step-only extras once, outside the loop.
192
+ const firstStepExtras = {
193
+ ...(observation !== undefined ? { observation } : {}),
194
+ ...(hypothesis !== undefined ? { hypothesis } : {}),
195
+ ...(evaluation !== undefined ? { evaluation } : {}),
196
+ ...(stepSummary !== undefined ? { stepSummary } : {}),
197
+ ...(isConclusion !== undefined ? { isConclusion } : {}),
198
+ ...(rollbackToStep !== undefined ? { rollbackToStep } : {}),
199
+ };
200
+ const baseOptions = {
201
+ ...(targetThoughts !== undefined ? { targetThoughts } : {}),
202
+ abortSignal: controller.signal,
203
+ onProgress,
204
+ };
206
205
  for (let index = 0; index < maxSteps; index++) {
207
206
  await ensureTaskIsActive(taskStore, taskId, controller);
208
207
  const inputThought = thoughtInputs[index];
@@ -217,30 +216,11 @@ async function executeReasoningSteps(args) {
217
216
  break;
218
217
  }
219
218
  const reasonOptions = {
219
+ ...baseOptions,
220
220
  ...(inputThought !== undefined ? { thought: inputThought } : {}),
221
- abortSignal: controller.signal,
222
- onProgress,
221
+ ...(activeSessionId !== undefined ? { sessionId: activeSessionId } : {}),
222
+ ...(index === 0 ? firstStepExtras : {}),
223
223
  };
224
- if (index === 0) {
225
- if (observation !== undefined)
226
- reasonOptions.observation = observation;
227
- if (hypothesis !== undefined)
228
- reasonOptions.hypothesis = hypothesis;
229
- if (evaluation !== undefined)
230
- reasonOptions.evaluation = evaluation;
231
- if (stepSummary !== undefined)
232
- reasonOptions.stepSummary = stepSummary;
233
- if (isConclusion !== undefined)
234
- reasonOptions.isConclusion = isConclusion;
235
- if (rollbackToStep !== undefined)
236
- reasonOptions.rollbackToStep = rollbackToStep;
237
- }
238
- if (activeSessionId !== undefined) {
239
- reasonOptions.sessionId = activeSessionId;
240
- }
241
- if (targetThoughts !== undefined) {
242
- reasonOptions.targetThoughts = targetThoughts;
243
- }
244
224
  session = await reason(queryText, level, reasonOptions);
245
225
  activeSessionId = session.id;
246
226
  if (shouldStopReasoningLoop(session, runMode)) {
@@ -263,7 +243,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
263
243
  sessionId: session.id,
264
244
  level: session.level,
265
245
  status: session.status,
266
- thoughts: session.thoughts,
246
+ thoughts: [...session.thoughts],
267
247
  generatedThoughts,
268
248
  requestedThoughts,
269
249
  totalThoughts: session.totalThoughts,
@@ -280,10 +260,10 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
280
260
  }
281
261
  function buildSummary(session, remainingThoughts) {
282
262
  if (session.status === 'completed') {
283
- return `Reasoning complete. ${String(session.thoughts.length)} thoughts produced at level "${session.level}". Session ${session.id}.`;
263
+ return `Reasoning complete ${String(session.thoughts.length)} thoughts at [${session.level}] level. Session ${session.id}.`;
284
264
  }
285
265
  if (session.status === 'cancelled') {
286
- return `Session cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
266
+ return `Reasoning cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
287
267
  }
288
268
  const recentSummaries = session.thoughts
289
269
  .filter((t) => t.stepSummary)
@@ -301,7 +281,7 @@ function buildSummary(session, remainingThoughts) {
301
281
  else if (progress < 0.7) {
302
282
  prompt = 'Formulate and critique hypotheses based on the facts.';
303
283
  }
304
- return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}", level: "${session.level}", thought: "<your next reasoning step>" }. ` +
284
+ return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}", thought: "<your next reasoning step>" }. ` +
305
285
  `${summaryText}Progress: ${String(session.thoughts.length)}/${String(session.totalThoughts)} thoughts, ${String(remainingThoughts)} remaining.`);
306
286
  }
307
287
  async function emitLog(server, level, data, sessionId) {
@@ -320,16 +300,22 @@ function createCancellationController(signal) {
320
300
  const controller = new AbortController();
321
301
  if (signal.aborted) {
322
302
  controller.abort();
323
- return controller;
303
+ return {
304
+ controller,
305
+ cleanup: () => {
306
+ // No listener to clean up when already aborted.
307
+ },
308
+ };
324
309
  }
325
310
  const onAbort = () => {
326
311
  controller.abort();
327
312
  };
328
- signal.addEventListener('abort', onAbort, { once: true });
329
- controller.signal.addEventListener('abort', () => {
313
+ const cleanup = () => {
330
314
  signal.removeEventListener('abort', onAbort);
331
- }, { once: true });
332
- return controller;
315
+ };
316
+ signal.addEventListener('abort', onAbort, { once: true });
317
+ controller.signal.addEventListener('abort', cleanup, { once: true });
318
+ return { controller, cleanup };
333
319
  }
334
320
  async function isTaskCancelled(taskStore, taskId) {
335
321
  try {
@@ -343,12 +329,12 @@ async function isTaskCancelled(taskStore, taskId) {
343
329
  async function ensureTaskIsActive(taskStore, taskId, controller) {
344
330
  if (await isTaskCancelled(taskStore, taskId)) {
345
331
  controller.abort();
346
- throw new Error('Reasoning task cancelled');
332
+ throw new ReasoningAbortedError('Reasoning task cancelled');
347
333
  }
348
334
  }
349
335
  function createProgressHandler(args) {
350
336
  const { server, taskStore, taskId, level, progressToken, controller, startingCount, batchTotal, } = args;
351
- return async (progress, total) => {
337
+ return async (progress, _total, summary) => {
352
338
  await ensureTaskIsActive(taskStore, taskId, controller);
353
339
  if (progressToken === undefined) {
354
340
  return;
@@ -359,26 +345,25 @@ function createProgressHandler(args) {
359
345
  const isTerminal = displayProgress >= batchTotal;
360
346
  // We must emit if it's the terminal update for this batch,
361
347
  // otherwise we respect the session-level skipping rules.
362
- if (!isTerminal && !shouldEmitProgress(progress, total, level)) {
348
+ // If a summary is provided, we force an emit to show the meaningful update.
349
+ if (!isTerminal &&
350
+ !summary &&
351
+ !shouldEmitProgress(displayProgress, batchTotal, level)) {
363
352
  return;
364
353
  }
365
- const message = isTerminal
366
- ? `.𖦹°‧ reasoning_think: [${String(total)}/${String(total)}] • done`
367
- : `.𖦹°‧ reasoning_think: [${String(progress)}/${String(total)}]`;
368
- try {
369
- await server.server.notification({
370
- method: 'notifications/progress',
371
- params: {
372
- progressToken,
373
- progress: displayProgress,
374
- total: batchTotal,
375
- message,
376
- },
377
- });
378
- }
379
- catch {
380
- // Ignore notification errors
381
- }
354
+ const message = formatProgressMessage({
355
+ toolName: `꩜ ${TOOL_NAME}`,
356
+ context: 'Thought',
357
+ metadata: `[${String(displayProgress)}/${String(batchTotal)}]${summary ? ` ${summary}` : ''}`,
358
+ ...(isTerminal ? { outcome: 'complete' } : {}),
359
+ });
360
+ await notifyProgress({
361
+ server,
362
+ progressToken,
363
+ progress: displayProgress,
364
+ total: batchTotal,
365
+ message,
366
+ });
382
367
  };
383
368
  }
384
369
  async function storeTaskFailure(taskStore, taskId, response) {
@@ -410,31 +395,29 @@ async function notifyTaskStatus(server, taskId, status) {
410
395
  }
411
396
  function assertRunToCompletionInputCount(params, thoughtInputs) {
412
397
  const { sessionId, targetThoughts } = params;
413
- if (sessionId === undefined && targetThoughts === undefined) {
414
- throw new Error('targetThoughts is required for run_to_completion when sessionId is not provided');
398
+ if (!sessionId && !targetThoughts) {
399
+ throw new InvalidRunModeArgsError('targetThoughts is required for run_to_completion when sessionId is not provided');
415
400
  }
416
401
  let requiredInputs = targetThoughts ?? 0;
417
- if (sessionId !== undefined) {
402
+ if (sessionId) {
418
403
  const existing = sessionStore.get(sessionId);
419
404
  if (!existing) {
420
- throw new Error(`Session not found: ${sessionId}`);
405
+ throw new SessionNotFoundError(sessionId);
421
406
  }
422
407
  requiredInputs = Math.max(0, existing.totalThoughts - existing.thoughts.length);
423
408
  }
424
409
  if (thoughtInputs.length < requiredInputs) {
425
- throw new Error(`run_to_completion requires at least ${String(requiredInputs)} thought inputs; received ${String(thoughtInputs.length)}`);
410
+ throw new InsufficientThoughtsError(`run_to_completion requires at least ${String(requiredInputs)} thought inputs; received ${String(thoughtInputs.length)}`);
426
411
  }
427
412
  }
428
413
  function getActionableMessage(errorCode, originalMessage) {
429
414
  switch (errorCode) {
430
- case 'E_SESSION_LEVEL_MISMATCH':
431
- return `${originalMessage} ACTION REQUIRED: Re-call reasoning_think with the correct level matching the session.`;
432
415
  case 'E_INVALID_THOUGHT_COUNT':
433
- return `${originalMessage} ACTION REQUIRED: Ensure targetThoughts is within the level's range.`;
416
+ return `${originalMessage} Fix: set targetThoughts within the level range (basic 3–5, normal 6–10, high 15–25).`;
434
417
  case 'E_INSUFFICIENT_THOUGHTS':
435
- return `${originalMessage} ACTION REQUIRED: Provide enough thoughts for run_to_completion or switch to step mode.`;
418
+ return `${originalMessage} Fix: provide enough thought inputs for the remaining steps, or use runMode: "step".`;
436
419
  case 'E_INVALID_RUN_MODE_ARGS':
437
- return `${originalMessage} ACTION REQUIRED: Provide targetThoughts when starting a new session in run_to_completion mode.`;
420
+ return `${originalMessage} Fix: set targetThoughts when starting a new session with runMode: "run_to_completion".`;
438
421
  default:
439
422
  return originalMessage;
440
423
  }
@@ -442,10 +425,13 @@ function getActionableMessage(errorCode, originalMessage) {
442
425
  async function handleTaskFailure(args) {
443
426
  const { server, taskStore, taskId, sessionId, error } = args;
444
427
  const originalMessage = getErrorMessage(error);
445
- const errorCode = mapReasoningErrorCode(originalMessage);
428
+ const errorCode = getReasoningErrorCode(error);
446
429
  const message = getActionableMessage(errorCode, originalMessage);
447
430
  const response = createErrorResponse(errorCode, message);
448
431
  if (await isTaskCancelled(taskStore, taskId)) {
432
+ if (sessionId) {
433
+ sessionStore.markCancelled(sessionId);
434
+ }
449
435
  await emitLog(server, 'notice', { event: 'task_cancelled', taskId, reason: message }, sessionId);
450
436
  return;
451
437
  }
@@ -466,9 +452,10 @@ async function handleTaskFailure(args) {
466
452
  async function runReasoningTask(args) {
467
453
  const { server, taskStore, taskId, params, progressToken, controller, sessionId, } = args;
468
454
  const { query, level, targetThoughts } = params;
469
- const runMode = resolveRunMode(params);
455
+ const runMode = params.runMode ?? 'step';
470
456
  const thoughtInputs = buildThoughtInputs(params);
471
457
  const queryText = query ?? '';
458
+ let resolvedSessionId = params.sessionId ?? sessionId;
472
459
  await emitLog(server, 'info', {
473
460
  event: 'task_started',
474
461
  taskId,
@@ -477,7 +464,7 @@ async function runReasoningTask(args) {
477
464
  hasSessionId: params.sessionId !== undefined,
478
465
  targetThoughts: targetThoughts ?? null,
479
466
  thoughtInputs: thoughtInputs.length,
480
- }, sessionId);
467
+ }, resolvedSessionId);
481
468
  try {
482
469
  if (runMode === 'run_to_completion') {
483
470
  assertRunToCompletionInputCount(params, thoughtInputs);
@@ -492,23 +479,20 @@ async function runReasoningTask(args) {
492
479
  params.rollback_to_step !== undefined)) {
493
480
  batchTotal = 1;
494
481
  }
482
+ const normalizedBatchTotal = Math.max(1, batchTotal);
495
483
  if (progressToken !== undefined) {
496
- try {
497
- await server.server.notification({
498
- method: 'notifications/progress',
499
- params: {
500
- progressToken,
501
- progress: 0,
502
- total: Math.max(1, batchTotal),
503
- message: level !== undefined
504
- ? `reasoning_think: starting [${level}]`
505
- : 'reasoning_think: continuing session',
506
- },
507
- });
508
- }
509
- catch {
510
- // Ignore notification errors
511
- }
484
+ const message = formatProgressMessage({
485
+ toolName: `꩜ ${TOOL_NAME}`,
486
+ context: level ? 'starting' : 'continuing',
487
+ metadata: level ? `[${level}]` : 'session',
488
+ });
489
+ await notifyProgress({
490
+ server,
491
+ progressToken,
492
+ progress: 0,
493
+ total: normalizedBatchTotal,
494
+ message,
495
+ });
512
496
  }
513
497
  const progressArgs = {
514
498
  server,
@@ -517,7 +501,7 @@ async function runReasoningTask(args) {
517
501
  level,
518
502
  controller,
519
503
  startingCount,
520
- batchTotal: Math.max(1, batchTotal),
504
+ batchTotal: normalizedBatchTotal,
521
505
  };
522
506
  if (progressToken !== undefined) {
523
507
  progressArgs.progressToken = progressToken;
@@ -552,8 +536,10 @@ async function runReasoningTask(args) {
552
536
  if (params.rollback_to_step !== undefined)
553
537
  executeArgs.rollbackToStep = params.rollback_to_step;
554
538
  const session = await executeReasoningSteps(executeArgs);
539
+ resolvedSessionId = session.id;
555
540
  if (await isTaskCancelled(taskStore, taskId)) {
556
- await emitLog(server, 'notice', { event: 'task_cancelled_before_result', taskId }, sessionId);
541
+ sessionStore.markCancelled(resolvedSessionId);
542
+ await emitLog(server, 'notice', { event: 'task_cancelled_before_result', taskId }, resolvedSessionId);
557
543
  return;
558
544
  }
559
545
  const generatedThoughts = Math.max(0, session.thoughts.length - startingCount);
@@ -575,8 +561,8 @@ async function runReasoningTask(args) {
575
561
  taskId,
576
562
  error,
577
563
  };
578
- if (sessionId !== undefined) {
579
- failureArgs.sessionId = sessionId;
564
+ if (resolvedSessionId !== undefined) {
565
+ failureArgs.sessionId = resolvedSessionId;
580
566
  }
581
567
  await handleTaskFailure(failureArgs);
582
568
  }
@@ -588,9 +574,6 @@ function getTaskId(extra) {
588
574
  return extra.taskId;
589
575
  }
590
576
  const TOOL_NAME = 'reasoning_think';
591
- function withIconMeta(iconMeta) {
592
- return iconMeta ? { icons: [iconMeta] } : undefined;
593
- }
594
577
  export function registerReasoningThinkTool(server, iconMeta) {
595
578
  server.experimental.tasks.registerToolTask(TOOL_NAME, {
596
579
  title: 'Reasoning Think',
@@ -598,17 +581,17 @@ export function registerReasoningThinkTool(server, iconMeta) {
598
581
 
599
582
  USAGE PATTERN:
600
583
  1. Start: { query: "...", level: "basic"|"normal"|"high", thought: "your analysis..." }
601
- 2. Continue: { sessionId: "<from response>", level: "<same level>", thought: "next step..." }
602
- 3. Repeat step 2 until response shows status: "completed"
584
+ 2. Continue: { sessionId: "<from response>", thought: "next step..." } — level is optional; session level is used
585
+ 3. Repeat until status: "completed" — the summary field contains the exact next call to make
603
586
 
604
- IMPORTANT: You MUST pass the returned sessionId on every continuation call, and use the same level throughout.
587
+ IMPORTANT: Pass the returned sessionId on every continuation call.
605
588
  The thought parameter stores YOUR reasoning verbatim — write thorough analysis in each step.
606
- Use step_summary to record a 1-sentence conclusion per step — these accumulate in the summary field for context.
589
+ Use step_summary for a 1-sentence conclusion per step — these accumulate in the summary field for navigation.
607
590
 
608
591
  Levels: ${getLevelDescriptionString()}.
609
- Alternative: Use runMode="run_to_completion" with thought + thoughts[] to submit all steps in one call.
610
- Alternative input: Use observation/hypothesis/evaluation fields instead of thought for structured reasoning.
611
- Error recovery: If E_SESSION_NOT_FOUND, the session expired start a new session. If E_INVALID_THOUGHT_COUNT, check level ranges.`,
592
+ Alternatives: runMode="run_to_completion" (batch), or observation/hypothesis/evaluation fields (structured).
593
+ Errors: E_SESSION_NOT_FOUND (expired start new), E_INVALID_THOUGHT_COUNT (check level ranges).
594
+ Protocol validation: malformed task metadata/arguments fail at request level before task start; runtime reasoning failures return tool isError=true payloads.`,
612
595
  inputSchema: ReasoningThinkInputSchema,
613
596
  outputSchema: ReasoningThinkToolOutputSchema,
614
597
  annotations: {
@@ -632,7 +615,7 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
632
615
  const extra = parseReasoningTaskExtra(rawExtra);
633
616
  const progressToken = extra._meta?.progressToken;
634
617
  if (!reasoningTaskLimiter.tryAcquire()) {
635
- throw new Error(TASK_OVERLOAD_MESSAGE);
618
+ throw new ServerBusyError();
636
619
  }
637
620
  let task;
638
621
  try {
@@ -645,13 +628,13 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
645
628
  reasoningTaskLimiter.release();
646
629
  throw error;
647
630
  }
648
- const controller = createCancellationController(extra.signal);
631
+ const cancellation = createCancellationController(extra.signal);
649
632
  const runReasoningArgs = {
650
633
  server,
651
634
  taskStore: extra.taskStore,
652
635
  taskId: task.taskId,
653
636
  params,
654
- controller,
637
+ controller: cancellation.controller,
655
638
  };
656
639
  if (progressToken !== undefined) {
657
640
  runReasoningArgs.progressToken = progressToken;
@@ -660,6 +643,7 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
660
643
  runReasoningArgs.sessionId = extra.sessionId;
661
644
  }
662
645
  void runReasoningTask(runReasoningArgs).finally(() => {
646
+ cancellation.cleanup();
663
647
  reasoningTaskLimiter.release();
664
648
  });
665
649
  return { task };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/cortex-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "mcpName": "io.github.j0hanz/cortex-mcp",
5
5
  "author": "Johanz",
6
6
  "license": "MIT",