@juspay/neurolink 7.13.0 → 7.14.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 (55) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +89 -25
  3. package/dist/config/conversationMemoryConfig.js +2 -1
  4. package/dist/context/ContextManager.js +15 -4
  5. package/dist/context/config.js +5 -1
  6. package/dist/context/utils.js +1 -1
  7. package/dist/core/baseProvider.d.ts +16 -1
  8. package/dist/core/baseProvider.js +208 -9
  9. package/dist/core/conversationMemoryManager.js +3 -2
  10. package/dist/core/factory.js +13 -2
  11. package/dist/factories/providerFactory.js +5 -11
  12. package/dist/factories/providerRegistry.js +2 -2
  13. package/dist/lib/config/conversationMemoryConfig.js +2 -1
  14. package/dist/lib/context/ContextManager.js +15 -4
  15. package/dist/lib/context/config.js +5 -1
  16. package/dist/lib/context/utils.js +1 -1
  17. package/dist/lib/core/baseProvider.d.ts +16 -1
  18. package/dist/lib/core/baseProvider.js +208 -9
  19. package/dist/lib/core/conversationMemoryManager.js +3 -2
  20. package/dist/lib/core/factory.js +13 -2
  21. package/dist/lib/factories/providerFactory.js +5 -11
  22. package/dist/lib/factories/providerRegistry.js +2 -2
  23. package/dist/lib/mcp/externalServerManager.d.ts +115 -0
  24. package/dist/lib/mcp/externalServerManager.js +677 -0
  25. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
  26. package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
  27. package/dist/lib/mcp/mcpClientFactory.d.ts +104 -0
  28. package/dist/lib/mcp/mcpClientFactory.js +416 -0
  29. package/dist/lib/mcp/toolDiscoveryService.d.ts +192 -0
  30. package/dist/lib/mcp/toolDiscoveryService.js +578 -0
  31. package/dist/lib/neurolink.d.ts +111 -16
  32. package/dist/lib/neurolink.js +517 -50
  33. package/dist/lib/providers/googleVertex.d.ts +1 -1
  34. package/dist/lib/providers/googleVertex.js +23 -7
  35. package/dist/lib/types/externalMcp.d.ts +282 -0
  36. package/dist/lib/types/externalMcp.js +6 -0
  37. package/dist/lib/types/generateTypes.d.ts +0 -1
  38. package/dist/lib/types/index.d.ts +1 -0
  39. package/dist/mcp/externalServerManager.d.ts +115 -0
  40. package/dist/mcp/externalServerManager.js +677 -0
  41. package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
  42. package/dist/mcp/mcpCircuitBreaker.js +338 -0
  43. package/dist/mcp/mcpClientFactory.d.ts +104 -0
  44. package/dist/mcp/mcpClientFactory.js +416 -0
  45. package/dist/mcp/toolDiscoveryService.d.ts +192 -0
  46. package/dist/mcp/toolDiscoveryService.js +578 -0
  47. package/dist/neurolink.d.ts +111 -16
  48. package/dist/neurolink.js +517 -50
  49. package/dist/providers/googleVertex.d.ts +1 -1
  50. package/dist/providers/googleVertex.js +23 -7
  51. package/dist/types/externalMcp.d.ts +282 -0
  52. package/dist/types/externalMcp.js +6 -0
  53. package/dist/types/generateTypes.d.ts +0 -1
  54. package/dist/types/index.d.ts +1 -0
  55. package/package.json +1 -1
