@j0hanz/cortex-mcp 1.4.0 → 1.5.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 (39) 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.js +21 -97
  10. package/dist/engine/session-store.d.ts +4 -0
  11. package/dist/engine/session-store.js +54 -26
  12. package/dist/lib/concurrency.d.ts +5 -0
  13. package/dist/lib/concurrency.js +17 -0
  14. package/dist/lib/errors.d.ts +23 -7
  15. package/dist/lib/errors.js +47 -5
  16. package/dist/lib/formatting.d.ts +13 -0
  17. package/dist/lib/formatting.js +18 -0
  18. package/dist/lib/prompt-contracts.d.ts +1 -9
  19. package/dist/lib/prompt-contracts.js +33 -122
  20. package/dist/lib/tool-contracts.d.ts +1 -1
  21. package/dist/lib/tool-contracts.js +91 -90
  22. package/dist/lib/tool-response.d.ts +4 -0
  23. package/dist/lib/tool-response.js +3 -0
  24. package/dist/lib/types.d.ts +17 -0
  25. package/dist/lib/types.js +8 -1
  26. package/dist/lib/validators.d.ts +7 -1
  27. package/dist/lib/validators.js +30 -7
  28. package/dist/prompts/index.js +132 -47
  29. package/dist/prompts/templates.d.ts +2 -0
  30. package/dist/prompts/templates.js +106 -0
  31. package/dist/resources/index.js +14 -60
  32. package/dist/resources/instructions.js +10 -2
  33. package/dist/resources/workflows.js +4 -3
  34. package/dist/schemas/inputs.js +2 -2
  35. package/dist/schemas/outputs.d.ts +29 -25
  36. package/dist/schemas/outputs.js +13 -13
  37. package/dist/server.js +1 -3
  38. package/dist/tools/reasoning-think.js +106 -149
  39. package/package.json +1 -1
@@ -1,12 +1,11 @@
1
1
  import { z } from 'zod';
2
+ import { REASONING_LEVELS, SESSION_STATUSES } from '../lib/types.js';
2
3
  const ErrorInfoSchema = z.strictObject({
3
4
  code: z.string(),
4
5
  message: z.string(),
5
6
  });
6
7
  const MISSING_RESULT_PATH = ['result'];
7
8
  const MISSING_ERROR_PATH = ['error'];
