@juspay/neurolink 7.14.7 → 7.15.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.
@@ -6,7 +6,7 @@
6
6
  import { EventEmitter } from "events";
7
7
  import { mcpLogger } from "../utils/logger.js";
8
8
  import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
9
- import { isObject, isString, isBoolean, isNullish, } from "../utils/typeUtils.js";
9
+ import { isObject, isNullish, } from "../utils/typeUtils.js";
10
10
  import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
11
11
  /**
12
12
  * ToolDiscoveryService
@@ -342,7 +342,7 @@ export class ToolDiscoveryService extends EventEmitter {
342
342
  // Update tool statistics
343
343
  this.updateToolStats(toolKey, true, duration);
344
344
  // Validate output if requested
345
- if (options.validateOutput !== false && result) {
345
+ if (options.validateOutput !== false) {
346
346
  this.validateToolOutput(result);
347
347
  }
348
348
  mcpLogger.debug(`[ToolDiscoveryService] Tool execution completed: ${toolName}`, {
@@ -445,46 +445,30 @@ export class ToolDiscoveryService extends EventEmitter {
445
445
  * Validate tool output with enhanced type safety
446
446
  */
447
447
  validateToolOutput(result) {
448
- // Check for null/undefined results
448
+ // GENERIC ERROR HANDLING FOR ALL MCP TOOLS
449
+ // Different MCP servers return different error formats, so we should be permissive
450
+ // and let the AI handle any response format instead of throwing errors
451
+ // Only throw for truly invalid responses (null/undefined)
449
452
  if (isNullish(result)) {
450
- throw new Error("Tool returned null or undefined result");
451
- }
452
- // Enhanced error detection for object results
453
- if (isObject(result)) {
454
- // Check for explicit error property
455
- if (result.error !== undefined) {
456
- const errorMessage = isString(result.error)
457
- ? result.error
458
- : "Tool execution failed with error";
459
- throw new Error(`Tool execution error: ${errorMessage}`);
460
- }
461
- // Check for boolean error flag
462
- if (isBoolean(result.isError) && result.isError === true) {
463
- const errorDetail = isString(result.message)
464
- ? `: ${result.message}`
465
- : "";
466
- throw new Error(`Tool execution failed${errorDetail}`);
467
- }
468
- // Check for common error status patterns
469
- if (isString(result.status) &&
470
- (result.status === "error" || result.status === "failed")) {
471
- const errorDetail = isString(result.message) || isString(result.reason)
472
- ? `: ${result.message || result.reason}`
473
- : "";
474
- throw new Error(`Tool execution failed${errorDetail}`);
475
- }
476
- // Check for success: false pattern
477
- if (isBoolean(result.success) && result.success === false) {
478
- const errorDetail = isString(result.message) || isString(result.error)
479
- ? `: ${result.message || result.error}`
480
- : "";
481
- throw new Error(`Tool execution unsuccessful${errorDetail}`);
482
- }
483
- }
484
- // Validate that string results are not empty
485
- if (isString(result) && result.trim() === "") {
486
- throw new Error("Tool returned empty string result");
453
+ mcpLogger.debug("[ToolDiscoveryService] Tool returned null/undefined, treating as empty response");
454
+ // Even null responses can be valid for some tools - don't throw
455
+ return;
487
456
  }
457
+ // Log what we received for debugging, but don't validate specific formats
458
+ mcpLogger.debug("[ToolDiscoveryService] Tool response received", {
459
+ type: typeof result,
460
+ isArray: Array.isArray(result),
461
+ isObject: isObject(result),
462
+ hasKeys: isObject(result) ? Object.keys(result).length : 0,
463
+ fullResponse: result // Log the complete response, not a truncated sample
464
+ });
465
+ // COMPLETELY PERMISSIVE APPROACH:
466
+ // - Any response format is valid (objects, strings, arrays, booleans, numbers)
467
+ // - Even error responses are passed to the AI to handle
468
+ // - The AI can interpret error messages and retry with different approaches
469
+ // - This works with any MCP server regardless of their response format
470
+ // No validation or throwing - let the AI handle everything
471
+ return;
488
472
  }
