cognitive-modules-cli 2.2.1 → 2.2.5

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/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -29
  4. package/dist/cli.js +513 -22
  5. package/dist/commands/add.d.ts +33 -14
  6. package/dist/commands/add.js +222 -13
  7. package/dist/commands/compose.js +60 -23
  8. package/dist/commands/index.d.ts +4 -0
  9. package/dist/commands/index.js +4 -0
  10. package/dist/commands/init.js +23 -1
  11. package/dist/commands/migrate.d.ts +30 -0
  12. package/dist/commands/migrate.js +650 -0
  13. package/dist/commands/pipe.d.ts +1 -0
  14. package/dist/commands/pipe.js +31 -11
  15. package/dist/commands/remove.js +33 -2
  16. package/dist/commands/run.d.ts +1 -0
  17. package/dist/commands/run.js +37 -27
  18. package/dist/commands/search.d.ts +28 -0
  19. package/dist/commands/search.js +143 -0
  20. package/dist/commands/test.d.ts +65 -0
  21. package/dist/commands/test.js +454 -0
  22. package/dist/commands/update.d.ts +1 -0
  23. package/dist/commands/update.js +106 -14
  24. package/dist/commands/validate.d.ts +36 -0
  25. package/dist/commands/validate.js +97 -0
  26. package/dist/errors/index.d.ts +218 -0
  27. package/dist/errors/index.js +412 -0
  28. package/dist/mcp/server.js +84 -79
  29. package/dist/modules/composition.js +97 -32
  30. package/dist/modules/loader.js +4 -2
  31. package/dist/modules/runner.d.ts +65 -0
  32. package/dist/modules/runner.js +293 -49
  33. package/dist/modules/subagent.d.ts +6 -1
  34. package/dist/modules/subagent.js +18 -13
  35. package/dist/modules/validator.js +14 -6
  36. package/dist/providers/anthropic.d.ts +15 -0
  37. package/dist/providers/anthropic.js +147 -5
  38. package/dist/providers/base.d.ts +11 -0
  39. package/dist/providers/base.js +18 -0
  40. package/dist/providers/gemini.d.ts +15 -0
  41. package/dist/providers/gemini.js +122 -5
  42. package/dist/providers/ollama.d.ts +15 -0
  43. package/dist/providers/ollama.js +111 -3
  44. package/dist/providers/openai.d.ts +11 -0
  45. package/dist/providers/openai.js +133 -0
  46. package/dist/registry/client.d.ts +204 -0
  47. package/dist/registry/client.js +356 -0
  48. package/dist/registry/index.d.ts +4 -0
  49. package/dist/registry/index.js +4 -0
  50. package/dist/server/http.js +173 -42
  51. package/dist/types.d.ts +32 -1
  52. package/dist/types.js +4 -1
  53. package/dist/version.d.ts +1 -0
  54. package/dist/version.js +4 -0
  55. package/package.json +31 -7
  56. package/dist/modules/composition.test.d.ts +0 -11
  57. package/dist/modules/composition.test.js +0 -450
  58. package/dist/modules/policy.test.d.ts +0 -10
  59. package/dist/modules/policy.test.js +0 -369
  60. package/src/cli.ts +0 -471
  61. package/src/commands/add.ts +0 -315
  62. package/src/commands/compose.ts +0 -185
  63. package/src/commands/index.ts +0 -13
  64. package/src/commands/init.ts +0 -94
  65. package/src/commands/list.ts +0 -33
  66. package/src/commands/pipe.ts +0 -76
  67. package/src/commands/remove.ts +0 -57
  68. package/src/commands/run.ts +0 -80
  69. package/src/commands/update.ts +0 -130
  70. package/src/commands/versions.ts +0 -79
  71. package/src/index.ts +0 -90
  72. package/src/mcp/index.ts +0 -5
  73. package/src/mcp/server.ts +0 -403
  74. package/src/modules/composition.test.ts +0 -558
  75. package/src/modules/composition.ts +0 -1674
  76. package/src/modules/index.ts +0 -9
  77. package/src/modules/loader.ts +0 -508
  78. package/src/modules/policy.test.ts +0 -455
  79. package/src/modules/runner.ts +0 -1983
  80. package/src/modules/subagent.ts +0 -277
  81. package/src/modules/validator.ts +0 -700
  82. package/src/providers/anthropic.ts +0 -89
  83. package/src/providers/base.ts +0 -29
  84. package/src/providers/deepseek.ts +0 -83
  85. package/src/providers/gemini.ts +0 -117
  86. package/src/providers/index.ts +0 -78
  87. package/src/providers/minimax.ts +0 -81
  88. package/src/providers/moonshot.ts +0 -82
  89. package/src/providers/ollama.ts +0 -83
  90. package/src/providers/openai.ts +0 -84
  91. package/src/providers/qwen.ts +0 -82
  92. package/src/server/http.ts +0 -316
  93. package/src/server/index.ts +0 -6
  94. package/src/types.ts +0 -599
  95. package/tsconfig.json +0 -17
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Cognitive Modules - Unified Error Handling
3
+ *
4
+ * Provides consistent error structures across HTTP, MCP, and CLI layers.
5
+ * Based on ERROR-CODES.md specification.
6
+ */
7
+ // =============================================================================
8
+ // Error Codes
9
+ // =============================================================================
10
+ /**
11
+ * Standard error codes per ERROR-CODES.md specification.
12
+ *
13
+ * Format: E{category}{sequence}
14
+ * - Category 1: Input errors
15
+ * - Category 2: Processing errors
16
+ * - Category 3: Output errors
17
+ * - Category 4: Runtime errors
18
+ * - Category 5-9: Module-specific (reserved)
19
+ */
20
+ export const ErrorCodes = {
21
+ // E1xxx: Input Errors
22
+ PARSE_ERROR: 'E1000',
23
+ INVALID_INPUT: 'E1001',
24
+ MISSING_REQUIRED_FIELD: 'E1002',
25
+ TYPE_MISMATCH: 'E1003',
26
+ UNSUPPORTED_VALUE: 'E1004',
27
+ INPUT_TOO_LARGE: 'E1005',
28
+ INVALID_REFERENCE: 'E1006',
29
+ // E2xxx: Processing Errors
30
+ LOW_CONFIDENCE: 'E2001',
31
+ TIMEOUT: 'E2002',
32
+ TOKEN_LIMIT: 'E2003',
33
+ NO_ACTION_POSSIBLE: 'E2004',
34
+ SEMANTIC_CONFLICT: 'E2005',
35
+ AMBIGUOUS_INPUT: 'E2006',
36
+ INSUFFICIENT_CONTEXT: 'E2007',
37
+ // E3xxx: Output Errors
38
+ OUTPUT_SCHEMA_VIOLATION: 'E3001',
39
+ PARTIAL_RESULT: 'E3002',
40
+ MISSING_RATIONALE: 'E3003',
41
+ OVERFLOW_LIMIT: 'E3004',
42
+ INVALID_ENUM: 'E3005',
43
+ CONSTRAINT_VIOLATION: 'E3006',
44
+ // E4xxx: Runtime Errors
45
+ INTERNAL_ERROR: 'E4000',
46
+ PROVIDER_UNAVAILABLE: 'E4001',
47
+ RATE_LIMITED: 'E4002',
48
+ CONTEXT_OVERFLOW: 'E4003',
49
+ CIRCULAR_DEPENDENCY: 'E4004',
50
+ MAX_DEPTH_EXCEEDED: 'E4005',
51
+ MODULE_NOT_FOUND: 'E4006',
52
+ PERMISSION_DENIED: 'E4007',
53
+ ENDPOINT_NOT_FOUND: 'E4008',
54
+ RESOURCE_NOT_FOUND: 'E4009',
55
+ };
56
+ // =============================================================================
57
+ // Legacy Code Mapping
58
+ // =============================================================================
59
+ const LEGACY_CODE_MAP = {
60
+ // Input errors
61
+ 'PARSE_ERROR': ErrorCodes.PARSE_ERROR,
62
+ 'INVALID_INPUT': ErrorCodes.INVALID_INPUT,
63
+ 'MISSING_REQUIRED_FIELD': ErrorCodes.MISSING_REQUIRED_FIELD,
64
+ 'TYPE_MISMATCH': ErrorCodes.TYPE_MISMATCH,
65
+ 'UNSUPPORTED_VALUE': ErrorCodes.UNSUPPORTED_VALUE,
66
+ 'UNSUPPORTED_LANGUAGE': ErrorCodes.UNSUPPORTED_VALUE,
67
+ 'INPUT_TOO_LARGE': ErrorCodes.INPUT_TOO_LARGE,
68
+ 'INVALID_REFERENCE': ErrorCodes.INVALID_REFERENCE,
69
+ // Processing errors
70
+ 'LOW_CONFIDENCE': ErrorCodes.LOW_CONFIDENCE,
71
+ 'TIMEOUT': ErrorCodes.TIMEOUT,
72
+ 'TOKEN_LIMIT': ErrorCodes.TOKEN_LIMIT,
73
+ 'NO_ACTION_POSSIBLE': ErrorCodes.NO_ACTION_POSSIBLE,
74
+ 'NO_SIMPLIFICATION_POSSIBLE': ErrorCodes.NO_ACTION_POSSIBLE,
75
+ 'SEMANTIC_CONFLICT': ErrorCodes.SEMANTIC_CONFLICT,
76
+ 'BEHAVIOR_CHANGE_REQUIRED': ErrorCodes.SEMANTIC_CONFLICT,
77
+ 'AMBIGUOUS_INPUT': ErrorCodes.AMBIGUOUS_INPUT,
78
+ 'INSUFFICIENT_CONTEXT': ErrorCodes.INSUFFICIENT_CONTEXT,
79
+ // Output errors
80
+ 'SCHEMA_VALIDATION_FAILED': ErrorCodes.OUTPUT_SCHEMA_VIOLATION,
81
+ 'OUTPUT_SCHEMA_VIOLATION': ErrorCodes.OUTPUT_SCHEMA_VIOLATION,
82
+ 'PARTIAL_RESULT': ErrorCodes.PARTIAL_RESULT,
83
+ // Runtime errors
84
+ 'INTERNAL_ERROR': ErrorCodes.INTERNAL_ERROR,
85
+ 'PROVIDER_UNAVAILABLE': ErrorCodes.PROVIDER_UNAVAILABLE,
86
+ 'RATE_LIMITED': ErrorCodes.RATE_LIMITED,
87
+ 'MODULE_NOT_FOUND': ErrorCodes.MODULE_NOT_FOUND,
88
+ 'PERMISSION_DENIED': ErrorCodes.PERMISSION_DENIED,
89
+ 'ENDPOINT_NOT_FOUND': ErrorCodes.ENDPOINT_NOT_FOUND,
90
+ 'RESOURCE_NOT_FOUND': ErrorCodes.RESOURCE_NOT_FOUND,
91
+ 'NOT_FOUND': ErrorCodes.RESOURCE_NOT_FOUND,
92
+ };
93
+ /**
94
+ * Normalize error code to E-format.
95
+ * Accepts both legacy string codes and E-format codes.
96
+ */
97
+ export function normalizeErrorCode(code) {
98
+ // Already E-format
99
+ if (/^E\d{4}$/.test(code)) {
100
+ return code;
101
+ }
102
+ // Legacy format
103
+ return LEGACY_CODE_MAP[code] || ErrorCodes.INTERNAL_ERROR;
104
+ }
105
+ // =============================================================================
106
+ // Error Envelope Factory
107
+ // =============================================================================
108
+ /**
109
+ * Default recoverability by error category.
110
+ */
111
+ function getDefaultRecoverable(code) {
112
+ const category = code.charAt(1);
113
+ switch (category) {
114
+ case '1': return true; // Input errors are usually recoverable
115
+ case '2': return true; // Processing errors may be recoverable
116
+ case '3': return false; // Output errors are not recoverable
117
+ case '4': {
118
+ // Runtime errors: some are recoverable
119
+ const recoverable4xxx = [
120
+ ErrorCodes.PROVIDER_UNAVAILABLE,
121
+ ErrorCodes.RATE_LIMITED,
122
+ ErrorCodes.MODULE_NOT_FOUND,
123
+ ];
124
+ return recoverable4xxx.includes(code);
125
+ }
126
+ default: return false;
127
+ }
128
+ }
129
+ /**
130
+ * Create a standardized error envelope.
131
+ *
132
+ * @example
133
+ * // Simple error
134
+ * makeErrorEnvelope({
135
+ * code: ErrorCodes.MODULE_NOT_FOUND,
136
+ * message: "Module 'code-reviewer' not found",
137
+ * suggestion: "Use 'cog list' to see available modules"
138
+ * });
139
+ *
140
+ * @example
141
+ * // Error with retry info
142
+ * makeErrorEnvelope({
143
+ * code: ErrorCodes.RATE_LIMITED,
144
+ * message: "Rate limit exceeded",
145
+ * retry_after_ms: 60000
146
+ * });
147
+ */
148
+ export function makeErrorEnvelope(options) {
149
+ const code = normalizeErrorCode(options.code);
150
+ const recoverable = options.recoverable ?? getDefaultRecoverable(code);
151
+ const error = {
152
+ code,
153
+ message: options.message,
154
+ recoverable,
155
+ };
156
+ if (options.suggestion) {
157
+ error.suggestion = options.suggestion;
158
+ }
159
+ if (options.retry_after_ms !== undefined) {
160
+ error.retry_after_ms = options.retry_after_ms;
161
+ }
162
+ if (options.details) {
163
+ error.details = options.details;
164
+ }
165
+ return {
166
+ ok: false,
167
+ version: options.version || '2.2',
168
+ meta: {
169
+ confidence: options.confidence ?? 0.0,
170
+ risk: options.risk ?? 'high',
171
+ explain: (options.explain || options.message).slice(0, 280),
172
+ trace_id: options.trace_id,
173
+ },
174
+ error,
175
+ partial_data: options.partial_data,
176
+ };
177
+ }
178
+ // =============================================================================
179
+ // Layer-Specific Helpers
180
+ // =============================================================================
181
+ export function attachContext(envelope, context) {
182
+ if (!context)
183
+ return envelope;
184
+ const { module, provider } = context;
185
+ if (!module && !provider)
186
+ return envelope;
187
+ return {
188
+ ...envelope,
189
+ ...(module ? { module } : {}),
190
+ ...(provider ? { provider } : {}),
191
+ };
192
+ }
193
+ /**
194
+ * Create error envelope for HTTP API responses.
195
+ *
196
+ * @returns Tuple of [statusCode, body]
197
+ */
198
+ export function makeHttpError(options) {
199
+ const envelope = attachContext(makeErrorEnvelope(options), options);
200
+ const code = normalizeErrorCode(options.code);
201
+ // Determine HTTP status code
202
+ let statusCode;
203
+ const category = code.charAt(1);
204
+ switch (category) {
205
+ case '1': {
206
+ // Input errors -> Bad Request (with specific overrides)
207
+ if (code === ErrorCodes.INPUT_TOO_LARGE) {
208
+ statusCode = 413; // Payload Too Large
209
+ }
210
+ else {
211
+ statusCode = 400;
212
+ }
213
+ break;
214
+ }
215
+ case '2':
216
+ statusCode = 422;
217
+ break; // Processing errors -> Unprocessable Entity
218
+ case '3':
219
+ statusCode = 500;
220
+ break; // Output errors -> Internal Server Error
221
+ case '4': {
222
+ // Runtime errors - map to appropriate HTTP status
223
+ if (code === ErrorCodes.MODULE_NOT_FOUND ||
224
+ code === ErrorCodes.ENDPOINT_NOT_FOUND ||
225
+ code === ErrorCodes.RESOURCE_NOT_FOUND) {
226
+ statusCode = 404; // Not Found
227
+ }
228
+ else if (code === ErrorCodes.PERMISSION_DENIED) {
229
+ statusCode = 403; // Forbidden
230
+ }
231
+ else if (code === ErrorCodes.RATE_LIMITED) {
232
+ statusCode = 429; // Too Many Requests
233
+ }
234
+ else {
235
+ statusCode = 500; // Internal Server Error
236
+ }
237
+ break;
238
+ }
239
+ default: statusCode = 500;
240
+ }
241
+ // Add HTTP-specific fields
242
+ return [statusCode, envelope];
243
+ }
244
+ /**
245
+ * Create error envelope for MCP tool responses.
246
+ */
247
+ export function makeMcpError(options) {
248
+ const envelope = attachContext(makeErrorEnvelope(options), {
249
+ module: options.module ?? 'unknown',
250
+ provider: options.provider ?? 'unknown',
251
+ });
252
+ return {
253
+ content: [
254
+ {
255
+ type: 'text',
256
+ text: JSON.stringify(envelope, null, 2),
257
+ },
258
+ ],
259
+ };
260
+ }
261
+ /**
262
+ * Convert error envelope to CLI-friendly error message.
263
+ */
264
+ export function toCliError(envelope) {
265
+ const { error } = envelope;
266
+ let message = `Error [${error.code}]: ${error.message}`;
267
+ if (error.suggestion) {
268
+ message += `\n Suggestion: ${error.suggestion}`;
269
+ }
270
+ if (error.retry_after_ms) {
271
+ const seconds = Math.ceil(error.retry_after_ms / 1000);
272
+ message += `\n Retry after: ${seconds}s`;
273
+ }
274
+ return message;
275
+ }
276
+ /**
277
+ * Convert CLI CommandResult-style error to standard envelope.
278
+ * Used for backward compatibility during migration.
279
+ */
280
+ export function fromCliError(errorMessage, code = ErrorCodes.INTERNAL_ERROR) {
281
+ return makeErrorEnvelope({
282
+ code,
283
+ message: errorMessage,
284
+ });
285
+ }
286
+ // =============================================================================
287
+ // Error Type Guards
288
+ // =============================================================================
289
+ /**
290
+ * Check if an error envelope indicates a recoverable error.
291
+ */
292
+ export function isRecoverable(envelope) {
293
+ return envelope.error.recoverable === true;
294
+ }
295
+ /**
296
+ * Check if an error envelope has partial data.
297
+ */
298
+ export function hasPartialData(envelope) {
299
+ return envelope.partial_data !== undefined;
300
+ }
301
+ /**
302
+ * Check if an error envelope suggests retrying.
303
+ */
304
+ export function shouldRetry(envelope) {
305
+ const retryableCodes = [
306
+ ErrorCodes.RATE_LIMITED,
307
+ ErrorCodes.PROVIDER_UNAVAILABLE,
308
+ ErrorCodes.TIMEOUT,
309
+ ];
310
+ return (envelope.error.recoverable === true &&
311
+ (envelope.error.retry_after_ms !== undefined ||
312
+ retryableCodes.includes(envelope.error.code)));
313
+ }
314
+ /**
315
+ * Create a standardized success envelope.
316
+ * Use this for consistent success responses across HTTP and MCP layers.
317
+ */
318
+ export function makeSuccessEnvelope(options) {
319
+ return {
320
+ ok: true,
321
+ version: options.version || '2.2',
322
+ meta: {
323
+ confidence: options.confidence ?? 1.0,
324
+ risk: options.risk ?? 'none',
325
+ explain: (options.explain || 'Operation completed successfully').slice(0, 280),
326
+ trace_id: options.trace_id,
327
+ },
328
+ data: options.data,
329
+ };
330
+ }
331
+ /**
332
+ * Create success envelope for MCP tool responses.
333
+ */
334
+ export function makeMcpSuccess(options) {
335
+ const envelope = makeSuccessEnvelope(options);
336
+ return {
337
+ content: [
338
+ {
339
+ type: 'text',
340
+ text: JSON.stringify(envelope, null, 2),
341
+ },
342
+ ],
343
+ };
344
+ }
345
+ // =============================================================================
346
+ // Common Error Factories
347
+ // =============================================================================
348
+ /**
349
+ * Create MODULE_NOT_FOUND error.
350
+ */
351
+ export function moduleNotFoundError(moduleName, options) {
352
+ return makeErrorEnvelope({
353
+ code: ErrorCodes.MODULE_NOT_FOUND,
354
+ message: `Module '${moduleName}' not found`,
355
+ suggestion: options?.suggestion || "Use 'cog list' to see available modules, or 'cog search' to find modules in registry",
356
+ trace_id: options?.trace_id,
357
+ });
358
+ }
359
+ /**
360
+ * Create PARSE_ERROR error.
361
+ */
362
+ export function parseError(details, options) {
363
+ return makeErrorEnvelope({
364
+ code: ErrorCodes.PARSE_ERROR,
365
+ message: details ? `JSON parsing failed: ${details}` : 'Invalid JSON body',
366
+ recoverable: false,
367
+ trace_id: options?.trace_id,
368
+ });
369
+ }
370
+ /**
371
+ * Create RATE_LIMITED error.
372
+ */
373
+ export function rateLimitedError(retryAfterMs, provider) {
374
+ return makeErrorEnvelope({
375
+ code: ErrorCodes.RATE_LIMITED,
376
+ message: `Rate limit exceeded${provider ? ` for provider '${provider}'` : ''}`,
377
+ retry_after_ms: retryAfterMs,
378
+ suggestion: `Wait ${Math.ceil(retryAfterMs / 1000)} seconds before retrying`,
379
+ });
380
+ }
381
+ /**
382
+ * Create INTERNAL_ERROR error.
383
+ */
384
+ export function internalError(message, options) {
385
+ return makeErrorEnvelope({
386
+ code: ErrorCodes.INTERNAL_ERROR,
387
+ message,
388
+ recoverable: false,
389
+ details: options?.details,
390
+ trace_id: options?.trace_id,
391
+ });
392
+ }
393
+ /**
394
+ * Create MISSING_REQUIRED_FIELD error.
395
+ */
396
+ export function missingFieldError(fieldName, options) {
397
+ return makeErrorEnvelope({
398
+ code: ErrorCodes.MISSING_REQUIRED_FIELD,
399
+ message: `Missing required field: ${fieldName}`,
400
+ suggestion: options?.suggestion || `Provide the '${fieldName}' field in your request`,
401
+ });
402
+ }
403
+ /**
404
+ * Create PERMISSION_DENIED error.
405
+ */
406
+ export function permissionDeniedError(reason) {
407
+ return makeErrorEnvelope({
408
+ code: ErrorCodes.PERMISSION_DENIED,
409
+ message: reason,
410
+ recoverable: false,
411
+ });
412
+ }
@@ -12,12 +12,14 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSche
12
12
  import { findModule, listModules, getDefaultSearchPaths } from '../modules/loader.js';
