@j0hanz/cortex-mcp 1.2.1 → 1.4.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 (95) hide show
  1. package/README.md +2 -2
  2. package/dist/engine/config.d.ts +3 -2
  3. package/dist/engine/config.js +15 -4
  4. package/dist/engine/context.d.ts +4 -5
  5. package/dist/engine/context.js +3 -1
  6. package/dist/engine/events.d.ts +8 -10
  7. package/dist/engine/events.js +6 -4
  8. package/dist/engine/reasoner.d.ts +7 -2
  9. package/dist/engine/reasoner.js +82 -39
  10. package/dist/engine/session-store.d.ts +14 -3
  11. package/dist/engine/session-store.js +134 -80
  12. package/dist/index.d.ts +0 -1
  13. package/dist/index.js +14 -8
  14. package/dist/lib/errors.d.ts +0 -1
  15. package/dist/lib/errors.js +16 -8
  16. package/dist/lib/formatting.d.ts +0 -1
  17. package/dist/lib/formatting.js +32 -25
  18. package/dist/lib/prompt-contracts.d.ts +14 -0
  19. package/dist/lib/prompt-contracts.js +124 -0
  20. package/dist/lib/text.d.ts +0 -1
  21. package/dist/lib/text.js +23 -18
  22. package/dist/lib/tool-contracts.d.ts +15 -0
  23. package/dist/lib/tool-contracts.js +92 -0
  24. package/dist/lib/tool-response.d.ts +0 -1
  25. package/dist/lib/tool-response.js +9 -7
  26. package/dist/lib/types.d.ts +23 -13
  27. package/dist/lib/types.js +0 -1
  28. package/dist/lib/validators.d.ts +0 -1
  29. package/dist/lib/validators.js +12 -11
  30. package/dist/prompts/index.d.ts +0 -1
  31. package/dist/prompts/index.js +98 -112
  32. package/dist/resources/index.d.ts +0 -1
  33. package/dist/resources/index.js +92 -68
  34. package/dist/resources/instructions.d.ts +1 -0
  35. package/dist/resources/instructions.js +95 -0
  36. package/dist/resources/tool-catalog.d.ts +1 -0
  37. package/dist/resources/tool-catalog.js +20 -0
  38. package/dist/resources/tool-info.d.ts +2 -0
  39. package/dist/resources/tool-info.js +35 -0
  40. package/dist/resources/workflows.d.ts +1 -0
  41. package/dist/resources/workflows.js +60 -0
  42. package/dist/schemas/inputs.d.ts +7 -2
  43. package/dist/schemas/inputs.js +62 -48
  44. package/dist/schemas/outputs.d.ts +3 -1
  45. package/dist/schemas/outputs.js +50 -25
  46. package/dist/server.d.ts +0 -1
  47. package/dist/server.js +71 -56
  48. package/dist/tools/index.d.ts +0 -4
  49. package/dist/tools/index.js +4 -5
  50. package/dist/tools/reasoning-think.d.ts +0 -1
  51. package/dist/tools/reasoning-think.js +273 -109
  52. package/package.json +11 -9
  53. package/dist/engine/config.d.ts.map +0 -1
  54. package/dist/engine/config.js.map +0 -1
  55. package/dist/engine/context.d.ts.map +0 -1
  56. package/dist/engine/context.js.map +0 -1
  57. package/dist/engine/events.d.ts.map +0 -1
  58. package/dist/engine/events.js.map +0 -1
  59. package/dist/engine/reasoner.d.ts.map +0 -1
  60. package/dist/engine/reasoner.js.map +0 -1
  61. package/dist/engine/session-store.d.ts.map +0 -1
  62. package/dist/engine/session-store.js.map +0 -1
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js.map +0 -1
  65. package/dist/instructions.md +0 -147
  66. package/dist/lib/errors.d.ts.map +0 -1
  67. package/dist/lib/errors.js.map +0 -1
  68. package/dist/lib/formatting.d.ts.map +0 -1
  69. package/dist/lib/formatting.js.map +0 -1
  70. package/dist/lib/instructions.d.ts +0 -5
  71. package/dist/lib/instructions.d.ts.map +0 -1
  72. package/dist/lib/instructions.js +0 -14
  73. package/dist/lib/instructions.js.map +0 -1
  74. package/dist/lib/text.d.ts.map +0 -1
  75. package/dist/lib/text.js.map +0 -1
  76. package/dist/lib/tool-response.d.ts.map +0 -1
  77. package/dist/lib/tool-response.js.map +0 -1
  78. package/dist/lib/types.d.ts.map +0 -1
  79. package/dist/lib/types.js.map +0 -1
  80. package/dist/lib/validators.d.ts.map +0 -1
  81. package/dist/lib/validators.js.map +0 -1
  82. package/dist/prompts/index.d.ts.map +0 -1
  83. package/dist/prompts/index.js.map +0 -1
  84. package/dist/resources/index.d.ts.map +0 -1
  85. package/dist/resources/index.js.map +0 -1
  86. package/dist/schemas/inputs.d.ts.map +0 -1
  87. package/dist/schemas/inputs.js.map +0 -1
  88. package/dist/schemas/outputs.d.ts.map +0 -1
  89. package/dist/schemas/outputs.js.map +0 -1
  90. package/dist/server.d.ts.map +0 -1
  91. package/dist/server.js.map +0 -1
  92. package/dist/tools/index.d.ts.map +0 -1
  93. package/dist/tools/index.js.map +0 -1
  94. package/dist/tools/reasoning-think.d.ts.map +0 -1
  95. package/dist/tools/reasoning-think.js.map +0 -1