489
473
  /**
490
474
  * Update tool statistics
package/dist/neurolink.js CHANGED
@@ -32,6 +32,8 @@ import { EventEmitter } from "events";
32
32
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
33
33
  import { applyConversationMemoryDefaults, getConversationMessages, storeConversationTurn, } from "./utils/conversationMemoryUtils.js";
34
34
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
35
+ // Import direct tools server for automatic registration
36
+ import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
35
37
  import { ContextManager } from "./context/ContextManager.js";
36
38
  import { defaultContextConfig } from "./context/config.js";
37
39
  import { isNonNullObject } from "./utils/typeUtils.js";
@@ -130,6 +132,19 @@ export class NeuroLink {
130
132
  ]);
131
133
  // Register all providers with lazy loading support
132
134
  await ProviderRegistry.registerAllProviders();
135
+ // Register the direct tools server to make websearch and other tools available
136
+ try {
137
+ // Use the server ID string for registration instead of the server object
138
+ await toolRegistry.registerServer("neurolink-direct", directToolsServer);
139
+ mcpLogger.debug("[NeuroLink] Direct tools server registered successfully", {
140
+ serverId: "neurolink-direct",
141
+ });
142
+ }
143
+ catch (error) {
144
+ mcpLogger.warn("[NeuroLink] Failed to register direct tools server", {
145
+ error: error instanceof Error ? error.message : String(error),
146
+ });
147
+ }
133
148
  // Load MCP configuration from .mcp-config.json using ExternalServerManager
134
149
  try {
135
150
  const configResult = await this.externalServerManager.loadMCPConfiguration();
@@ -356,27 +371,55 @@ export class NeuroLink {
356
371
  }
357
372
  // Try MCP-enhanced generation first (if not explicitly disabled)
358
373
  if (!options.disableTools) {
359
- try {
360
- logger.debug(`[${functionTag}] Attempting MCP generation...`);
361
- const mcpResult = await this.tryMCPGeneration(options);
362
- if (mcpResult && mcpResult.content) {
363
- logger.debug(`[${functionTag}] MCP generation successful`);
364
- // Store conversation turn
365
- await storeConversationTurn(this.conversationMemory, options, mcpResult);
366
- return mcpResult;
374
+ let mcpAttempts = 0;
375
+ const maxMcpRetries = 2; // Allow retries for tool-related failures
376
+ while (mcpAttempts <= maxMcpRetries) {
377
+ try {
378
+ logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${mcpAttempts + 1}/${maxMcpRetries + 1})...`);
379
+ const mcpResult = await this.tryMCPGeneration(options);
380
+ if (mcpResult && mcpResult.content) {
381
+ logger.debug(`[${functionTag}] MCP generation successful on attempt ${mcpAttempts + 1}`, {
382
+ contentLength: mcpResult.content.length,
383
+ toolsUsed: mcpResult.toolsUsed?.length || 0,
384
+ toolExecutions: mcpResult.toolExecutions?.length || 0,
385
+ });
386
+ // Store conversation turn
387
+ await storeConversationTurn(this.conversationMemory, options, mcpResult);
388
+ return mcpResult;
389
+ }
390
+ else {
391
+ logger.debug(`[${functionTag}] MCP generation returned empty result on attempt ${mcpAttempts + 1}:`, {
392
+ hasResult: !!mcpResult,
393
+ hasContent: !!(mcpResult && mcpResult.content),
394
+ contentLength: mcpResult?.content?.length || 0,
395
+ toolExecutions: mcpResult?.toolExecutions?.length || 0,
396
+ });
397
+ // If we got a result but no content, and we have tool executions, this might be a tool success case
398
+ if (mcpResult &&
399
+ mcpResult.toolExecutions &&
400
+ mcpResult.toolExecutions.length > 0) {
401
+ logger.debug(`[${functionTag}] Found tool executions but no content, continuing with result`);
402
+ // Store conversation turn even with empty content if tools executed
403
+ await storeConversationTurn(this.conversationMemory, options, mcpResult);
404
+ return mcpResult;
405
+ }
406
+ }
367
407
  }
368
- else {
369
- logger.debug(`[${functionTag}] MCP generation returned empty result:`, {
370
- hasResult: !!mcpResult,
371
- hasContent: !!(mcpResult && mcpResult.content),
372
- contentLength: mcpResult?.content?.length || 0,
408
+ catch (error) {
409
+ mcpAttempts++;
410
+ logger.debug(`[${functionTag}] MCP generation failed on attempt ${mcpAttempts}/${maxMcpRetries + 1}`, {
411
+ error: error instanceof Error ? error.message : String(error),
412
+ willRetry: mcpAttempts <= maxMcpRetries,
373
413
  });
414
+ // If this was the last attempt, break and fall back
415
+ if (mcpAttempts > maxMcpRetries) {
416
+ logger.debug(`[${functionTag}] All MCP attempts exhausted, falling back to direct generation`);
417
+ break;
418
+ }
419
+ // Small delay before retry to allow transient issues to resolve
420
+ await new Promise((resolve) => setTimeout(resolve, 500));
374
421
  }
375
- }
376
- catch (error) {
377
- logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
378
- error: error instanceof Error ? error.message : String(error),
379
- });
422
+ mcpAttempts++;
380
423
  }
381
424
  }
382
425
  // Fall back to direct provider generation
@@ -430,19 +473,40 @@ export class NeuroLink {
430
473
  conversationMessages, // Inject conversation history
431
474
  });
432
475
  const responseTime = Date.now() - startTime;
433
- // Check if result is meaningful
434
- if (!result || !result.content || result.content.trim().length === 0) {
476
+ // Enhanced result validation - consider tool executions as valid results
477
+ const hasContent = result && result.content && result.content.trim().length > 0;
478
+ const hasToolExecutions = result && result.toolExecutions && result.toolExecutions.length > 0;
479
+ // Log detailed result analysis for debugging
480
+ mcpLogger.debug(`[${functionTag}] Result validation:`, {
481
+ hasResult: !!result,
482
+ hasContent,
483
+ hasToolExecutions,
484
+ contentLength: result?.content?.length || 0,
485
+ toolExecutionsCount: result?.toolExecutions?.length || 0,
486
+ toolsUsedCount: result?.toolsUsed?.length || 0,
487
+ });
488
+ // Accept result if it has content OR successful tool executions
489
+ if (!hasContent && !hasToolExecutions) {
490
+ mcpLogger.debug(`[${functionTag}] Result rejected: no content and no tool executions`);
435
491
  return null; // Let caller fall back to direct generation
436
492
  }
437
- // Return enhanced result with external tool information
493
+ // Transform tool executions with enhanced preservation
494
+ const transformedToolExecutions = transformToolExecutionsForMCP(result.toolExecutions);
495
+ // Log transformation results
496
+ mcpLogger.debug(`[${functionTag}] Tool execution transformation:`, {
497
+ originalCount: result?.toolExecutions?.length || 0,
498
+ transformedCount: transformedToolExecutions.length,
499
+ transformedTools: transformedToolExecutions.map((te) => te.toolName),
500
+ });
501
+ // Return enhanced result with preserved tool information
438
502
  return {
439
- content: result.content,
503
+ content: result.content || "", // Ensure content is never undefined
440
504
  provider: providerName,
441
505
  usage: result.usage,
442
506
  responseTime,
443
507
  toolsUsed: result.toolsUsed || [],
444
- toolExecutions: transformToolExecutionsForMCP(result.toolExecutions),
445
- enhancedWithTools: true,
508
+ toolExecutions: transformedToolExecutions,
509
+ enhancedWithTools: Boolean(hasToolExecutions), // Mark as enhanced if tools were actually used
446
510
  availableTools: transformToolsForMCP(availableTools),
447
511
  // Include analytics and evaluation from BaseProvider
448
512
  analytics: result.analytics,
@@ -24,13 +24,56 @@ export function transformToolExecutions(toolExecutions) {
24
24
  if (!toolExecutions || !Array.isArray(toolExecutions)) {
25
25
  return [];
26
26
  }
27
- return toolExecutions.map((te) => {
27
+ return toolExecutions.map((te, index) => {
28
28
  const teRecord = te;
29
+ // Enhanced tool name extraction with multiple fallback strategies
30
+ let toolName = teRecord.name ||
31
+ teRecord.toolName ||
32
+ teRecord.tool ||
33
+ "";
34
+ // If still no name, try to extract from nested objects
35
+ if (!toolName &&
36
+ teRecord.toolCall &&
37
+ typeof teRecord.toolCall === "object") {
38
+ const toolCall = teRecord.toolCall;
39
+ toolName =
40
+ toolCall.name || toolCall.toolName || "";
41
+ }
42
+ // Last resort: use index-based fallback to avoid "Unknown Tool"
43
+ if (!toolName) {
44
+ toolName = `tool_execution_${index}`;
45
+ }
46
+ // Enhanced input extraction
47
+ let input = teRecord.input ||
48
+ teRecord.parameters ||
49
+ teRecord.args ||
50
+ {};
51
+ // Extract input from nested toolCall if available
52
+ if (Object.keys(input).length === 0 &&
53
+ teRecord.toolCall &&
54
+ typeof teRecord.toolCall === "object") {
55
+ const toolCall = teRecord.toolCall;
56
+ input =
57
+ toolCall.input ||
58
+ toolCall.parameters ||
59
+ toolCall.args ||
60
+ {};
61
+ }
62
+ // Enhanced output extraction with success indication
63
+ let output = teRecord.output ||
64
+ teRecord.result ||
65
+ teRecord.response ||
66
+ "success";
67
+ // Enhanced duration extraction
68
+ let duration = teRecord.duration ??
69
+ teRecord.executionTime ??
70
+ teRecord.responseTime ??
71
+ 0;
29
72
  return {
30
- name: teRecord.name || "",
31
- input: teRecord.input || {},
32
- output: teRecord.output || "success",
33
- duration: teRecord.duration || 0,
73
+ name: toolName,
74
+ input: input,
75
+ output: output,
76
+ duration: duration,
34
77
  };
35
78
  });
36
79
  }
@@ -42,13 +85,63 @@ export function transformToolExecutionsForMCP(toolExecutions) {
42
85
  if (!toolExecutions || !Array.isArray(toolExecutions)) {
43
86
  return [];
44
87
  }
45
- return toolExecutions.map((te) => {
88
+ return toolExecutions.map((te, index) => {
46
89
  const teRecord = te;
90
+ // Enhanced tool name extraction matching the main function
91
+ let toolName = teRecord.name ||
92
+ teRecord.toolName ||
93
+ teRecord.tool ||
94
+ "";
95
+ // Try nested toolCall extraction
96
+ if (!toolName &&
97
+ teRecord.toolCall &&
98
+ typeof teRecord.toolCall === "object") {
99
+ const toolCall = teRecord.toolCall;
100
+ toolName =
101
+ toolCall.name || toolCall.toolName || "";
102
+ }
103
+ // Fallback to avoid empty names
104
+ if (!toolName) {
105
+ toolName = `mcp_tool_execution_${index}`;
106
+ }
107
+ // Enhanced execution time extraction
108
+ let executionTime = teRecord.duration ??
109
+ teRecord.executionTime ??
110
+ teRecord.responseTime ??
111
+ 0;
112
+ // Enhanced success detection - check for actual success indicators
113
+ let success = true; // Default to true
114
+ // Check for explicit success/error indicators
115
+ if (teRecord.success !== undefined) {
116
+ success = Boolean(teRecord.success);
117
+ }
118
+ else if (teRecord.error !== undefined) {
119
+ success = false;
120
+ }
121
+ else if (teRecord.status !== undefined) {
122
+ const status = String(teRecord.status).toLowerCase().trim();
123
+ success = !["error", "failed", "failure", "fail"].includes(status);
124
+ }
125
+ // Enhanced server ID extraction
126
+ let serverId = teRecord.serverId ||
127
+ teRecord.server ||
128
+ teRecord.source ||
129
+ undefined;
130
+ // Try to extract from nested structures
131
+ if (!serverId &&
132
+ teRecord.toolCall &&
133
+ typeof teRecord.toolCall === "object") {
134
+ const toolCall = teRecord.toolCall;
135
+ serverId =
136
+ toolCall.serverId ||
137
+ toolCall.server ||
138
+ undefined;
139
+ }
47
140
  return {
48
- toolName: teRecord.name || "",
49
- executionTime: teRecord.duration || 0,
50
- success: true, // Assume success if tool executed (AI providers handle failures differently)
51
- serverId: teRecord.serverId || undefined,
141
+ toolName: toolName,
142
+ executionTime: executionTime,
143
+ success: success,
144
+ serverId: serverId,
52
145
  };
53
146
  });
54
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "7.14.7",
3
+ "version": "7.15.0",
4
4
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",
@@ -148,6 +148,7 @@
148
148
  "@aws-sdk/client-sagemaker": "^3.862.0",
149
149
  "@aws-sdk/client-sagemaker-runtime": "^3.862.0",
150
150
  "@aws-sdk/types": "^3.862.0",
151
+ "@google-cloud/vertexai": "^1.10.0",
151
152
  "@google/generative-ai": "^0.24.1",
152
153
  "@huggingface/inference": "^2.8.0",
153
154
  "@modelcontextprotocol/sdk": "^1.13.0",