13
13
  import { runModule } from '../modules/runner.js';
14
14
  import { getProvider } from '../providers/index.js';
15
+ import { VERSION } from '../version.js';
16
+ import { ErrorCodes, attachContext, makeErrorEnvelope, makeMcpError, makeMcpSuccess } from '../errors/index.js';
15
17
  // =============================================================================
16
18
  // Server Setup
17
19
  // =============================================================================
18
20
  const server = new Server({
19
21
  name: 'cognitive-modules',
20
- version: '1.3.0',
22
+ version: VERSION,
21
23
  }, {
22
24
  capabilities: {
23
25
  tools: {},
@@ -35,7 +37,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
35
37
  tools: [
36
38
  {
37
39
  name: 'cognitive_run',
38
- description: 'Run a Cognitive Module to get structured AI analysis results',
40
+ description: 'Run a Cognitive Module to get structured AI analysis results (Cognitive Protocol v2.2)',
39
41
  inputSchema: {
40
42
  type: 'object',
41
43
  properties: {
@@ -61,7 +63,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
61
63
  },
62
64
  {
63
65
  name: 'cognitive_list',
64
- description: 'List all installed Cognitive Modules',
66
+ description: 'List all installed Cognitive Modules (Cognitive Protocol v2.2)',
65
67
  inputSchema: {
66
68
  type: 'object',
67
69
  properties: {},
@@ -69,7 +71,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
69
71
  },
70
72
  {
71
73
  name: 'cognitive_info',
72
- description: 'Get detailed information about a Cognitive Module',
74
+ description: 'Get detailed information about a Cognitive Module (Cognitive Protocol v2.2)',
73
75
  inputSchema: {
74
76
  type: 'object',
75
77
  properties: {
@@ -84,113 +86,105 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
84
86
  ],
85
87
  };
86
88
  });
89
+ // Error handling now uses unified errors module (../errors/index.js)
87
90
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
88
91
  const { name, arguments: args } = request.params;
92
+ const runContext = name === 'cognitive_run'
93
+ ? { module: args.module, provider: args.provider ?? 'unknown' }
94
+ : undefined;
89
95
  try {
90
96
  switch (name) {
91
97
  case 'cognitive_run': {
92
98
  const { module: moduleName, args: inputArgs, provider: providerName, model } = args;
99
+ const providerLabel = providerName ?? 'unknown';
93
100
  // Find module
94
101
  const moduleData = await findModule(moduleName, searchPaths);
95
102
  if (!moduleData) {
96
- return {
97
- content: [
98
- {
99
- type: 'text',
100
- text: JSON.stringify({ ok: false, error: `Module '${moduleName}' not found` }),
101
- },
102
- ],
103
- };
103
+ return makeMcpError({
104
+ code: ErrorCodes.MODULE_NOT_FOUND,
105
+ message: `Module '${moduleName}' not found`,
106
+ suggestion: 'Use cognitive_list to see available modules',
107
+ module: moduleName,
108
+ provider: providerLabel,
109
+ });
104
110
  }
105
111
  // Create provider
106
112
  const provider = getProvider(providerName, model);
107
- // Run module
113
+ const resolvedProvider = provider?.name ?? providerLabel;
114
+ // Run module - result is already v2.2 envelope
108
115
  const result = await runModule(moduleData, provider, {
109
- input: { query: inputArgs, code: inputArgs },
116
+ args: inputArgs,
110
117
  useV22: true,
111
118
  });
119
+ const contextual = attachContext(result, {
120
+ module: moduleName,
121
+ provider: resolvedProvider,
122
+ });
112
123
  return {
113
124
  content: [
114
125
  {
115
126
  type: 'text',
116
- text: JSON.stringify(result, null, 2),
127
+ text: JSON.stringify(contextual, null, 2),
117
128
  },
118
129
  ],
119
130
  };
120
131
  }
121
132
  case 'cognitive_list': {
122
133
  const modules = await listModules(searchPaths);
123
- return {
124
- content: [
125
- {
126
- type: 'text',
127
- text: JSON.stringify({
128
- modules: modules.map((m) => ({
129
- name: m.name,
130
- location: m.location,
131
- format: m.format,
132
- tier: m.tier,
133
- })),
134
- count: modules.length,
135
- }, null, 2),
136
- },
137
- ],
138
- };
134
+ return makeMcpSuccess({
135
+ data: {
136
+ modules: modules.map((m) => ({
137
+ name: m.name,
138
+ location: m.location,
139
+ format: m.format,
140
+ tier: m.tier,
141
+ responsibility: m.responsibility,
142
+ })),
143
+ count: modules.length,
144
+ },
145
+ explain: `Found ${modules.length} installed modules`,
146
+ });
139
147
  }
140
148
  case 'cognitive_info': {
141
149
  const { module: moduleName } = args;
142
150
  const moduleData = await findModule(moduleName, searchPaths);
143
151
  if (!moduleData) {
144
- return {
145
- content: [
146
- {
147
- type: 'text',
148
- text: JSON.stringify({ ok: false, error: `Module '${moduleName}' not found` }),
149
- },
150
- ],
151
- };
152
+ return makeMcpError({
153
+ code: ErrorCodes.MODULE_NOT_FOUND,
154
+ message: `Module '${moduleName}' not found`,
155
+ suggestion: 'Use cognitive_list to see available modules',
156
+ module: moduleName,
157
+ });
152
158
  }
153
- return {
154
- content: [
155
- {
156
- type: 'text',
157
- text: JSON.stringify({
158
- ok: true,
159
- name: moduleData.name,
160
- version: moduleData.version,
161
- responsibility: moduleData.responsibility,
162
- tier: moduleData.tier,
163
- format: moduleData.format,
164
- inputSchema: moduleData.inputSchema,
165
- outputSchema: moduleData.outputSchema,
166
- }, null, 2),
167
- },
168
- ],
169
- };
159
+ return makeMcpSuccess({
160
+ data: {
161
+ name: moduleData.name,
162
+ version: moduleData.version,
163
+ responsibility: moduleData.responsibility,
164
+ tier: moduleData.tier,
165
+ format: moduleData.format,
166
+ inputSchema: moduleData.inputSchema,
167
+ outputSchema: moduleData.outputSchema,
168
+ },
169
+ explain: `Module '${moduleName}' info retrieved`,
170
+ });
170
171
  }
171
172
  default:
172
- return {
173
- content: [
174
- {
175
- type: 'text',
176
- text: JSON.stringify({ ok: false, error: `Unknown tool: ${name}` }),
177
- },
178
- ],
179
- };
173
+ return makeMcpError({
174
+ code: ErrorCodes.INVALID_REFERENCE,
175
+ message: `Unknown tool: ${name}`,
176
+ suggestion: 'Use cognitive_run, cognitive_list, or cognitive_info',
177
+ });
180
178
  }
181
179
  }
182
180
  catch (error) {
183
- return {
184
- content: [
185
- {
186
- type: 'text',
187
- text: JSON.stringify({
188
- ok: false,
189
- error: error instanceof Error ? error.message : String(error),
190
- }),
191
- },
192
- ],
193
- };
181
+ const errorMessage = error instanceof Error ? error.message : String(error);
182
+ return makeMcpError({
183
+ code: ErrorCodes.INTERNAL_ERROR,
184
+ message: errorMessage,
185
+ recoverable: false,
186
+ ...(runContext ?? {}),
187
+ });
194
188
  }
195
189
  });
196
190
  // =============================================================================
@@ -234,12 +228,17 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
234
228
  const moduleName = match[1];
235
229
  const moduleData = await findModule(moduleName, searchPaths);
236
230
  if (!moduleData) {
231
+ const envelope = attachContext(makeErrorEnvelope({
232
+ code: ErrorCodes.MODULE_NOT_FOUND,
233
+ message: `Module '${moduleName}' not found`,
234
+ recoverable: true,
235
+ }), { module: moduleName });
237
236
  return {
238
237
  contents: [
239
238
  {
240
239
  uri,
241
- mimeType: 'text/plain',
242
- text: `Module '${moduleName}' not found`,
240
+ mimeType: 'application/json',
241
+ text: JSON.stringify(envelope, null, 2),
243
242
  },
244
243
  ],
245
244
  };
@@ -254,12 +253,18 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
254
253
  ],
255
254
  };
256
255
  }
256
+ // Return structured error for unknown resource
257
+ const envelope = makeErrorEnvelope({
258
+ code: ErrorCodes.RESOURCE_NOT_FOUND,
259
+ message: `Unknown resource: ${uri}`,
260
+ recoverable: true,
261
+ });
257
262
  return {
258
263
  contents: [
259
264
  {
260
265
  uri,
261
- mimeType: 'text/plain',
262
- text: `Unknown resource: ${uri}`,
266
+ mimeType: 'application/json',
267
+ text: JSON.stringify(envelope, null, 2),
263
268
  },
264
269
  ],
265
270
  };