package/README.md CHANGED
@@ -98,7 +98,7 @@ Add to `~/.cursor/mcp.json`:
98
98
 
99
99
  ### Tools
100
100
 
101
- #### `reasoning.think`
101
+ #### `reasoning_think`
102
102
 
103
103
  Perform multi-step reasoning on a query with a selected depth level.
104
104
 
@@ -137,7 +137,7 @@ Perform multi-step reasoning on a query with a selected depth level.
137
137
 
138
138
  ### Tasks
139
139
 
140
- Task-augmented tool calls are supported for `reasoning.think`. Use `tools/call` with task support to run long-running reasoning sessions asynchronously.
140
+ Task-augmented tool calls are supported for `reasoning_think`. Use `tools/call` with task support to run long-running reasoning sessions asynchronously.
141
141
 
142
142
  ## Configuration
143
143
 
@@ -1,4 +1,4 @@
1
- import type { ReasoningLevel } from '../lib/types.js';
1
+ import type { LevelConfig, ReasoningLevel } from '../lib/types.js';
2
2
  export declare const LEVEL_CONFIGS: {
3
3
  readonly basic: {
4
4
  readonly minThoughts: 3;
@@ -16,5 +16,6 @@ export declare const LEVEL_CONFIGS: {
16
16
  readonly tokenBudget: 32768;
17
17
  };
18
18
  };
19
+ export declare function getLevelConfig(level: ReasoningLevel): LevelConfig;
19
20
  export declare function assertTargetThoughtsInRange(level: ReasoningLevel, targetThoughts: number): void;
20
- //# sourceMappingURL=config.d.ts.map
21
+ export declare function getLevelDescriptionString(): string;
@@ -4,10 +4,21 @@ export const LEVEL_CONFIGS = {
4
4
  normal: { minThoughts: 6, maxThoughts: 10, tokenBudget: 8192 },
5
5
  high: { minThoughts: 15, maxThoughts: 25, tokenBudget: 32768 },
6
6
  };
7
+ export function getLevelConfig(level) {
8
+ return LEVEL_CONFIGS[level];
9
+ }
7
10
  export function assertTargetThoughtsInRange(level, targetThoughts) {
8
- const error = getTargetThoughtsError(level, targetThoughts);
9
- if (error) {
10
- throw new Error(error);
11
+ const errorMessage = getTargetThoughtsError(level, targetThoughts);
12
+ if (!errorMessage) {
13
+ return;
11
14
  }
15
+ throw new Error(errorMessage);
16
+ }
17
+ export function getLevelDescriptionString() {
18
+ return Object.entries(LEVEL_CONFIGS)
19
+ .map(([level, config]) => {
20
+ const budgetK = Math.round(config.tokenBudget / 1024);
21
+ return `${level} (${config.minThoughts}–${config.maxThoughts} steps, ${budgetK}K budget)`;
22
+ })
23
+ .join(', ');
12
24
  }
13
- //# sourceMappingURL=config.js.map
@@ -1,7 +1,6 @@
1
- interface EngineContext {
2
- sessionId: string;
3
- abortSignal?: AbortSignal;
1
+ export interface EngineContext {
2
+ readonly sessionId: string;
3
+ readonly abortSignal?: AbortSignal;
4
4
  }
5
5
  export declare function runWithContext<T>(ctx: EngineContext, fn: () => T): T;
6
- export {};
7
- //# sourceMappingURL=context.d.ts.map
6
+ export declare function getContext(): EngineContext | undefined;
@@ -3,4 +3,6 @@ const storage = new AsyncLocalStorage();
3
3
  export function runWithContext(ctx, fn) {
4
4
  return storage.run(ctx, fn);
5
5
  }
6
- //# sourceMappingURL=context.js.map
6
+ export function getContext() {
7
+ return storage.getStore();
8
+ }
@@ -1,5 +1,12 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import type { ReasoningLevel } from '../lib/types.js';
3
+ interface ThoughtBudgetExhaustedPayload {
4
+ sessionId: string;
5
+ tokensUsed: number;
6
+ tokenBudget: number;
7
+ generatedThoughts: number;
8
+ requestedThoughts: number;
9
+ }
3
10
  interface EngineEvents {
4
11
  'thought:added': [{
5
12
  sessionId: string;
@@ -14,15 +21,7 @@ interface EngineEvents {
14
21
  revision: number;
15
22
  }
16
23
  ];
17
- 'thought:budget-exhausted': [
18
- {
19
- sessionId: string;
20
- tokensUsed: number;
21
- tokenBudget: number;
22
- generatedThoughts: number;
23
- requestedThoughts: number;
24
- }
25
- ];
24
+ 'thought:budget-exhausted': [ThoughtBudgetExhaustedPayload];
26
25
  'session:created': [{
27
26
  sessionId: string;
28
27
  level: ReasoningLevel;
@@ -52,4 +51,3 @@ interface TypedEmitter<T> extends Omit<EventEmitter, 'on' | 'off' | 'emit'> {
52
51
  }
53
52
  export declare const engineEvents: TypedEmitter<EngineEvents>;
54
53
  export {};
55
- //# sourceMappingURL=events.d.ts.map
@@ -1,9 +1,11 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { getErrorMessage } from '../lib/errors.js';
3
+ const ENGINE_ERROR_LOG_PREFIX = '[engine]';
4
+ const ENGINE_ERROR_PREFIX_WITH_SPACE = `${ENGINE_ERROR_LOG_PREFIX} `;
3
5
  export const engineEvents = new EventEmitter({
4
6
  captureRejections: true,
5
7
  });
6
- engineEvents.on('error', (err) => {
7
- process.stderr.write(`[engine] ${getErrorMessage(err)}\n`);
8
- });
9
- //# sourceMappingURL=events.js.map
8
+ function logEngineError(err) {
9
+ process.stderr.write(`${ENGINE_ERROR_PREFIX_WITH_SPACE}${getErrorMessage(err)}\n`);
10
+ }
11
+ engineEvents.on('error', logEngineError);
@@ -5,9 +5,14 @@ export { sessionStore };
5
5
  interface ReasonOptions {
6
6
  sessionId?: string;
7
7
  targetThoughts?: number;
8
- thought: string;
8
+ thought?: string;
9
+ observation?: string;
10
+ hypothesis?: string;
11
+ evaluation?: string;
12
+ stepSummary?: string;
13
+ isConclusion?: boolean;
14
+ rollbackToStep?: number;
9
15
  abortSignal?: AbortSignal;
10
16
  onProgress?: (progress: number, total: number) => void | Promise<void>;
11
17
  }
12
18
  export declare function reason(query: string, level: ReasoningLevel | undefined, options?: ReasonOptions): Promise<Readonly<Session>>;
13
- //# sourceMappingURL=reasoner.d.ts.map
@@ -1,12 +1,14 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { createSegmenter } from '../lib/text.js';
3
- import { assertTargetThoughtsInRange, LEVEL_CONFIGS } from './config.js';
3
+ import { assertTargetThoughtsInRange, getLevelConfig } from './config.js';
4
4
  import { runWithContext } from './context.js';
5
5
  import { engineEvents } from './events.js';
6
6
  import { SessionStore } from './session-store.js';
7
7
  const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
8
8
  const DEFAULT_MAX_SESSIONS = 100;
9
9
  const DEFAULT_MAX_TOTAL_TOKENS = 500_000;
10
+ const NON_WHITESPACE = /\S/u;
11
+ const COMPLEXITY_KEYWORDS = /\b(compare|analy[sz]e|trade[- ]?off|design|plan|critique|evaluate|review|architecture)\b/i;
10
12
  function parsePositiveIntEnv(name, fallback, minimum = 1) {
11
13
  const raw = process.env[name];
12
14
  if (raw === undefined) {
@@ -23,48 +25,57 @@ const sentenceSegmenter = createSegmenter('sentence');
23
25
  const sessionLocks = new Map();
24
26
  export { sessionStore };
25
27
  export async function reason(query, level, options) {
26
- if (!options?.thought) {
27
- throw new Error('thought is required: provide your reasoning content');
28
+ const { sessionId, targetThoughts, thought, observation, hypothesis, evaluation, stepSummary, isConclusion, rollbackToStep, abortSignal, onProgress, } = options ?? {};
29
+ const hasContent = thought !== undefined ||
30
+ (observation !== undefined &&
31
+ hypothesis !== undefined &&
32
+ evaluation !== undefined);
33
+ if (!hasContent && rollbackToStep === undefined) {
34
+ throw new Error('Either thought (or observation/hypothesis/evaluation) or rollback_to_step is required');
28
35
  }
29
- const { sessionId, targetThoughts, thought, abortSignal, onProgress } = options;
30
36
  const session = resolveSession(level, sessionId, query, targetThoughts);
31
- const config = LEVEL_CONFIGS[session.level];
37
+ const config = getLevelConfig(session.level);
32
38
  const { totalThoughts } = session;
33
39
  return runWithContext({ sessionId: session.id, ...(abortSignal ? { abortSignal } : {}) }, () => withSessionLock(session.id, async () => {
34
40
  throwIfReasoningAborted(abortSignal);
41
+ if (rollbackToStep !== undefined) {
42
+ sessionStore.rollback(session.id, rollbackToStep);
43
+ }
35
44
  const current = getSessionOrThrow(session.id);
36
- if (current.tokensUsed >= config.tokenBudget) {
37
- emitBudgetExhausted({
38
- sessionId: session.id,
39
- tokensUsed: current.tokensUsed,
40
- tokenBudget: config.tokenBudget,
41
- generatedThoughts: 0,
42
- requestedThoughts: totalThoughts,
43
- });
45
+ let content = thought;
46
+ if (!content && observation) {
47
+ content = `**Observation:** ${observation}\n\n**Hypothesis:** ${hypothesis ?? ''}\n\n**Evaluation:** ${evaluation ?? ''}`;
48
+ }
49
+ if (!content) {
50
+ // Only rollback occurred
51
+ return current;
52
+ }
53
+ if (emitBudgetExhaustedIfNeeded({
54
+ session: current,
55
+ tokenBudget: config.tokenBudget,
56
+ generatedThoughts: 0,
57
+ requestedThoughts: totalThoughts,
58
+ })) {
44
59
  return current;
45
60
  }
46
61
  const nextIndex = current.thoughts.length;
47
- if (nextIndex >= totalThoughts) {
62
+ if (nextIndex >= totalThoughts && !isConclusion) {
48
63
  return current;
49
64
  }
50
- const stepContent = thought;
51
- const addedThought = sessionStore.addThought(session.id, stepContent);
65
+ const addedThought = sessionStore.addThought(session.id, content, stepSummary);
52
66
  engineEvents.emit('thought:added', {
53
67
  sessionId: session.id,
54
68
  index: addedThought.index,
55
69
  content: addedThought.content,
56
70
  });
57
71
  const updated = getSessionOrThrow(session.id);
58
- if (updated.tokensUsed >= config.tokenBudget) {
59
- emitBudgetExhausted({
60
- sessionId: session.id,
61
- tokensUsed: updated.tokensUsed,
62
- tokenBudget: config.tokenBudget,
63
- generatedThoughts: addedThought.index + 1,
64
- requestedThoughts: totalThoughts,
65
- });
66
- }
67
- if (updated.thoughts.length >= totalThoughts) {
72
+ emitBudgetExhaustedIfNeeded({
73
+ session: updated,
74
+ tokenBudget: config.tokenBudget,
75
+ generatedThoughts: addedThought.index + 1,
76
+ requestedThoughts: totalThoughts,
77
+ });
78
+ if (isConclusion || updated.thoughts.length >= totalThoughts) {
68
79
  sessionStore.markCompleted(session.id);
69
80
  }
70
81
  if (onProgress) {
@@ -103,25 +114,46 @@ function getSessionOrThrow(sessionId) {
103
114
  function emitBudgetExhausted(data) {
104
115
  engineEvents.emit('thought:budget-exhausted', data);
105
116
  }
117
+ function createBudgetExhaustedPayload(args) {
118
+ const { session, tokenBudget, generatedThoughts, requestedThoughts } = args;
119
+ return {
120
+ sessionId: session.id,
121
+ tokensUsed: session.tokensUsed,
122
+ tokenBudget,
123
+ generatedThoughts,
124
+ requestedThoughts,
125
+ };
126
+ }
127
+ function emitBudgetExhaustedIfNeeded(args) {
128
+ const { session, tokenBudget } = args;
129
+ if (session.tokensUsed < tokenBudget) {
130
+ return false;
131
+ }
132
+ emitBudgetExhausted(createBudgetExhaustedPayload(args));
133
+ return true;
134
+ }
135
+ function assertExistingSessionConstraints(existing, level, targetThoughts) {
136
+ if (level !== undefined && existing.level !== level) {
137
+ // Warning: ignoring provided level in favor of session level
138
+ }
139
+ if (targetThoughts !== undefined &&
140
+ targetThoughts !== existing.totalThoughts) {
141
+ throw new Error(`Cannot change targetThoughts on an existing session (current: ${String(existing.totalThoughts)}). Omit targetThoughts or pass ${String(existing.totalThoughts)}.`);
142
+ }
143
+ }
106
144
  function resolveSession(level, sessionId, query, targetThoughts) {
107
145
  if (sessionId) {
108
146
  const existing = sessionStore.get(sessionId);
109
147
  if (!existing) {
110
148
  throw new Error(`Session not found: ${sessionId}`);
111
149
  }
112
- if (level !== undefined && existing.level !== level) {
113
- // Warning: ignoring provided level in favor of session level
114
- }
115
- if (targetThoughts !== undefined &&
116
- targetThoughts !== existing.totalThoughts) {
117
- throw new Error(`Cannot change targetThoughts on an existing session (current: ${String(existing.totalThoughts)}). Omit targetThoughts or pass ${String(existing.totalThoughts)}.`);
118
- }
150
+ assertExistingSessionConstraints(existing, level, targetThoughts);
119
151
  return existing;
120
152
  }
121
153
  if (level === undefined) {
122
154
  throw new Error('level is required for new sessions');
123
155
  }
124
- const config = LEVEL_CONFIGS[level];
156
+ const config = getLevelConfig(level);
125
157
  const totalThoughts = resolveThoughtCount(level, query, config, targetThoughts);
126
158
  const session = sessionStore.create(level, totalThoughts);
127
159
  engineEvents.emit('session:created', {
@@ -143,8 +175,7 @@ function resolveThoughtCount(level, query, config, targetThoughts) {
143
175
  const queryByteLength = Buffer.byteLength(queryText, 'utf8');
144
176
  const lengthScore = Math.min(1, queryByteLength / 400);
145
177
  const structureScore = Math.min(0.4, getStructureDensityScore(queryText));
146
- const keywords = /\b(compare|analy[sz]e|trade[- ]?off|design|plan|critique|evaluate|review|architecture)\b/i;
147
- const keywordScore = keywords.test(queryText) ? 0.25 : 0;
178
+ const keywordScore = COMPLEXITY_KEYWORDS.test(queryText) ? 0.25 : 0;
148
179
  const score = Math.min(1, lengthScore + structureScore + keywordScore);
149
180
  return config.minThoughts + Math.round(span * score);
150
181
  }
@@ -154,7 +185,7 @@ function countSentences(queryText) {
154
185
  }
155
186
  let count = 0;
156
187
  for (const sentence of sentenceSegmenter.segment(queryText)) {
157
- if (sentence.segment.trim().length > 0) {
188
+ if (NON_WHITESPACE.test(sentence.segment)) {
158
189
  count++;
159
190
  }
160
191
  }
@@ -165,7 +196,20 @@ function getStructureDensityScore(queryText) {
165
196
  if (sentenceCount > 1) {
166
197
  return (sentenceCount - 1) * 0.08;
167
198
  }
168
- const markerMatches = queryText.match(/[?:;,\n]/g)?.length ?? 0;
199
+ let markerMatches = 0;
200
+ for (let index = 0; index < queryText.length; index++) {
201
+ switch (queryText.charCodeAt(index)) {
202
+ case 63: // ?
203
+ case 58: // :
204
+ case 59: // ;
205
+ case 44: // ,
206
+ case 10: // \n
207
+ markerMatches += 1;
208
+ break;
209
+ default:
210
+ break;
211
+ }
212
+ }
169
213
  return markerMatches * 0.05;
170
214
  }
171
215
  function throwIfReasoningAborted(signal) {
@@ -179,4 +223,3 @@ function throwIfReasoningAborted(signal) {
179
223
  throw new Error('Reasoning aborted');
180
224
  }
181
225
  }
182
- //# sourceMappingURL=reasoner.js.map
@@ -1,4 +1,4 @@
1
- import type { ReasoningLevel, Session, Thought } from '../lib/types.js';
1
+ import type { ReasoningLevel, Session, SessionSummary, Thought } from '../lib/types.js';
2
2
  export declare class SessionStore {
3
3
  private readonly sessions;
4
4
  private readonly sessionOrder;
@@ -13,15 +13,20 @@ export declare class SessionStore {
13
13
  constructor(ttlMs?: number, maxSessions?: number, maxTotalTokens?: number);
14
14
  create(level: ReasoningLevel, totalThoughts?: number): Readonly<Session>;
15
15
  get(id: string): Readonly<Session> | undefined;
16
+ getSummary(id: string): Readonly<SessionSummary> | undefined;
16
17
  list(): Readonly<Session>[];
18
+ listSessionIds(): readonly string[];
19
+ listSummaries(): readonly SessionSummary[];
17
20
  getTtlMs(): number;
18
21
  getExpiresAt(sessionId: string): number | undefined;
19
22
  getTotalTokensUsed(): number;
20
23
  delete(id: string): boolean;
21
- addThought(sessionId: string, content: string): Thought;
24
+ addThought(sessionId: string, content: string, stepSummary?: string): Thought;
25
+ rollback(sessionId: string, toIndex: number): void;
22
26
  reviseThought(sessionId: string, thoughtIndex: number, content: string): Thought;
23
27
  markCompleted(sessionId: string): void;
24
28
  markCancelled(sessionId: string): void;
29
+ private updateSessionStatus;
25
30
  private evictIfAtCapacity;
26
31
  private evictForTokenHeadroom;
27
32
  private findOldestSession;
@@ -30,11 +35,17 @@ export declare class SessionStore {
30
35
  private addToOrder;
31
36
  private touchOrder;
32
37
  private removeFromOrder;
38
+ private setNextId;
39
+ private setPrevId;
33
40
  private deleteSessionInternal;
41
+ private markSessionTouched;
42
+ private getSessionIdsForIteration;
34
43
  private snapshotThought;
35
44
  private snapshotSession;
45
+ private snapshotSessionSummary;
36
46
  private emitSessionsListChanged;
37
47
  private emitSessionsResourceUpdated;
48
+ private emitSessionsCollectionUpdated;
49
+ private emitSessionEvicted;
38
50
  private emitSessionResourcesUpdated;
39
51
  }
40
- //# sourceMappingURL=session-store.d.ts.map