@juspay/neurolink 7.29.1 → 7.29.2

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 (59) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cli/commands/config.d.ts +3 -3
  3. package/dist/cli/commands/mcp.js +25 -0
  4. package/dist/cli/factories/commandFactory.d.ts +1 -0
  5. package/dist/cli/factories/commandFactory.js +115 -21
  6. package/dist/cli/index.js +8 -0
  7. package/dist/core/factory.js +77 -4
  8. package/dist/factories/providerFactory.js +3 -0
  9. package/dist/factories/providerRegistry.js +2 -2
  10. package/dist/lib/core/factory.js +77 -4
  11. package/dist/lib/factories/providerFactory.js +3 -0
  12. package/dist/lib/factories/providerRegistry.js +2 -2
  13. package/dist/lib/mcp/externalServerManager.js +13 -14
  14. package/dist/lib/mcp/flexibleToolValidator.d.ts +50 -0
  15. package/dist/lib/mcp/flexibleToolValidator.js +161 -0
  16. package/dist/lib/mcp/toolRegistry.d.ts +2 -2
  17. package/dist/lib/mcp/toolRegistry.js +25 -50
  18. package/dist/lib/neurolink.d.ts +2 -0
  19. package/dist/lib/neurolink.js +137 -69
  20. package/dist/lib/providers/amazonBedrock.d.ts +47 -6
  21. package/dist/lib/providers/amazonBedrock.js +282 -23
  22. package/dist/lib/providers/aws/credentialProvider.d.ts +58 -0
  23. package/dist/lib/providers/aws/credentialProvider.js +267 -0
  24. package/dist/lib/providers/aws/credentialTester.d.ts +49 -0
  25. package/dist/lib/providers/aws/credentialTester.js +394 -0
  26. package/dist/lib/proxy/awsProxyIntegration.d.ts +23 -0
  27. package/dist/lib/proxy/awsProxyIntegration.js +285 -0
  28. package/dist/lib/proxy/proxyFetch.d.ts +9 -5
  29. package/dist/lib/proxy/proxyFetch.js +232 -98
  30. package/dist/lib/proxy/utils/noProxyUtils.d.ts +39 -0
  31. package/dist/lib/proxy/utils/noProxyUtils.js +149 -0
  32. package/dist/lib/types/providers.d.ts +43 -0
  33. package/dist/lib/utils/providerConfig.d.ts +1 -0
  34. package/dist/lib/utils/providerConfig.js +2 -1
  35. package/dist/lib/utils/providerHealth.js +123 -5
  36. package/dist/mcp/externalServerManager.js +13 -14
  37. package/dist/mcp/flexibleToolValidator.d.ts +50 -0
  38. package/dist/mcp/flexibleToolValidator.js +161 -0
  39. package/dist/mcp/toolRegistry.d.ts +2 -2
  40. package/dist/mcp/toolRegistry.js +25 -50
  41. package/dist/neurolink.d.ts +2 -0
  42. package/dist/neurolink.js +137 -69
  43. package/dist/providers/amazonBedrock.d.ts +47 -6
  44. package/dist/providers/amazonBedrock.js +282 -23
  45. package/dist/providers/aws/credentialProvider.d.ts +58 -0
  46. package/dist/providers/aws/credentialProvider.js +267 -0
  47. package/dist/providers/aws/credentialTester.d.ts +49 -0
  48. package/dist/providers/aws/credentialTester.js +394 -0
  49. package/dist/proxy/awsProxyIntegration.d.ts +23 -0
  50. package/dist/proxy/awsProxyIntegration.js +285 -0
  51. package/dist/proxy/proxyFetch.d.ts +9 -5
  52. package/dist/proxy/proxyFetch.js +232 -98
  53. package/dist/proxy/utils/noProxyUtils.d.ts +39 -0
  54. package/dist/proxy/utils/noProxyUtils.js +149 -0
  55. package/dist/types/providers.d.ts +43 -0
  56. package/dist/utils/providerConfig.d.ts +1 -0
  57. package/dist/utils/providerConfig.js +2 -1
  58. package/dist/utils/providerHealth.js +123 -5
  59. package/package.json +5 -1