@@ -0,0 +1,416 @@
1
+ /**
2
+ * MCP Client Factory
3
+ * Creates and manages MCP clients for external servers
4
+ * Supports stdio, SSE, and WebSocket transports
5
+ */
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
9
+ import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
10
+ import { spawn } from "child_process";
11
+ import { mcpLogger } from "../utils/logger.js";
12
+ import { globalCircuitBreakerManager, } from "./mcpCircuitBreaker.js";
13
+ /**
14
+ * MCPClientFactory
15
+ * Factory class for creating MCP clients with different transports
16
+ */
17
+ export class MCPClientFactory {
18
+ static NEUROLINK_IMPLEMENTATION = {
19
+ name: "neurolink-sdk",
20
+ version: "1.0.0",
21
+ };
22
+ static DEFAULT_CAPABILITIES = {
23
+ tools: {},
24
+ resources: {},
25
+ prompts: {},
26
+ sampling: {},
27
+ roots: {
28
+ listChanged: false,
29
+ },
30
+ };
31
+ /**
32
+ * Create an MCP client for the given server configuration
33
+ */
34
+ static async createClient(config, timeout = 10000) {
35
+ const startTime = Date.now();
36
+ try {
37
+ mcpLogger.info(`[MCPClientFactory] Creating client for ${config.id}`, {
38
+ transport: config.transport,
39
+ command: config.command,
40
+ });
41
+ // Create circuit breaker for this server
42
+ const circuitBreaker = globalCircuitBreakerManager.getBreaker(`mcp-client-${config.id}`, {
43
+ failureThreshold: 3,
44
+ resetTimeout: 30000,
45
+ operationTimeout: timeout,
46
+ });
47
+ // Create client with circuit breaker protection
48
+ const result = await circuitBreaker.execute(async () => {
49
+ return await this.createClientInternal(config, timeout);
50
+ });
51
+ mcpLogger.info(`[MCPClientFactory] Client created successfully for ${config.id}`, {
52
+ duration: Date.now() - startTime,
53
+ capabilities: result.capabilities,
54
+ });
55
+ return {
56
+ ...result,
57
+ success: true,
58
+ duration: Date.now() - startTime,
59
+ };
60
+ }
61
+ catch (error) {
62
+ const errorMessage = error instanceof Error ? error.message : String(error);
63
+ mcpLogger.error(`[MCPClientFactory] Failed to create client for ${config.id}:`, error);
64
+ return {
65
+ success: false,
66
+ error: errorMessage,
67
+ duration: Date.now() - startTime,
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Internal client creation logic
73
+ */
74
+ static async createClientInternal(config, timeout) {
75
+ // Create transport
76
+ const { transport, process } = await this.createTransport(config);
77
+ try {
78
+ // Create client
79
+ const client = new Client(this.NEUROLINK_IMPLEMENTATION, {
80
+ capabilities: this.DEFAULT_CAPABILITIES,
81
+ });
82
+ // Connect with timeout
83
+ await Promise.race([
84
+ client.connect(transport),
85
+ this.createTimeoutPromise(timeout, `Client connection timeout for ${config.id}`),
86
+ ]);
87
+ // Perform handshake to get server capabilities
88
+ const serverCapabilities = await this.performHandshake(client, timeout);
89
+ mcpLogger.debug(`[MCPClientFactory] Handshake completed for ${config.id}`, {
90
+ capabilities: serverCapabilities,
91
+ });
92
+ return {
93
+ client,
94
+ transport,
95
+ process,
96
+ capabilities: serverCapabilities,
97
+ };
98
+ }
99
+ catch (error) {
100
+ // Clean up on failure
101
+ try {
102
+ await transport.close();
103
+ }
104
+ catch (closeError) {
105
+ mcpLogger.debug(`[MCPClientFactory] Error closing transport during cleanup:`, closeError);
106
+ }
107
+ if (process && !process.killed) {
108
+ process.kill("SIGTERM");
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+ /**
114
+ * Create transport based on configuration
115
+ */
116
+ static async createTransport(config) {
117
+ switch (config.transport) {
118
+ case "stdio":
119
+ return this.createStdioTransport(config);
120
+ case "sse":
121
+ return this.createSSETransport(config);
122
+ case "websocket":
123
+ return this.createWebSocketTransport(config);
124
+ default:
125
+ throw new Error(`Unsupported transport type: ${config.transport}`);
126
+ }
127
+ }
128
+ /**
129
+ * Create stdio transport with process spawning
130
+ */
131
+ static async createStdioTransport(config) {
132
+ mcpLogger.debug(`[MCPClientFactory] Creating stdio transport for ${config.id}`, {
133
+ command: config.command,
134
+ args: config.args,
135
+ });
136
+ // Spawn the process
137
+ const childProcess = spawn(config.command, config.args, {
138
+ stdio: ["pipe", "pipe", "pipe"],
139
+ env: {
140
+ ...process.env,
141
+ ...config.env,
142
+ },
143
+ cwd: config.cwd,
144
+ });
145
+ // Handle process errors
146
+ const processErrorPromise = new Promise((_, reject) => {
147
+ childProcess.on("error", (error) => {
148
+ reject(new Error(`Process spawn error: ${error.message}`));
149
+ });
150
+ childProcess.on("exit", (code, signal) => {
151
+ if (code !== 0) {
152
+ reject(new Error(`Process exited with code ${code}, signal ${signal}`));
153
+ }
154
+ });
155
+ });
156
+ // Wait for process to be ready or fail
157
+ await Promise.race([
158
+ new Promise((resolve) => setTimeout(resolve, 1000)), // Give process time to start
159
+ processErrorPromise,
160
+ ]);
161
+ // Check if process is still running
162
+ if (childProcess.killed || childProcess.exitCode !== null) {
163
+ throw new Error("Process failed to start or exited immediately");
164
+ }
165
+ // Create transport
166
+ const transport = new StdioClientTransport({
167
+ command: config.command,
168
+ args: config.args,
169
+ env: Object.fromEntries(Object.entries({
170
+ ...process.env,
171
+ ...config.env,
172
+ }).filter(([, value]) => value !== undefined)),
173
+ cwd: config.cwd,
174
+ });
175
+ return { transport, process: childProcess };
176
+ }
177
+ /**
178
+ * Create SSE transport
179
+ */
180
+ static async createSSETransport(config) {
181
+ if (!config.url) {
182
+ throw new Error("URL is required for SSE transport");
183
+ }
184
+ mcpLogger.debug(`[MCPClientFactory] Creating SSE transport for ${config.id}`, {
185
+ url: config.url,
186
+ });
187
+ try {
188
+ const url = new URL(config.url);
189
+ const transport = new SSEClientTransport(url);
190
+ return { transport };
191
+ }
192
+ catch (error) {
193
+ throw new Error(`Invalid SSE URL: ${error instanceof Error ? error.message : String(error)}`);
194
+ }
195
+ }
196
+ /**
197
+ * Create WebSocket transport
198
+ */
199
+ static async createWebSocketTransport(config) {
200
+ if (!config.url) {
201
+ throw new Error("URL is required for WebSocket transport");
202
+ }
203
+ mcpLogger.debug(`[MCPClientFactory] Creating WebSocket transport for ${config.id}`, {
204
+ url: config.url,
205
+ });
206
+ try {
207
+ const url = new URL(config.url);
208
+ const transport = new WebSocketClientTransport(url);
209
+ return { transport };
210
+ }
211
+ catch (error) {
212
+ throw new Error(`Invalid WebSocket URL: ${error instanceof Error ? error.message : String(error)}`);
213
+ }
214
+ }
215
+ /**
216
+ * Perform MCP handshake and get server capabilities
217
+ */
218
+ static async performHandshake(client, timeout) {
219
+ try {
220
+ // The MCP SDK handles the handshake automatically during connect()
221
+ // We can request server info to verify the connection
222
+ const serverInfo = await Promise.race([
223
+ this.getServerInfo(client),
224
+ this.createTimeoutPromise(timeout, "Handshake timeout"),
225
+ ]);
226
+ // Extract capabilities from server info
227
+ return this.extractCapabilities(serverInfo);
228
+ }
229
+ catch (error) {
230
+ mcpLogger.warn("[MCPClientFactory] Handshake failed, but connection may still be valid:", error);
231
+ // Return default capabilities if handshake fails
232
+ // The connection might still work for basic operations
233
+ return this.DEFAULT_CAPABILITIES;
234
+ }
235
+ }
236
+ /**
237
+ * Get server information
238
+ */
239
+ static async getServerInfo(client) {
240
+ try {
241
+ // Try to list tools to verify server is responding
242
+ const toolsResult = await client.listTools();
243
+ return {
244
+ tools: toolsResult.tools || [],
245
+ capabilities: this.DEFAULT_CAPABILITIES,
246
+ };
247
+ }
248
+ catch (error) {
249
+ // If listing tools fails, try a simpler ping
250
+ mcpLogger.debug("[MCPClientFactory] Tool listing failed, server may not support tools yet");
251
+ return {
252
+ tools: [],
253
+ capabilities: this.DEFAULT_CAPABILITIES,
254
+ };
255
+ }
256
+ }
257
+ /**
258
+ * Extract capabilities from server info
259
+ */
260
+ static extractCapabilities(serverInfo) {
261
+ // For now, return default capabilities
262
+ // This can be enhanced when MCP servers provide more detailed capability info
263
+ return {
264
+ ...this.DEFAULT_CAPABILITIES,
265
+ tools: serverInfo.tools ? {} : undefined,
266
+ };
267
+ }
268
+ /**
269
+ * Create a timeout promise
270
+ */
271
+ static createTimeoutPromise(timeout, message) {
272
+ return new Promise((_, reject) => {
273
+ setTimeout(() => {
274
+ reject(new Error(message));
275
+ }, timeout);
276
+ });
277
+ }
278
+ /**
279
+ * Close an MCP client and clean up resources
280
+ */
281
+ static async closeClient(client, transport, process) {
282
+ const errors = [];
283
+ // Close client
284
+ try {
285
+ await client.close();
286
+ }
287
+ catch (error) {
288
+ errors.push(`Client close error: ${error instanceof Error ? error.message : String(error)}`);
289
+ }
290
+ // Close transport
291
+ try {
292
+ await transport.close();
293
+ }
294
+ catch (error) {
295
+ errors.push(`Transport close error: ${error instanceof Error ? error.message : String(error)}`);
296
+ }
297
+ // Kill process if exists
298
+ if (process && !process.killed) {
299
+ try {
300
+ process.kill("SIGTERM");
301
+ // Force kill after 5 seconds
302
+ setTimeout(() => {
303
+ if (!process.killed) {
304
+ mcpLogger.warn("[MCPClientFactory] Force killing process");
305
+ process.kill("SIGKILL");
306
+ }
307
+ }, 5000);
308
+ }
309
+ catch (error) {
310
+ errors.push(`Process kill error: ${error instanceof Error ? error.message : String(error)}`);
311
+ }
312
+ }
313
+ if (errors.length > 0) {
314
+ mcpLogger.warn("[MCPClientFactory] Errors during client cleanup:", errors);
315
+ }
316
+ }
317
+ /**
318
+ * Test connection to an MCP server
319
+ */
320
+ static async testConnection(config, timeout = 5000) {
321
+ let client;
322
+ let transport;
323
+ let process;
324
+ try {
325
+ const result = await this.createClient(config, timeout);
326
+ if (!result.success) {
327
+ return { success: false, error: result.error };
328
+ }
329
+ client = result.client;
330
+ transport = result.transport;
331
+ process = result.process;
332
+ // Try to list tools as a connectivity test
333
+ if (client) {
334
+ try {
335
+ await client.listTools();
336
+ }
337
+ catch (error) {
338
+ // Tool listing failure doesn't necessarily mean connection failure
339
+ mcpLogger.debug("[MCPClientFactory] Tool listing failed during test, but connection may be valid");
340
+ }
341
+ }
342
+ return {
343
+ success: true,
344
+ capabilities: result.capabilities,
345
+ };
346
+ }
347
+ catch (error) {
348
+ return {
349
+ success: false,
350
+ error: error instanceof Error ? error.message : String(error),
351
+ };
352
+ }
353
+ finally {
354
+ // Clean up test connection
355
+ if (client && transport) {
356
+ try {
357
+ await this.closeClient(client, transport, process);
358
+ }
359
+ catch (error) {
360
+ mcpLogger.debug("[MCPClientFactory] Error cleaning up test connection:", error);
361
+ }
362
+ }
363
+ }
364
+ }
365
+ /**
366
+ * Validate MCP server configuration for client creation
367
+ */
368
+ static validateClientConfig(config) {
369
+ const errors = [];
370
+ // Basic validation
371
+ if (!config.command) {
372
+ errors.push("Command is required");
373
+ }
374
+ if (!config.transport) {
375
+ errors.push("Transport is required");
376
+ }
377
+ if (!["stdio", "sse", "websocket"].includes(config.transport)) {
378
+ errors.push("Transport must be stdio, sse, or websocket");
379
+ }
380
+ // Transport-specific validation
381
+ if (config.transport === "sse" || config.transport === "websocket") {
382
+ if (!config.url) {
383
+ errors.push(`URL is required for ${config.transport} transport`);
384
+ }
385
+ else {
386
+ try {
387
+ new URL(config.url);
388
+ }
389
+ catch {
390
+ errors.push(`Invalid URL for ${config.transport} transport`);
391
+ }
392
+ }
393
+ }
394
+ if (config.transport === "stdio") {
395
+ if (!Array.isArray(config.args)) {
396
+ errors.push("Args array is required for stdio transport");
397
+ }
398
+ }
399
+ return {
400
+ isValid: errors.length === 0,
401
+ errors,
402
+ };
403
+ }
404
+ /**
405
+ * Get supported transport types
406
+ */
407
+ static getSupportedTransports() {
408
+ return ["stdio", "sse", "websocket"];
409
+ }
410
+ /**
411
+ * Get default client capabilities
412
+ */
413
+ static getDefaultCapabilities() {
414
+ return { ...this.DEFAULT_CAPABILITIES };
415
+ }
416
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Tool Discovery Service
3
+ * Automatically discovers and registers tools from external MCP servers
4
+ * Handles tool validation, transformation, and lifecycle management
5
+ */
6
+ import { EventEmitter } from "events";
7
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import type { ExternalMCPToolInfo, ExternalMCPToolResult, ExternalMCPToolContext } from "../types/externalMcp.js";
9
+ import type { JsonObject } from "../types/common.js";
10
+ /**
11
+ * Tool discovery result
12
+ */
13
+ export interface ToolDiscoveryResult {
14
+ /** Whether discovery was successful */
15
+ success: boolean;
16
+ /** Number of tools discovered */
17
+ toolCount: number;
18
+ /** Discovered tools */
19
+ tools: ExternalMCPToolInfo[];
20
+ /** Error message if failed */
21
+ error?: string;
22
+ /** Discovery duration in milliseconds */
23
+ duration: number;
24
+ /** Server ID */
25
+ serverId: string;
26
+ }
27
+ /**
28
+ * Tool execution options
29
+ */
30
+ export interface ToolExecutionOptions {
31
+ /** Execution timeout in milliseconds */
32
+ timeout?: number;
33
+ /** Additional context for execution */
34
+ context?: Partial<ExternalMCPToolContext>;
35
+ /** Whether to validate input parameters */
36
+ validateInput?: boolean;
37
+ /** Whether to validate output */
38
+ validateOutput?: boolean;
39
+ }
40
+ /**
41
+ * Tool validation result
42
+ */
43
+ export interface ToolValidationResult {
44
+ /** Whether the tool is valid */
45
+ isValid: boolean;
46
+ /** Validation errors */
47
+ errors: string[];
48
+ /** Validation warnings */
49
+ warnings: string[];
50
+ /** Tool metadata */
51
+ metadata?: {
52
+ category?: string;
53
+ complexity?: "simple" | "moderate" | "complex";
54
+ requiresAuth?: boolean;
55
+ isDeprecated?: boolean;
56
+ };
57
+ }
58
+ /**
59
+ * Tool registry events
60
+ */
61
+ export interface ToolRegistryEvents {
62
+ toolRegistered: {
63
+ serverId: string;
64
+ toolName: string;
65
+ toolInfo: ExternalMCPToolInfo;
66
+ timestamp: Date;
67
+ };
68
+ toolUnregistered: {
69
+ serverId: string;
70
+ toolName: string;
71
+ timestamp: Date;
72
+ };
73
+ toolUpdated: {
74
+ serverId: string;
75
+ toolName: string;
76
+ oldInfo: ExternalMCPToolInfo;
77
+ newInfo: ExternalMCPToolInfo;
78
+ timestamp: Date;
79
+ };
80
+ discoveryCompleted: {
81
+ serverId: string;
82
+ toolCount: number;
83
+ duration: number;
84
+ timestamp: Date;
85
+ };
86
+ discoveryFailed: {
87
+ serverId: string;
88
+ error: string;
89
+ timestamp: Date;
90
+ };
91
+ }
92
+ /**
93
+ * ToolDiscoveryService
94
+ * Handles automatic tool discovery and registration from external MCP servers
95
+ */
96
+ export declare class ToolDiscoveryService extends EventEmitter {
97
+ private toolRegistry;
98
+ private serverTools;
99
+ private discoveryInProgress;
100
+ constructor();
101
+ /**
102
+ * Discover tools from an external MCP server
103
+ */
104
+ discoverTools(serverId: string, client: Client, timeout?: number): Promise<ToolDiscoveryResult>;
105
+ /**
106
+ * Perform the actual tool discovery
107
+ */
108
+ private performToolDiscovery;
109
+ /**
110
+ * Register discovered tools
111
+ */
112
+ private registerDiscoveredTools;
113
+ /**
114
+ * Create tool info from MCP tool definition
115
+ */
116
+ private createToolInfo;
117
+ /**
118
+ * Infer tool category from tool definition
119
+ */
120
+ private inferToolCategory;
121
+ /**
122
+ * Validate a tool
123
+ */
124
+ private validateTool;
125
+ /**
126
+ * Infer tool complexity
127
+ */
128
+ private inferComplexity;
129
+ /**
130
+ * Infer if tool requires authentication
131
+ */
132
+ private inferAuthRequirement;
133
+ /**
134
+ * Execute a tool
135
+ */
136
+ executeTool(toolName: string, serverId: string, client: Client, parameters: JsonObject, options?: ToolExecutionOptions): Promise<ExternalMCPToolResult>;
137
+ /**
138
+ * Validate tool parameters
139
+ */
140
+ private validateToolParameters;
141
+ /**
142
+ * Validate parameter type
143
+ */
144
+ private validateParameterType;
145
+ /**
146
+ * Validate tool output
147
+ */
148
+ private validateToolOutput;
149
+ /**
150
+ * Update tool statistics
151
+ */
152
+ private updateToolStats;
153
+ /**
154
+ * Get tool by name and server
155
+ */
156
+ getTool(toolName: string, serverId: string): ExternalMCPToolInfo | undefined;
157
+ /**
158
+ * Get all tools for a server
159
+ */
160
+ getServerTools(serverId: string): ExternalMCPToolInfo[];
161
+ /**
162
+ * Get all registered tools
163
+ */
164
+ getAllTools(): ExternalMCPToolInfo[];
165
+ /**
166
+ * Clear tools for a server
167
+ */
168
+ clearServerTools(serverId: string): void;
169
+ /**
170
+ * Update tool availability
171
+ */
172
+ updateToolAvailability(toolName: string, serverId: string, isAvailable: boolean): void;
173
+ /**
174
+ * Create tool key for registry
175
+ */
176
+ private createToolKey;
177
+ /**
178
+ * Create timeout promise
179
+ */
180
+ private createTimeoutPromise;
181
+ /**
182
+ * Get discovery statistics
183
+ */
184
+ getStatistics(): {
185
+ totalTools: number;
186
+ availableTools: number;
187
+ unavailableTools: number;
188
+ totalServers: number;
189
+ toolsByServer: Record<string, number>;
190
+ toolsByCategory: Record<string, number>;
191
+ };
192
+ }