8
- const LEVEL_VALUES = ['basic', 'normal', 'high'];
9
- const STATUS_VALUES = ['active', 'completed', 'cancelled'];
10
9
  const ThoughtSchema = z.strictObject({
11
10
  index: z.number(),
12
11
  content: z.string(),
@@ -16,17 +15,12 @@ const ThoughtSchema = z.strictObject({
16
15
  .optional()
17
16
  .describe('A 1-sentence summary of the conclusion reached in this step, if provided.'),
18
17
  });
19
- export const DefaultOutputSchema = z.strictObject({
20
- ok: z.boolean(),
21
- result: z.unknown().optional(),
22
- error: ErrorInfoSchema.optional(),
23
- });
24
18
  const ReasoningThinkSuccessSchema = z.strictObject({
25
19
  ok: z.literal(true),
26
20
  result: z.strictObject({
27
21
  sessionId: z.string(),
28
- level: z.enum(LEVEL_VALUES),
29
- status: z.enum(STATUS_VALUES),
22
+ level: z.enum(REASONING_LEVELS),
23
+ status: z.enum(SESSION_STATUSES),
30
24
  thoughts: z.array(ThoughtSchema),
31
25
  generatedThoughts: z.number(),
32
26
  requestedThoughts: z.number(),
@@ -53,10 +47,6 @@ const ReasoningThinkErrorSchema = z.strictObject({
53
47
  ok: z.literal(false),
54
48
  error: ErrorInfoSchema,
55
49
  });
56
- export const ReasoningThinkResultSchema = z.discriminatedUnion('ok', [
57
- ReasoningThinkSuccessSchema,
58
- ReasoningThinkErrorSchema,
59
- ]);
60
50
  function getMissingFieldIssue(data) {
61
51
  if (data.ok && data.result === undefined) {
62
52
  return {
@@ -92,3 +82,13 @@ export const ReasoningThinkToolOutputSchema = z
92
82
  });
93
83
  }
94
84
  });
85
+ /** Generic ok/error envelope — useful for contract tests and external validators. */
86
+ export const DefaultOutputSchema = z.union([
87
+ z.strictObject({ ok: z.literal(true), result: z.unknown() }),
88
+ z.strictObject({ ok: z.literal(false), error: ErrorInfoSchema }),
89
+ ]);
90
+ /** Full discriminated union for the reasoning_think tool result. */
91
+ export const ReasoningThinkResultSchema = z.union([
92
+ ReasoningThinkSuccessSchema,
93
+ ReasoningThinkErrorSchema,
94
+ ]);
package/dist/server.js CHANGED
@@ -7,7 +7,6 @@ import { getErrorMessage } from './lib/errors.js';
7
7
  import { registerAllTools } from './tools/index.js';
8
8
  import { registerAllPrompts } from './prompts/index.js';
9
9
  import { registerAllResources } from './resources/index.js';
10
- import { buildServerInstructions } from './resources/instructions.js';
11
10
  const ICON_MIME = 'image/svg+xml';
12
11
  const SERVER_NAME = 'cortex-mcp';
13
12
  const SERVER_TITLE = 'Cortex MCP';
@@ -147,7 +146,6 @@ function installCloseCleanup(server, cleanup) {
147
146
  };
148
147
  }
149
148
  export function createServer() {
150
- const instructions = buildServerInstructions();
151
149
  const version = loadVersion();
152
150
  const taskStore = new InMemoryTaskStore();
153
151
  const localIcon = getLocalIconData();
@@ -180,7 +178,7 @@ export function createServer() {
180
178
  },
181
179
  },
182
180
  taskStore,
183
- ...(instructions ? { instructions } : {}),
181
+ instructions: 'Multi-level reasoning MCP server. Use reasoning_think to decompose queries into sequential thought steps at basic (3–5), normal (6–10), or high (15–25) depth. Full usage guide: read internal://instructions or invoke get-help.',
184
182
  });
185
183
  registerAllTools(server, iconMeta);
186
184
  registerAllPrompts(server, iconMeta);
@@ -1,13 +1,13 @@
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 { 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
11
  function buildTraceResource(session) {
12
12
  return {
13
13
  uri: `file:///cortex/sessions/${session.id}/trace.md`,
@@ -15,37 +15,7 @@ function buildTraceResource(session) {
15
15
  text: formatThoughtsToMarkdown(session),
16
16
  };
17
17
  }
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
- }
18
+ const reasoningTaskLimiter = createTaskLimiter(parsePositiveIntEnv('CORTEX_MAX_ACTIVE_REASONING_TASKS', DEFAULT_MAX_ACTIVE_REASONING_TASKS));
49
19
  function isTaskStoreLike(value) {
50
20
  if (!isObjectRecord(value)) {
51
21
  return false;
@@ -134,27 +104,11 @@ function assertCallToolResult(value) {
134
104
  throw new Error('Stored task result is not a valid CallToolResult.');
135
105
  }
136
106
  }
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';
107
+ function getReasoningErrorCode(error) {
108
+ if (error instanceof ReasoningError) {
109
+ return error.code;
157
110
  }
111
+ return 'E_REASONING';
158
112
  }
159
113
  function shouldEmitProgress(progress, total, level) {
160
114
  if (progress <= 1 || progress >= total) {
@@ -167,8 +121,22 @@ function shouldEmitProgress(progress, total, level) {
167
121
  // Basic/Normal: emit every step
168
122
  return true;
169
123
  }
170
- function resolveRunMode(params) {
171
- return params.runMode ?? 'step';
124
+ async function notifyProgress(args) {
125
+ const { server, progressToken, progress, total, message } = args;
126
+ try {
127
+ await server.server.notification({
128
+ method: 'notifications/progress',
129
+ params: {
130
+ progressToken,
131
+ progress,
132
+ total,
133
+ message,
134
+ },
135
+ });
136
+ }
137
+ catch {
138
+ // Ignore notification errors
139
+ }
172
140
  }
173
141
  function buildThoughtInputs(params) {
174
142
  const primary = Array.isArray(params.thought)
@@ -203,6 +171,20 @@ async function executeReasoningSteps(args) {
203
171
  rollbackToStep !== undefined)) {
204
172
  maxSteps = 1;
205
173
  }
174
+ // Build first-step-only extras once, outside the loop.
175
+ const firstStepExtras = {
176
+ ...(observation !== undefined ? { observation } : {}),
177
+ ...(hypothesis !== undefined ? { hypothesis } : {}),
178
+ ...(evaluation !== undefined ? { evaluation } : {}),
179
+ ...(stepSummary !== undefined ? { stepSummary } : {}),
180
+ ...(isConclusion !== undefined ? { isConclusion } : {}),
181
+ ...(rollbackToStep !== undefined ? { rollbackToStep } : {}),
182
+ };
183
+ const baseOptions = {
184
+ ...(targetThoughts !== undefined ? { targetThoughts } : {}),
185
+ abortSignal: controller.signal,
186
+ onProgress,
187
+ };
206
188
  for (let index = 0; index < maxSteps; index++) {
207
189
  await ensureTaskIsActive(taskStore, taskId, controller);
208
190
  const inputThought = thoughtInputs[index];
@@ -217,30 +199,11 @@ async function executeReasoningSteps(args) {
217
199
  break;
218
200
  }
219
201
  const reasonOptions = {
202
+ ...baseOptions,
220
203
  ...(inputThought !== undefined ? { thought: inputThought } : {}),
221
- abortSignal: controller.signal,
222
- onProgress,
204
+ ...(activeSessionId !== undefined ? { sessionId: activeSessionId } : {}),
205
+ ...(index === 0 ? firstStepExtras : {}),
223
206
  };
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
207
  session = await reason(queryText, level, reasonOptions);
245
208
  activeSessionId = session.id;
246
209
  if (shouldStopReasoningLoop(session, runMode)) {
@@ -263,7 +226,7 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
263
226
  sessionId: session.id,
264
227
  level: session.level,
265
228
  status: session.status,
266
- thoughts: session.thoughts,
229
+ thoughts: [...session.thoughts],
267
230
  generatedThoughts,
268
231
  requestedThoughts,
269
232
  totalThoughts: session.totalThoughts,
@@ -280,10 +243,10 @@ function buildStructuredResult(session, generatedThoughts, targetThoughts) {
280
243
  }
281
244
  function buildSummary(session, remainingThoughts) {
282
245
  if (session.status === 'completed') {
283
- return `Reasoning complete. ${String(session.thoughts.length)} thoughts produced at level "${session.level}". Session ${session.id}.`;
246
+ return `Reasoning complete ${String(session.thoughts.length)} thoughts at [${session.level}] level. Session ${session.id}.`;
284
247
  }
285
248
  if (session.status === 'cancelled') {
286
- return `Session cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
249
+ return `Reasoning cancelled at thought ${String(session.thoughts.length)}/${String(session.totalThoughts)}. Session ${session.id}.`;
287
250
  }
288
251
  const recentSummaries = session.thoughts
289
252
  .filter((t) => t.stepSummary)
@@ -301,7 +264,7 @@ function buildSummary(session, remainingThoughts) {
301
264
  else if (progress < 0.7) {
302
265
  prompt = 'Formulate and critique hypotheses based on the facts.';
303
266
  }
304
- return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}", level: "${session.level}", thought: "<your next reasoning step>" }. ` +
267
+ return (`CONTINUE: ${prompt} Call reasoning_think with { sessionId: "${session.id}", thought: "<your next reasoning step>" }. ` +
305
268
  `${summaryText}Progress: ${String(session.thoughts.length)}/${String(session.totalThoughts)} thoughts, ${String(remainingThoughts)} remaining.`);
306
269
  }
307
270
  async function emitLog(server, level, data, sessionId) {
@@ -343,12 +306,12 @@ async function isTaskCancelled(taskStore, taskId) {
343
306
  async function ensureTaskIsActive(taskStore, taskId, controller) {
344
307
  if (await isTaskCancelled(taskStore, taskId)) {
345
308
  controller.abort();
346
- throw new Error('Reasoning task cancelled');
309
+ throw new ReasoningAbortedError('Reasoning task cancelled');
347
310
  }
348
311
  }
349
312
  function createProgressHandler(args) {
350
313
  const { server, taskStore, taskId, level, progressToken, controller, startingCount, batchTotal, } = args;
351
- return async (progress, total) => {
314
+ return async (progress) => {
352
315
  await ensureTaskIsActive(taskStore, taskId, controller);
353
316
  if (progressToken === undefined) {
354
317
  return;
@@ -359,26 +322,23 @@ function createProgressHandler(args) {
359
322
  const isTerminal = displayProgress >= batchTotal;
360
323
  // We must emit if it's the terminal update for this batch,
361
324
  // otherwise we respect the session-level skipping rules.
362
- if (!isTerminal && !shouldEmitProgress(progress, total, level)) {
325
+ if (!isTerminal &&
326
+ !shouldEmitProgress(displayProgress, batchTotal, level)) {
363
327
  return;
364
328
  }
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
- }
329
+ const message = formatProgressMessage({
330
+ toolName: TOOL_NAME,
331
+ context: 'Thought',
332
+ metadata: `[${String(displayProgress)}/${String(batchTotal)}]`,
333
+ ...(isTerminal ? { outcome: 'complete' } : {}),
334
+ });
335
+ await notifyProgress({
336
+ server,
337
+ progressToken,
338
+ progress: displayProgress,
339
+ total: batchTotal,
340
+ message,
341
+ });
382
342
  };
383
343
  }
384
344
  async function storeTaskFailure(taskStore, taskId, response) {
@@ -410,31 +370,29 @@ async function notifyTaskStatus(server, taskId, status) {
410
370
  }
411
371
  function assertRunToCompletionInputCount(params, thoughtInputs) {
412
372
  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');
373
+ if (!sessionId && !targetThoughts) {
374
+ throw new InvalidRunModeArgsError('targetThoughts is required for run_to_completion when sessionId is not provided');
415
375
  }
416
376
  let requiredInputs = targetThoughts ?? 0;
417
- if (sessionId !== undefined) {
377
+ if (sessionId) {
418
378
  const existing = sessionStore.get(sessionId);
419
379
  if (!existing) {
420
- throw new Error(`Session not found: ${sessionId}`);
380
+ throw new SessionNotFoundError(sessionId);
421
381
  }
422
382
  requiredInputs = Math.max(0, existing.totalThoughts - existing.thoughts.length);
423
383
  }
424
384
  if (thoughtInputs.length < requiredInputs) {
425
- throw new Error(`run_to_completion requires at least ${String(requiredInputs)} thought inputs; received ${String(thoughtInputs.length)}`);
385
+ throw new InsufficientThoughtsError(`run_to_completion requires at least ${String(requiredInputs)} thought inputs; received ${String(thoughtInputs.length)}`);
426
386
  }
427
387
  }
428
388
  function getActionableMessage(errorCode, originalMessage) {
429
389
  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
390
  case 'E_INVALID_THOUGHT_COUNT':
433
- return `${originalMessage} ACTION REQUIRED: Ensure targetThoughts is within the level's range.`;
391
+ return `${originalMessage} Fix: set targetThoughts within the level range (basic 3–5, normal 6–10, high 15–25).`;
434
392
  case 'E_INSUFFICIENT_THOUGHTS':
435
- return `${originalMessage} ACTION REQUIRED: Provide enough thoughts for run_to_completion or switch to step mode.`;
393
+ return `${originalMessage} Fix: provide enough thought inputs for the remaining steps, or use runMode: "step".`;
436
394
  case 'E_INVALID_RUN_MODE_ARGS':
437
- return `${originalMessage} ACTION REQUIRED: Provide targetThoughts when starting a new session in run_to_completion mode.`;
395
+ return `${originalMessage} Fix: set targetThoughts when starting a new session with runMode: "run_to_completion".`;
438
396
  default:
439
397
  return originalMessage;
440
398
  }
@@ -442,10 +400,13 @@ function getActionableMessage(errorCode, originalMessage) {
442
400
  async function handleTaskFailure(args) {
443
401
  const { server, taskStore, taskId, sessionId, error } = args;
444
402
  const originalMessage = getErrorMessage(error);
445
- const errorCode = mapReasoningErrorCode(originalMessage);
403
+ const errorCode = getReasoningErrorCode(error);
446
404
  const message = getActionableMessage(errorCode, originalMessage);
447
405
  const response = createErrorResponse(errorCode, message);
448
406
  if (await isTaskCancelled(taskStore, taskId)) {
407
+ if (sessionId) {
408
+ sessionStore.markCancelled(sessionId);
409
+ }
449
410
  await emitLog(server, 'notice', { event: 'task_cancelled', taskId, reason: message }, sessionId);
450
411
  return;
451
412
  }
@@ -466,9 +427,10 @@ async function handleTaskFailure(args) {
466
427
  async function runReasoningTask(args) {
467
428
  const { server, taskStore, taskId, params, progressToken, controller, sessionId, } = args;
468
429
  const { query, level, targetThoughts } = params;
469
- const runMode = resolveRunMode(params);
430
+ const runMode = params.runMode ?? 'step';
470
431
  const thoughtInputs = buildThoughtInputs(params);
471
432
  const queryText = query ?? '';
433
+ let resolvedSessionId = params.sessionId ?? sessionId;
472
434
  await emitLog(server, 'info', {
473
435
  event: 'task_started',
474
436
  taskId,
@@ -477,7 +439,7 @@ async function runReasoningTask(args) {
477
439
  hasSessionId: params.sessionId !== undefined,
478
440
  targetThoughts: targetThoughts ?? null,
479
441
  thoughtInputs: thoughtInputs.length,
480
- }, sessionId);
442
+ }, resolvedSessionId);
481
443
  try {
482
444
  if (runMode === 'run_to_completion') {
483
445
  assertRunToCompletionInputCount(params, thoughtInputs);
@@ -492,23 +454,20 @@ async function runReasoningTask(args) {
492
454
  params.rollback_to_step !== undefined)) {
493
455
  batchTotal = 1;
494
456
  }
457
+ const normalizedBatchTotal = Math.max(1, batchTotal);
495
458
  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
- }
459
+ const message = formatProgressMessage({
460
+ toolName: TOOL_NAME,
461
+ context: 'reasoning',
462
+ metadata: level ? `starting [${level}]` : 'continuing session',
463
+ });
464
+ await notifyProgress({
465
+ server,
466
+ progressToken,
467
+ progress: 0,
468
+ total: normalizedBatchTotal,
469
+ message,
470
+ });
512
471
  }
513
472
  const progressArgs = {
514
473
  server,
@@ -517,7 +476,7 @@ async function runReasoningTask(args) {
517
476
  level,
518
477
  controller,
519
478
  startingCount,
520
- batchTotal: Math.max(1, batchTotal),
479
+ batchTotal: normalizedBatchTotal,
521
480
  };
522
481
  if (progressToken !== undefined) {
523
482
  progressArgs.progressToken = progressToken;
@@ -552,8 +511,10 @@ async function runReasoningTask(args) {
552
511
  if (params.rollback_to_step !== undefined)
553
512
  executeArgs.rollbackToStep = params.rollback_to_step;
554
513
  const session = await executeReasoningSteps(executeArgs);
514
+ resolvedSessionId = session.id;
555
515
  if (await isTaskCancelled(taskStore, taskId)) {
556
- await emitLog(server, 'notice', { event: 'task_cancelled_before_result', taskId }, sessionId);
516
+ sessionStore.markCancelled(resolvedSessionId);
517
+ await emitLog(server, 'notice', { event: 'task_cancelled_before_result', taskId }, resolvedSessionId);
557
518
  return;
558
519
  }
559
520
  const generatedThoughts = Math.max(0, session.thoughts.length - startingCount);
@@ -575,8 +536,8 @@ async function runReasoningTask(args) {
575
536
  taskId,
576
537
  error,
577
538
  };
578
- if (sessionId !== undefined) {
579
- failureArgs.sessionId = sessionId;
539
+ if (resolvedSessionId !== undefined) {
540
+ failureArgs.sessionId = resolvedSessionId;
580
541
  }
581
542
  await handleTaskFailure(failureArgs);
582
543
  }
@@ -588,9 +549,6 @@ function getTaskId(extra) {
588
549
  return extra.taskId;
589
550
  }
590
551
  const TOOL_NAME = 'reasoning_think';
591
- function withIconMeta(iconMeta) {
592
- return iconMeta ? { icons: [iconMeta] } : undefined;
593
- }
594
552
  export function registerReasoningThinkTool(server, iconMeta) {
595
553
  server.experimental.tasks.registerToolTask(TOOL_NAME, {
596
554
  title: 'Reasoning Think',
@@ -598,17 +556,16 @@ export function registerReasoningThinkTool(server, iconMeta) {
598
556
 
599
557
  USAGE PATTERN:
600
558
  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"
559
+ 2. Continue: { sessionId: "<from response>", thought: "next step..." } — level is optional; session level is used
560
+ 3. Repeat until status: "completed" — the summary field contains the exact next call to make
603
561
 
604
- IMPORTANT: You MUST pass the returned sessionId on every continuation call, and use the same level throughout.
562
+ IMPORTANT: Pass the returned sessionId on every continuation call.
605
563
  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.
564
+ Use step_summary for a 1-sentence conclusion per step — these accumulate in the summary field for navigation.
607
565
 
608
566
  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.`,
567
+ Alternatives: runMode="run_to_completion" (batch), or observation/hypothesis/evaluation fields (structured).
568
+ Errors: E_SESSION_NOT_FOUND (expired start new), E_INVALID_THOUGHT_COUNT (check level ranges).`,
612
569
  inputSchema: ReasoningThinkInputSchema,
613
570
  outputSchema: ReasoningThinkToolOutputSchema,
614
571
  annotations: {
@@ -632,7 +589,7 @@ Error recovery: If E_SESSION_NOT_FOUND, the session expired — start a new sess
632
589
  const extra = parseReasoningTaskExtra(rawExtra);
633
590
  const progressToken = extra._meta?.progressToken;
634
591
  if (!reasoningTaskLimiter.tryAcquire()) {
635
- throw new Error(TASK_OVERLOAD_MESSAGE);
592
+ throw new ServerBusyError();
636
593
  }
637
594
  let task;
638
595
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/cortex-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.j0hanz/cortex-mcp",
5
5
  "author": "Johanz",
6
6
  "license": "MIT",