@@ -359,9 +359,10 @@ export function createAnthropicBaseConfig() {
359
359
  // =============================================================================
360
360
  /**
361
361
  * Gets AWS Region with default fallback
362
+ * Supports both AWS_REGION and AWS_DEFAULT_REGION for broader compatibility
362
363
  */
363
364
  export function getAWSRegion() {
364
- return process.env.AWS_REGION || "us-east-1";
365
+ return (process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1");
365
366
  }
366
367
  /**
367
368
  * Gets AWS Session Token if available
@@ -130,6 +130,14 @@ export class ProviderHealthChecker {
130
130
  */
131
131
  static async checkEnvironmentConfiguration(providerName, healthStatus) {
132
132
  const requiredEnvVars = this.getRequiredEnvironmentVariables(providerName);
133
+ logger.debug(`[ProviderHealthChecker] Checking environment configuration for ${providerName}`, {
134
+ requiredEnvVars,
135
+ presentEnvVars: requiredEnvVars.map((envVar) => ({
136
+ name: envVar,
137
+ present: !!process.env[envVar],
138
+ hasValue: !!(process.env[envVar] && process.env[envVar].trim() !== ""),
139
+ })),
140
+ });
133
141
  let allConfigured = true;
134
142
  const missingVars = [];
135
143
  for (const envVar of requiredEnvVars) {
@@ -140,6 +148,12 @@ export class ProviderHealthChecker {
140
148
  }
141
149
  }
142
150
  healthStatus.isConfigured = allConfigured;
151
+ logger.debug(`[ProviderHealthChecker] Environment configuration result for ${providerName}`, {
152
+ isConfigured: allConfigured,
153
+ missingVars,
154
+ totalRequired: requiredEnvVars.length,
155
+ totalMissing: missingVars.length,
156
+ });
143
157
  if (!allConfigured) {
144
158
  healthStatus.configurationIssues.push(`Missing required environment variables: ${missingVars.join(", ")}`);
145
159
  healthStatus.recommendations.push(`Set the following environment variables: ${missingVars.join(", ")}`);
@@ -211,6 +225,12 @@ export class ProviderHealthChecker {
211
225
  }
212
226
  return;
213
227
  }
228
+ // Providers that don't use API keys directly
229
+ if (providerName === AIProviderName.OLLAMA ||
230
+ providerName === AIProviderName.BEDROCK) {
231
+ healthStatus.hasApiKey = true;
232
+ return;
233
+ }
214
234
  // 🔧 STANDARD HANDLING FOR OTHER PROVIDERS
215
235
  const apiKeyVar = this.getApiKeyEnvironmentVariable(providerName);
216
236
  const apiKey = process.env[apiKeyVar];
@@ -342,7 +362,9 @@ export class ProviderHealthChecker {
342
362
  case AIProviderName.GOOGLE_AI:
343
363
  return ["GOOGLE_AI_API_KEY"];
344
364
  case AIProviderName.BEDROCK:
345
- return ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
365
+ // Bedrock credentials are resolved via AWS SDK default provider chain.
366
+ // Region/auth validated in provider-specific checks.
367
+ return [];
346
368
  case AIProviderName.AZURE:
347
369
  return ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"];
348
370
  case AIProviderName.OLLAMA:
@@ -556,13 +578,109 @@ export class ProviderHealthChecker {
556
578
  }
557
579
  break;
558
580
  }
559
- case AIProviderName.BEDROCK:
560
- // Check AWS region
561
- if (!process.env.AWS_REGION) {
581
+ case AIProviderName.BEDROCK: {
582
+ logger.debug("Starting AWS Bedrock comprehensive health check", {
583
+ providerName,
584
+ });
585
+ // Check AWS region configuration
586
+ const awsRegion = process.env.AWS_REGION;
587
+ const validBedrockRegions = [
588
+ "us-east-1",
589
+ "us-west-2",
590
+ "ap-southeast-1",
591
+ "ap-northeast-1",
592
+ "eu-central-1",
593
+ "eu-west-1",
594
+ "ap-south-1",
595
+ ];
596
+ logger.debug("AWS Region validation", {
597
+ hasAwsRegion: !!awsRegion,
598
+ awsRegion: awsRegion || "not set",
599
+ validBedrockRegions,
600
+ });
601
+ if (!awsRegion) {
562
602
  healthStatus.configurationIssues.push("AWS_REGION not set");
563
- healthStatus.recommendations.push("Set AWS_REGION (e.g., us-east-1)");
603
+ healthStatus.recommendations.push(`Set AWS_REGION to a Bedrock-supported region: ${validBedrockRegions.join(", ")}`);
604
+ }
605
+ else if (!validBedrockRegions.includes(awsRegion)) {
606
+ healthStatus.configurationIssues.push(`AWS_REGION '${awsRegion}' may not support all Bedrock models`);
607
+ healthStatus.recommendations.push(`Consider using a primary Bedrock region: ${validBedrockRegions.slice(0, 3).join(", ")}`);
608
+ }
609
+ // Check AWS credentials configuration
610
+ const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
611
+ const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
612
+ const awsSessionToken = process.env.AWS_SESSION_TOKEN;
613
+ const awsProfile = process.env.AWS_PROFILE;
614
+ logger.debug("AWS Credentials validation", {
615
+ hasAccessKeyId: !!awsAccessKeyId,
616
+ hasSecretAccessKey: !!awsSecretAccessKey,
617
+ hasSessionToken: !!awsSessionToken,
618
+ hasProfile: !!awsProfile,
619
+ authMethod: awsProfile
620
+ ? "AWS Profile"
621
+ : awsAccessKeyId
622
+ ? "Access Keys"
623
+ : "None detected",
624
+ });
625
+ if (!awsAccessKeyId && !awsProfile) {
626
+ healthStatus.configurationIssues.push("No AWS credentials found");
627
+ healthStatus.recommendations.push("Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or configure AWS_PROFILE");
628
+ }
629
+ else if (awsAccessKeyId && !awsSecretAccessKey) {
630
+ healthStatus.configurationIssues.push("AWS_ACCESS_KEY_ID set but AWS_SECRET_ACCESS_KEY missing");
631
+ healthStatus.recommendations.push("Set AWS_SECRET_ACCESS_KEY to match your AWS_ACCESS_KEY_ID");
632
+ }
633
+ // Check for Bedrock-specific model configuration
634
+ const bedrockModel = process.env.BEDROCK_MODEL || process.env.BEDROCK_MODEL_ID;
635
+ const supportedModels = [
636
+ "anthropic.claude-3-sonnet-20240229-v1:0",
637
+ "anthropic.claude-3-haiku-20240307-v1:0",
638
+ "anthropic.claude-3-opus-20240229-v1:0",
639
+ "anthropic.claude-v2:1",
640
+ "amazon.titan-text-express-v1",
641
+ ];
642
+ logger.debug("Bedrock Model validation", {
643
+ hasBedrockModel: !!bedrockModel,
644
+ bedrockModel: bedrockModel || "not set",
645
+ supportedModels: supportedModels.slice(0, 3),
646
+ });
647
+ if (!bedrockModel) {
648
+ healthStatus.recommendations.push("Set BEDROCK_MODEL or BEDROCK_MODEL_ID for faster startup (e.g., anthropic.claude-3-sonnet-20240229-v1:0)");
649
+ }
650
+ else if (!supportedModels.some((model) => model === bedrockModel)) {
651
+ healthStatus.recommendations.push(`Consider using a popular Bedrock model: ${supportedModels.slice(0, 3).join(", ")}`);
652
+ }
653
+ // Check for additional Bedrock configuration
654
+ const bedrockEndpoint = process.env.BEDROCK_ENDPOINT_URL;
655
+ if (bedrockEndpoint) {
656
+ logger.debug("Custom Bedrock endpoint detected", {
657
+ endpoint: bedrockEndpoint,
658
+ });
659
+ if (!bedrockEndpoint.startsWith("https://")) {
660
+ healthStatus.configurationIssues.push("BEDROCK_ENDPOINT_URL should use HTTPS");
661
+ healthStatus.recommendations.push("Update BEDROCK_ENDPOINT_URL to use HTTPS protocol");
662
+ }
663
+ }
664
+ // AWS SDK Configuration checks
665
+ const awsConfig = {
666
+ maxAttempts: process.env.AWS_MAX_ATTEMPTS,
667
+ retryMode: process.env.AWS_RETRY_MODE,
668
+ defaultsMode: process.env.AWS_DEFAULTS_MODE,
669
+ };
670
+ logger.debug("AWS SDK Configuration", {
671
+ awsConfig,
672
+ hasAdvancedConfig: Object.values(awsConfig).some(Boolean),
673
+ });
674
+ if (healthStatus.configurationIssues.length === 0) {
675
+ healthStatus.hasApiKey = true;
676
+ logger.debug("AWS Bedrock configuration appears valid", {
677
+ region: awsRegion,
678
+ hasCredentials: !!(awsAccessKeyId || awsProfile),
679
+ hasModel: !!bedrockModel,
680
+ });
564
681
  }
565
682
  break;
683
+ }
566
684
  case AIProviderName.AZURE: {
567
685
  // Check Azure OpenAI endpoint
568
686
  const azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
@@ -664,6 +664,9 @@ export class ExternalServerManager extends EventEmitter {
664
664
  const delay = Math.min(1000 *
665
665
  Math.pow(this.config.restartBackoffMultiplier, instance.reconnectAttempts - 1), 30000);
666
666
  mcpLogger.info(`[ExternalServerManager] Scheduling restart for ${serverId} in ${delay}ms (attempt ${instance.reconnectAttempts})`);
667
+ if (instance.restartTimer) {
668
+ return;
669
+ } // already scheduled
667
670
  instance.restartTimer = setTimeout(async () => {
668
671
  try {
669
672
  await this.stopServer(serverId);
@@ -914,6 +917,7 @@ export class ExternalServerManager extends EventEmitter {
914
917
  }
915
918
  try {
916
919
  mcpLogger.debug(`[ExternalServerManager] Registering ${instance.toolsMap.size} tools with main registry for server: ${serverId}`);
920
+ const registrations = [];
917
921
  for (const [toolName, tool] of instance.toolsMap.entries()) {
918
922
  const toolId = `${serverId}.${toolName}`;
919
923
  const toolInfo = {
@@ -923,21 +927,16 @@ export class ExternalServerManager extends EventEmitter {
923
927
  serverId: serverId,
924
928
  category: detectCategory({ isExternal: true, serverId }),
925
929
  };
926
- // Register with main tool registry
927
- try {
928
- toolRegistry.registerTool(toolId, toolInfo, {
929
- execute: async (params, context) => {
930
- // Execute tool via ExternalServerManager for proper lifecycle management
931
- return await this.executeTool(serverId, toolName, params, { timeout: this.config.defaultTimeout });
932
- },
933
- });
934
- mcpLogger.debug(`[ExternalServerManager] Registered tool with main registry: ${toolId}`);
935
- }
936
- catch (registrationError) {
937
- mcpLogger.warn(`[ExternalServerManager] Failed to register tool ${toolId} with main registry:`, registrationError);
938
- }
930
+ registrations.push(toolRegistry.registerTool(toolId, toolInfo, {
931
+ execute: async (params) => await this.executeTool(serverId, toolName, params, {
932
+ timeout: this.config.defaultTimeout,
933
+ }),
934
+ }));
939
935
  }
940
- mcpLogger.info(`[ExternalServerManager] Successfully registered ${instance.toolsMap.size} tools with main registry for ${serverId}`);
936
+ const results = await Promise.allSettled(registrations);
937
+ const ok = results.filter((r) => r.status === "fulfilled").length;
938
+ const failed = results.length - ok;
939
+ mcpLogger.info(`[ExternalServerManager] Registered ${ok}/${results.length} tools with main registry for ${serverId}${failed ? ` (${failed} failed)` : ""}`);
941
940
  }
942
941
  catch (error) {
943
942
  mcpLogger.error(`[ExternalServerManager] Failed to register tools with main registry for ${serverId}:`, error);
@@ -0,0 +1,50 @@
1
+ /**
2
+ * FlexibleToolValidator - Universal Safety Checks Only
3
+ *
4
+ * Following Anthropic's MCP specification which intentionally leaves tool naming flexible,
5
+ * this validator only blocks truly dangerous cases to support maximum MCP tool compatibility.
6
+ *
7
+ * Phase 1 Implementation:
8
+ * - Universal safety checks only (empty names, control characters, excessive length)
9
+ * - No context-specific validation or arbitrary pattern restrictions
10
+ * - Designed to support ALL legitimate MCP tools (github.create_repo, filesystem.read_file, etc.)
11
+ */
12
+ export interface FlexibleValidationResult {
13
+ isValid: boolean;
14
+ error?: string;
15
+ warnings?: string[];
16
+ }
17
+ export declare class FlexibleToolValidator {
18
+ private static readonly MAX_TOOL_NAME_LENGTH;
19
+ private static readonly MIN_TOOL_NAME_LENGTH;
20
+ /**
21
+ * Validate tool name with universal safety checks only
22
+ *
23
+ * This method only blocks truly dangerous cases:
24
+ * 1. Empty or whitespace-only names
25
+ * 2. Control characters that could break systems
26
+ * 3. Excessively long names that could cause memory issues
27
+ *
28
+ * Everything else is allowed to support maximum MCP tool compatibility.
29
+ */
30
+ static validateToolName(toolId: string): FlexibleValidationResult;
31
+ /**
32
+ * Validate tool information with minimal safety checks
33
+ */
34
+ static validateToolInfo(toolId: string, toolInfo: {
35
+ description?: string;
36
+ serverId?: string;
37
+ }): FlexibleValidationResult;
38
+ /**
39
+ * Get information about what this validator checks
40
+ */
41
+ static getValidationInfo(): {
42
+ philosophy: string;
43
+ checks: string[];
44
+ whatIsAllowed: string[];
45
+ examples: {
46
+ valid: string[];
47
+ invalid: string[];
48
+ };
49
+ };
50
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * FlexibleToolValidator - Universal Safety Checks Only
3
+ *
4
+ * Following Anthropic's MCP specification which intentionally leaves tool naming flexible,
5
+ * this validator only blocks truly dangerous cases to support maximum MCP tool compatibility.
6
+ *
7
+ * Phase 1 Implementation:
8
+ * - Universal safety checks only (empty names, control characters, excessive length)
9
+ * - No context-specific validation or arbitrary pattern restrictions
10
+ * - Designed to support ALL legitimate MCP tools (github.create_repo, filesystem.read_file, etc.)
11
+ */
12
+ import { registryLogger } from "../utils/logger.js";
13
+ export class FlexibleToolValidator {
14
+ // Universal safety limits (generous to support all legitimate tools)
15
+ static MAX_TOOL_NAME_LENGTH = 1000; // Much more generous than npm's 214
16
+ static MIN_TOOL_NAME_LENGTH = 1;
17
+ /**
18
+ * Validate tool name with universal safety checks only
19
+ *
20
+ * This method only blocks truly dangerous cases:
21
+ * 1. Empty or whitespace-only names
22
+ * 2. Control characters that could break systems
23
+ * 3. Excessively long names that could cause memory issues
24
+ *
25
+ * Everything else is allowed to support maximum MCP tool compatibility.
26
+ */
27
+ static validateToolName(toolId) {
28
+ const warnings = [];
29
+ // Safety Check 1: Empty or whitespace-only names
30
+ if (!toolId || typeof toolId !== "string") {
31
+ return {
32
+ isValid: false,
33
+ error: "Tool name is required and must be a string",
34
+ };
35
+ }
36
+ // Safety Check 2: Control characters that could break systems (check BEFORE trimming!)
37
+ // Only block truly dangerous control characters, not printable characters
38
+ //
39
+ // This regex blocks dangerous C0 control characters and DEL:
40
+ // - \x00-\x08: NULL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS
41
+ // - \x0B: Vertical Tab (VT)
42
+ // - \x0C: Form Feed (FF)
43
+ // - \x0E-\x1F: SO, SI, DLE, DC1-4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS-US
44
+ // - \x7F: DEL
45
+ //
46
+ // Explicitly ALLOWS these printable control characters:
47
+ // - \x09: TAB (horizontal tab) - commonly used in text
48
+ // - \x0A: LF (line feed) - commonly used in text
49
+ // - \x0D: CR (carriage return) - commonly used in text
50
+ // eslint-disable-next-line no-control-regex
51
+ const hasControlCharacters = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(toolId);
52
+ if (hasControlCharacters) {
53
+ return {
54
+ isValid: false,
55
+ error: "Tool name contains control characters that could break systems",
56
+ };
57
+ }
58
+ const trimmedName = toolId.trim();
59
+ if (trimmedName.length === 0) {
60
+ return {
61
+ isValid: false,
62
+ error: "Tool name cannot be empty or whitespace-only",
63
+ };
64
+ }
65
+ // Safety Check 3: Length limits (very generous)
66
+ if (trimmedName.length < this.MIN_TOOL_NAME_LENGTH) {
67
+ return {
68
+ isValid: false,
69
+ error: `Tool name must be at least ${this.MIN_TOOL_NAME_LENGTH} character long`,
70
+ };
71
+ }
72
+ if (trimmedName.length > this.MAX_TOOL_NAME_LENGTH) {
73
+ return {
74
+ isValid: false,
75
+ error: `Tool name exceeds maximum length of ${this.MAX_TOOL_NAME_LENGTH} characters`,
76
+ };
77
+ }
78
+ // Optional warnings for unusual but not dangerous patterns
79
+ if (trimmedName !== toolId) {
80
+ warnings.push("Tool name has leading/trailing whitespace (will be trimmed)");
81
+ }
82
+ if (trimmedName.length > 200) {
83
+ warnings.push("Tool name is unusually long but allowed");
84
+ }
85
+ registryLogger.debug(`✅ FlexibleToolValidator: Tool '${toolId}' passed universal safety checks`);
86
+ return {
87
+ isValid: true,
88
+ warnings: warnings.length > 0 ? warnings : undefined,
89
+ };
90
+ }
91
+ /**
92
+ * Validate tool information with minimal safety checks
93
+ */
94
+ static validateToolInfo(toolId, toolInfo) {
95
+ // First validate the tool name
96
+ const nameValidation = this.validateToolName(toolId);
97
+ if (!nameValidation.isValid) {
98
+ return nameValidation;
99
+ }
100
+ const warnings = [...(nameValidation.warnings || [])];
101
+ // Minimal safety checks for tool info
102
+ if (toolInfo.description && typeof toolInfo.description !== "string") {
103
+ return {
104
+ isValid: false,
105
+ error: "Tool description must be a string if provided",
106
+ };
107
+ }
108
+ if (toolInfo.serverId && typeof toolInfo.serverId !== "string") {
109
+ return {
110
+ isValid: false,
111
+ error: "Tool serverId must be a string if provided",
112
+ };
113
+ }
114
+ registryLogger.debug(`✅ FlexibleToolValidator: Tool info for '${toolId}' passed validation`);
115
+ return {
116
+ isValid: true,
117
+ warnings: warnings.length > 0 ? warnings : undefined,
118
+ };
119
+ }
120
+ /**
121
+ * Get information about what this validator checks
122
+ */
123
+ static getValidationInfo() {
124
+ return {
125
+ philosophy: "Maximum flexibility with universal safety only - following Anthropic's MCP specification",
126
+ checks: [
127
+ "Empty or whitespace-only names",
128
+ "Excessive length (over 1000 characters)",
129
+ "Control characters that could break systems",
130
+ ],
131
+ whatIsAllowed: [
132
+ "Dots (github.create_repo, filesystem.read_file)",
133
+ "Hyphens and underscores (my-tool, user_helper)",
134
+ "Numbers (tool1, my_tool_v2)",
135
+ "Unicode characters (🚀_tool, café_manager)",
136
+ "Mixed case (createRepo, ReadFile)",
137
+ "Long descriptive names (enterprise_database_connection_manager)",
138
+ "Any legitimate MCP tool naming pattern",
139
+ ],
140
+ examples: {
141
+ valid: [
142
+ "github.create_repo",
143
+ "filesystem.read_file",
144
+ "my-custom-tool",
145
+ "user_helper",
146
+ "tool1",
147
+ "🚀_rocket_tool",
148
+ "enterprise.database.connection.manager",
149
+ "UPPERCASE_TOOL",
150
+ "mixed_Case.Tool-Name_123",
151
+ ],
152
+ invalid: [
153
+ "", // Empty
154
+ " ", // Whitespace only
155
+ "tool\x00", // Control character
156
+ "a".repeat(1001), // Too long
157
+ ],
158
+ },
159
+ };
160
+ }
161
+ }
@@ -29,7 +29,7 @@ export interface ToolExecutionOptions {
29
29
  }
30
30
  export declare class MCPToolRegistry extends MCPRegistry {
31
31
  private tools;
32
- private toolImpls;
32
+ private toolImplementations;
33
33
  private toolExecutionStats;
34
34
  private builtInServerInfos;
35
35
  constructor();
@@ -122,7 +122,7 @@ export declare class MCPToolRegistry extends MCPRegistry {
122
122
  * Register a tool with implementation directly
123
123
  * This is used for external MCP server tools
124
124
  */
125
- registerTool(toolId: string, toolInfo: ToolInfo, toolImpl: ToolImplementation): void;
125
+ registerTool(toolId: string, toolInfo: ToolInfo, toolImpl: ToolImplementation): Promise<void>;
126
126
  /**
127
127
  * Remove a tool
128
128
  */
@@ -8,9 +8,10 @@ import { randomUUID } from "crypto";
8
8
  import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
9
9
  import { directAgentTools } from "../agent/directTools.js";
10
10
  import { detectCategory, createMCPServerInfo } from "../utils/mcpDefaults.js";
11
+ import { FlexibleToolValidator } from "./flexibleToolValidator.js";
11
12
  export class MCPToolRegistry extends MCPRegistry {
12
13
  tools = new Map();
13
- toolImpls = new Map(); // Store actual tool implementations
14
+ toolImplementations = new Map(); // Store actual tool implementations
14
15
  toolExecutionStats = new Map();
15
16
  builtInServerInfos = []; // DIRECT storage for MCPServerInfo
16
17
  constructor() {
@@ -38,7 +39,7 @@ export class MCPToolRegistry extends MCPRegistry {
38
39
  category: detectCategory({ isBuiltIn: true, serverId: "direct" }),
39
40
  };
40
41
  this.tools.set(toolId, toolInfo);
41
- this.toolImpls.set(toolId, {
42
+ this.toolImplementations.set(toolId, {
42
43
  execute: async (params, context) => {
43
44
  try {
44
45
  // Direct tools from AI SDK expect their specific parameter structure
@@ -153,7 +154,7 @@ export class MCPToolRegistry extends MCPRegistry {
153
154
  // Register only with fully-qualified toolId to avoid collisions
154
155
  this.tools.set(toolId, toolInfo);
155
156
  // Store the actual tool implementation for execution using toolId as key
156
- this.toolImpls.set(toolId, {
157
+ this.toolImplementations.set(toolId, {
157
158
  execute: tool.execute ||
158
159
  (async () => {
159
160
  throw new Error(`Tool ${tool.name} has no execute function`);
@@ -234,9 +235,9 @@ export class MCPToolRegistry extends MCPRegistry {
234
235
  ...context,
235
236
  };
236
237
  // Get the tool implementation using the resolved toolId
237
- const toolImpl = this.toolImpls.get(toolId);
238
+ const toolImpl = this.toolImplementations.get(toolId);
238
239
  registryLogger.debug(`Looking for tool '${toolName}' (toolId: '${toolId}'), found: ${!!toolImpl}, type: ${typeof toolImpl?.execute}`);
239
- registryLogger.debug(`Available tools:`, Array.from(this.toolImpls.keys()));
240
+ registryLogger.debug(`Available tools:`, Array.from(this.toolImplementations.keys()));
240
241
  if (!toolImpl || typeof toolImpl?.execute !== "function") {
241
242
  throw new Error(`Tool '${toolName}' implementation not found or not executable`);
242
243
  }
@@ -450,55 +451,25 @@ export class MCPToolRegistry extends MCPRegistry {
450
451
  * Register a tool with implementation directly
451
452
  * This is used for external MCP server tools
452
453
  */
453
- registerTool(toolId, toolInfo, toolImpl) {
454
+ async registerTool(toolId, toolInfo, toolImpl) {
454
455
  registryLogger.debug(`Registering tool: ${toolId}`);
455
- // Import validation functions synchronously - they are pure functions
456
- let validateTool;
457
- let isToolNameAvailable;
458
- let suggestToolNames;
459
- try {
460
- // Try ES module import first
461
- const toolRegistrationModule = require("../sdk/toolRegistration.js");
462
- ({ validateTool, isToolNameAvailable, suggestToolNames } =
463
- toolRegistrationModule);
464
- }
465
- catch (error) {
466
- // Fallback: skip validation if import fails (graceful degradation)
467
- registryLogger.warn("Tool validation module not available, skipping advanced validation", {
468
- error: error instanceof Error ? error.message : String(error),
469
- });
470
- // Create minimal validation functions
471
- validateTool = () => { }; // No-op
472
- isToolNameAvailable = () => true; // Allow all names
473
- suggestToolNames = () => ["alternative_tool"];
474
- }
475
- // Check if tool name is available (not reserved)
476
- if (!isToolNameAvailable(toolId)) {
477
- const suggestions = suggestToolNames(toolId);
478
- registryLogger.error(`Tool registration failed for ${toolId}: Name not available`);
479
- throw new Error(`Tool name '${toolId}' is not available (reserved or invalid format). ` +
480
- `Suggested alternatives: ${suggestions.slice(0, 3).join(", ")}`);
456
+ // Universal safety validation using FlexibleToolValidator
457
+ // Only blocks truly dangerous cases to support maximum MCP tool compatibility
458
+ const validation = FlexibleToolValidator.validateToolInfo(toolId, {
459
+ description: toolInfo.description,
460
+ serverId: toolInfo.serverId,
461
+ });
462
+ if (!validation.isValid) {
463
+ registryLogger.error(`Tool registration failed for ${toolId}: ${validation.error}`);
464
+ throw new Error(`Tool validation failed: ${validation.error}`);
481
465
  }
482
- // Create a simplified tool object for validation
483
- const toolForValidation = {
484
- description: toolInfo.description || "",
485
- execute: async () => "",
486
- parameters: undefined,
487
- metadata: {
488
- category: toolInfo.category,
489
- serverId: toolInfo.serverId,
490
- },
491
- };
492
- // Use comprehensive validation logic
493
- try {
494
- validateTool(toolId, toolForValidation);
495
- }
496
- catch (error) {
497
- registryLogger.error(`Tool registration failed for ${toolId}:`, error instanceof Error ? error.message : String(error));
498
- throw error;
466
+ // Log any warnings but allow registration to proceed
467
+ if (validation.warnings && validation.warnings.length > 0) {
468
+ registryLogger.warn(`Tool registration warnings for ${toolId}:`, validation.warnings);
499
469
  }
470
+ registryLogger.debug(`✅ Tool '${toolId}' passed flexible validation - registration proceeding`);
500
471
  this.tools.set(toolId, toolInfo);
501
- this.toolImpls.set(toolId, toolImpl);
472
+ this.toolImplementations.set(toolId, toolImpl);
502
473
  registryLogger.debug(`Successfully registered tool: ${toolId}`);
503
474
  }
504
475
  /**
@@ -509,6 +480,7 @@ export class MCPToolRegistry extends MCPRegistry {
509
480
  let removed = false;
510
481
  if (this.tools.has(toolName)) {
511
482
  this.tools.delete(toolName);
483
+ this.toolImplementations.delete(toolName); // Fix memory leak
512
484
  this.toolExecutionStats.delete(toolName);
513
485
  registryLogger.info(`Removed tool: ${toolName}`);
514
486
  removed = true;
@@ -518,6 +490,7 @@ export class MCPToolRegistry extends MCPRegistry {
518
490
  for (const [toolId, tool] of Array.from(this.tools.entries())) {
519
491
  if (tool.name === toolName) {
520
492
  this.tools.delete(toolId);
493
+ this.toolImplementations.delete(toolId); // Fix memory leak
521
494
  this.toolExecutionStats.delete(toolId);
522
495
  registryLogger.info(`Removed tool: ${toolId}`);
523
496
  removed = true;
@@ -567,6 +540,8 @@ export class MCPToolRegistry extends MCPRegistry {
567
540
  for (const [toolId, tool] of this.tools.entries()) {
568
541
  if (tool.serverId === serverId) {
569
542
  this.tools.delete(toolId);
543
+ this.toolImplementations.delete(toolId); // Fix memory leak
544
+ this.toolExecutionStats.delete(toolId); // Fix memory leak
570
545
  removedTools.push(toolId);
571
546
  }
572
547
  }
@@ -54,6 +54,8 @@ export declare class NeuroLink {
54
54
  * @param toolName - Name of the tool
55
55
  * @param startTime - Timestamp when tool execution started
56
56
  * @param success - Whether the tool execution was successful
57
+ * @param result - The result of the tool execution (optional)
58
+ * @param error - The error if execution failed (optional)
57
59
  */
58
60
  private emitToolEndEvent;
59
61
  private conversationMemory?;