@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
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [7.14.0](https://github.com/juspay/neurolink/compare/v7.13.0...v7.14.0) (2025-08-14)
2
+
3
+ ### Features
4
+
5
+ - **(external-mcp):** add external MCP server integration support ([c03dee8](https://github.com/juspay/neurolink/commit/c03dee8dd7a2e06e78bc743d7b3a5cff858395de))
6
+
1
7
  ## [7.13.0](https://github.com/juspay/neurolink/compare/v7.12.0...v7.13.0) (2025-08-14)
2
8
 
3
9
  ### Features
package/README.md CHANGED
@@ -69,7 +69,7 @@ npx @juspay/neurolink sagemaker benchmark my-endpoint # Performance testing
69
69
  - **🛡️ Error Recovery** - Graceful failures with provider fallback and retry logic
70
70
  - **📊 Analytics & Evaluation** - Built-in usage tracking and AI-powered quality assessment
71
71
  - **🎯 Real-time Event Monitoring** - EventEmitter integration for progress tracking and debugging
72
- - **🔧 MCP Integration** - Model Context Protocol with 6 built-in tools and 58+ discoverable servers
72
+ - **🔧 External MCP Integration** - Model Context Protocol with 6 built-in tools + full external MCP server support
73
73
  - **🚀 Lighthouse Integration** - Unified tool registration API supporting both object and array formats for seamless Lighthouse tool import
74
74
 
75
75
  ---
@@ -118,6 +118,28 @@ npx @juspay/neurolink status # Check all providers
118
118
  ```bash
119
119
  # SDK Installation for using in your typescript projects
120
120
  npm install @juspay/neurolink
121
+
122
+ # 🆕 NEW: External MCP Server Integration Quick Test
123
+ node -e "
124
+ const { NeuroLink } = require('@juspay/neurolink');
125
+ (async () => {
126
+ const neurolink = new NeuroLink();
127
+
128
+ // Add external filesystem MCP server
129
+ await neurolink.addExternalMCPServer('filesystem', {
130
+ command: 'npx',
131
+ args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
132
+ transport: 'stdio'
133
+ });
134
+
135
+ // External tools automatically available in generate()
136
+ const result = await neurolink.generate({
137
+ input: { text: 'List files in the current directory' }
138
+ });
139
+ console.log('🎉 External MCP integration working!');
140
+ console.log(result.content);
141
+ })();
142
+ "
121
143
  ```
122
144
 
123
145
  ### Basic Usage
@@ -327,41 +349,56 @@ console.log(productData.name, productData.price, productData.features);
327
349
  - ⚡ **Automatic Fallback** - Never fail when providers are down, intelligent provider switching
328
350
  - 🖥️ **CLI + SDK** - Use from command line or integrate programmatically with TypeScript support
329
351
  - 🛡️ **Production Ready** - Enterprise-grade error handling, performance optimization, extracted from production
330
- - ✅ **MCP Integration** - Model Context Protocol with working built-in tools and 58+ discoverable external servers
352
+ - ✅ **External MCP Integration** - Model Context Protocol with built-in tools + full external MCP server support
331
353
  - 🔍 **Smart Model Resolution** - Fuzzy matching, aliases, and capability-based search across all providers
332
354
  - 🏠 **Local AI Support** - Run completely offline with Ollama or through LiteLLM proxy
333
355
  - 🌍 **Universal Model Access** - Direct providers + 100,000+ models via Hugging Face + 100+ models via LiteLLM
334
356
  - 🧠 **Automatic Context Summarization** - Stateful, long-running conversations with automatic history summarization.
335
357
  - 📊 **Analytics & Evaluation** - Built-in usage tracking and AI-powered quality assessment
336
358
 
337
- ## 🛠️ MCP Integration Status ✅ **BUILT-IN TOOLS WORKING**
359
+ ## 🛠️ External MCP Integration Status ✅ **PRODUCTION READY**
338
360
 
339
- | Component | Status | Description |
340
- | ------------------- | ------------------ | -------------------------------------------------------- |
341
- | Built-in Tools | ✅ **Working** | 6 core tools fully functional across all providers |
342
- | SDK Custom Tools | ✅ **Working** | Register custom tools programmatically |
343
- | External Discovery | 🔍 **Discovery** | 58+ MCP servers discovered from AI tools ecosystem |
344
- | Tool Execution | ✅ **Working** | Real-time AI tool calling with built-in tools |
345
- | **External Tools** | 🚧 **Development** | Manual config needs one-line fix, activation in progress |
346
- | **CLI Integration** | ✅ **READY** | **Production-ready with built-in tools** |
347
- | External Activation | 🔧 **Development** | Discovery complete, activation protocol in progress |
361
+ | Component | Status | Description |
362
+ | ---------------------- | -------------- | ---------------------------------------------------------------- |
363
+ | Built-in Tools | ✅ **Working** | 6 core tools fully functional across all providers |
364
+ | SDK Custom Tools | ✅ **Working** | Register custom tools programmatically |
365
+ | **External MCP Tools** | **Working** | **Full external MCP server support with dynamic tool discovery** |
366
+ | Tool Execution | ✅ **Working** | Real-time AI tool calling with all tool types |
367
+ | **Streaming Support** | **Working** | **External MCP tools work with streaming generation** |
368
+ | **Multi-Provider** | ✅ **Working** | **External tools work across all AI providers** |
369
+ | **CLI Integration** | **READY** | **Production-ready with external MCP support** |
348
370
 
349
- ### ✅ Quick MCP Test (v1.7.1)
371
+ ### ✅ External MCP Integration Demo
350
372
 
351
373
  ```bash
352
374
  # Test built-in tools (works immediately)
353
375
  npx @juspay/neurolink generate "What time is it?" --debug
354
376
 
355
- # Disable tools for pure text generation
356
- npx @juspay/neurolink generate "Write a poem" --disable-tools
377
+ # 🆕 NEW: External MCP server integration (SDK)
378
+ import { NeuroLink } from '@juspay/neurolink';
379
+
380
+ const neurolink = new NeuroLink();
381
+
382
+ // Add external MCP server (e.g., Bitbucket)
383
+ await neurolink.addExternalMCPServer('bitbucket', {
384
+ command: 'npx',
385
+ args: ['-y', '@nexus2520/bitbucket-mcp-server'],
386
+ transport: 'stdio',
387
+ env: {
388
+ BITBUCKET_USERNAME: process.env.BITBUCKET_USERNAME,
389
+ BITBUCKET_TOKEN: process.env.BITBUCKET_TOKEN,
390
+ BITBUCKET_BASE_URL: 'https://bitbucket.example.com'
391
+ }
392
+ });
393
+
394
+ // Use external MCP tools in generation
395
+ const result = await neurolink.generate({
396
+ input: { text: 'Get pull request #123 details from the main repository' },
397
+ disableTools: false // External MCP tools automatically available
398
+ });
357
399
 
358
400
  # Discover available MCP servers
359
401
  npx @juspay/neurolink mcp discover --format table
360
-
361
- # Install popular MCP servers (NEW: Bitbucket support added!)
362
- npx @juspay/neurolink mcp install filesystem
363
- npx @juspay/neurolink mcp install github
364
- npx @juspay/neurolink mcp install bitbucket # 🆕 NEW
365
402
  ```
366
403
 
367
404
  ### 🔧 SDK Custom Tool Registration (NEW!)
@@ -663,16 +700,43 @@ npx @juspay/neurolink generate "Hello!" --provider openai-compatible
663
700
  - **Extensibility**: Connect external tools and services via MCP protocol
664
701
  - **🆕 Dynamic Server Management**: Programmatically add MCP servers at runtime
665
702
 
666
- ### 🔧 Programmatic MCP Server Management [Coming Soon]
703
+ ### 🔧 External MCP Server Management **AVAILABLE NOW**
667
704
 
668
- **Note**: External MCP server activation is in development. Currently available:
705
+ **External MCP integration is now production-ready:**
669
706
 
670
707
  - ✅ 6 built-in tools working across all providers
671
708
  - ✅ SDK custom tool registration
672
- - 🔍 MCP server discovery (58+ servers found)
673
- - 🚧 External server activation (one-line fix pending)
709
+ - **External MCP server management** (add, remove, list, test servers)
710
+ - **Dynamic tool discovery** (automatic tool registration from external servers)
711
+ - ✅ **Multi-provider support** (external tools work with all AI providers)
712
+ - ✅ **Streaming integration** (external tools work with real-time streaming)
713
+ - ✅ **Enhanced tool tracking** (proper parameter extraction and execution logging)
674
714
 
675
- Manual MCP configuration (`.mcp-config.json`) support coming soon.
715
+ ```typescript
716
+ // Complete external MCP server API
717
+ const neurolink = new NeuroLink();
718
+
719
+ // Server management
720
+ await neurolink.addExternalMCPServer(serverId, config);
721
+ await neurolink.removeExternalMCPServer(serverId);
722
+ const servers = neurolink.listExternalMCPServers();
723
+ const server = neurolink.getExternalMCPServer(serverId);
724
+
725
+ // Tool management
726
+ const tools = neurolink.getExternalMCPTools();
727
+ const serverTools = neurolink.getExternalMCPServerTools(serverId);
728
+
729
+ // Direct tool execution
730
+ const result = await neurolink.executeExternalMCPTool(
731
+ serverId,
732
+ toolName,
733
+ params,
734
+ );
735
+
736
+ // Statistics and monitoring
737
+ const stats = neurolink.getExternalMCPStatistics();
738
+ await neurolink.shutdownExternalMCPServers();
739
+ ```
676
740
 
677
741
  ## 🤝 Contributing
678
742
 
@@ -34,6 +34,7 @@ export function getConversationMemoryDefaults() {
34
34
  return {
35
35
  enabled: process.env.NEUROLINK_MEMORY_ENABLED === "true",
36
36
  maxSessions: Number(process.env.NEUROLINK_MEMORY_MAX_SESSIONS) || DEFAULT_MAX_SESSIONS,
37
- maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) || DEFAULT_MAX_TURNS_PER_SESSION,
37
+ maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
38
+ DEFAULT_MAX_TURNS_PER_SESSION,
38
39
  };
39
40
  }
@@ -14,7 +14,10 @@ export class ContextManager {
14
14
  constructor(generatorFunction, config, initialContext = "This is the start of the conversation.") {
15
15
  this.internalGenerator = generatorFunction;
16
16
  this.config = config;
17
- const initialMessage = { role: "system", content: initialContext };
17
+ const initialMessage = {
18
+ role: "system",
19
+ content: initialContext,
20
+ };
18
21
  initialMessage.wordCount = this.config.estimateWordCount([initialMessage]);
19
22
  this.history = [initialMessage];
20
23
  this.wordCount = initialMessage.wordCount;
@@ -55,7 +58,9 @@ export class ContextManager {
55
58
  const result = await this.internalGenerator(textOptions);
56
59
  if (typeof result.content === "string" && result.content.length > 0) {
57
60
  // Replace the history with a single system message containing the summary
58
- const newHistory = [{ role: "system", content: result.content }];
61
+ const newHistory = [
62
+ { role: "system", content: result.content },
63
+ ];
59
64
  this.history = newHistory;
60
65
  this.wordCount = this.config.estimateWordCount(this.history);
61
66
  logger.info(`[ContextManager] Summarization complete. New history length: ${this.wordCount} words.`);
@@ -63,7 +68,10 @@ export class ContextManager {
63
68
  else {
64
69
  logger.warn("[ContextManager] Summarization returned empty or non-string content; truncating history as a fallback.");
65
70
  this._truncateHistory(this.config.lowWaterMarkWords);
66
- this.history.unshift({ role: "system", content: ContextManager.SUMMARIZATION_EMPTY_WARNING });
71
+ this.history.unshift({
72
+ role: "system",
73
+ content: ContextManager.SUMMARIZATION_EMPTY_WARNING,
74
+ });
67
75
  this.wordCount = this.config.estimateWordCount(this.history);
68
76
  }
69
77
  logger.debug(`[ContextManager] New history: ${JSON.stringify(this.history)}`);
@@ -72,7 +80,10 @@ export class ContextManager {
72
80
  logger.error("Context summarization failed:", { error });
73
81
  // Fallback strategy: truncate the history to the target word count.
74
82
  this._truncateHistory(this.config.lowWaterMarkWords);
75
- this.history.unshift({ role: "system", content: ContextManager.SUMMARIZATION_FAILED_WARNING });
83
+ this.history.unshift({
84
+ role: "system",
85
+ content: ContextManager.SUMMARIZATION_FAILED_WARNING,
86
+ });
76
87
  this.wordCount = this.config.estimateWordCount(this.history);
77
88
  }
78
89
  }
@@ -6,7 +6,11 @@ function estimateWordCount(history) {
6
6
  if (!history || history.length === 0) {
7
7
  return 0;
8
8
  }
9
- return history.reduce((acc, msg) => acc + (msg.content.trim().split(/\s+/).filter(word => word.length > 0).length || 0), 0);
9
+ return history.reduce((acc, msg) => acc +
10
+ (msg.content
11
+ .trim()
12
+ .split(/\s+/)
13
+ .filter((word) => word.length > 0).length || 0), 0);
10
14
  }
11
15
  /**
12
16
  * Generates the default prompt for summarization.
@@ -4,5 +4,5 @@
4
4
  * @returns A formatted string representing the conversation.
5
5
  */
6
6
  export function formatHistoryToString(history) {
7
- return history.map(msg => `${msg.role}: ${msg.content}`).join('\n\n');
7
+ return history.map((msg) => `${msg.role}: ${msg.content}`).join("\n\n");
8
8
  }
@@ -6,7 +6,7 @@ import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
6
6
  import type { JsonValue, UnknownRecord } from "../types/common.js";
7
7
  import type { ToolResult } from "../types/tools.js";
8
8
  /**
9
- * Interface for SDK with in-memory MCP servers
9
+ * Interface for SDK with in-memory MCP servers and external MCP support
10
10
  */
11
11
  export interface NeuroLinkSDK {
12
12
  getInMemoryServers?: () => Map<string, {
@@ -18,6 +18,16 @@ export interface NeuroLinkSDK {
18
18
  category?: string;
19
19
  metadata?: UnknownRecord;
20
20
  }>;
21
+ externalServerManager?: {
22
+ getAllTools: () => Array<{
23
+ name: string;
24
+ description: string;
25
+ serverId: string;
26
+ isAvailable: boolean;
27
+ inputSchema?: Record<string, unknown>;
28
+ }>;
29
+ executeTool: (serverId: string, toolName: string, params: any) => Promise<any>;
30
+ };
21
31
  }
22
32
  /**
23
33
  * Interface for tool information in MCP servers
@@ -86,6 +96,11 @@ export declare abstract class BaseProvider implements AIProvider {
86
96
  * MCP tools are added when available (without blocking)
87
97
  */
88
98
  protected getAllTools(): Promise<Record<string, Tool>>;
99
+ /**
100
+ * Convert MCP JSON Schema to Zod schema for AI SDK tools
101
+ * Handles common MCP schema patterns safely
102
+ */
103
+ private convertMCPSchemaToZod;
89
104
  /**
90
105
  * Set session context for MCP tools
91
106
  */
@@ -157,10 +157,20 @@ export class BaseProvider {
157
157
  try {
158
158
  // Import generateText dynamically to avoid circular dependencies
159
159
  const { generateText } = await import("ai");
160
- // Get ALL available tools (direct + MCP when available)
160
+ // Get ALL available tools (direct + MCP + external from options)
161
161
  const shouldUseTools = !options.disableTools && this.supportsTools();
162
- const tools = shouldUseTools ? await this.getAllTools() : {};
163
- logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}: ${Object.keys(tools).join(", ")}`);
162
+ const baseTools = shouldUseTools ? await this.getAllTools() : {};
163
+ const tools = shouldUseTools
164
+ ? {
165
+ ...baseTools,
166
+ ...(options.tools || {}), // Include external tools passed from NeuroLink
167
+ }
168
+ : {};
169
+ logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}:`, {
170
+ directTools: Object.keys(baseTools),
171
+ externalTools: Object.keys(options.tools || {}),
172
+ totalTools: Object.keys(tools),
173
+ });
164
174
  // EVERY provider uses Vercel AI SDK - no exceptions
165
175
  const model = await this.getAISDKModel(); // This method is now REQUIRED
166
176
  // Build proper message array with conversation history
@@ -201,22 +211,66 @@ export class BaseProvider {
201
211
  const uniqueToolsUsed = [...new Set(toolsUsed)];
202
212
  // ✅ Extract tool executions from AI SDK result
203
213
  const toolExecutions = [];
214
+ // Create a map of tool calls to their arguments for matching with results
215
+ const toolCallArgsMap = new Map();
204
216
  // Extract tool executions from AI SDK result steps
205
- // Extract tool executions from steps (where tool results are stored)
206
217
  if (result.steps &&
207
218
  Array.isArray(result.steps)) {
208
219
  for (const step of result.steps ||
209
220
  []) {
210
- // Focus only on tool results (which have complete execution data)
211
- // Tool calls are just the requests, tool results contain the actual execution data
221
+ // First, collect tool calls and their arguments
222
+ if (step?.toolCalls && Array.isArray(step.toolCalls)) {
223
+ for (const toolCall of step.toolCalls) {
224
+ const tcRecord = toolCall;
225
+ const toolName = tcRecord.toolName ||
226
+ tcRecord.name ||
227
+ "unknown";
228
+ const toolId = tcRecord.toolCallId ||
229
+ tcRecord.id ||
230
+ toolName;
231
+ // Extract arguments from tool call
232
+ let callArgs = {};
233
+ if (tcRecord.args) {
234
+ callArgs = tcRecord.args;
235
+ }
236
+ else if (tcRecord.arguments) {
237
+ callArgs = tcRecord.arguments;
238
+ }
239
+ else if (tcRecord.parameters) {
240
+ callArgs = tcRecord.parameters;
241
+ }
242
+ toolCallArgsMap.set(toolId, callArgs);
243
+ toolCallArgsMap.set(toolName, callArgs); // Also map by name as fallback
244
+ }
245
+ }
246
+ // Then, process tool results and match with call arguments
212
247
  if (step?.toolResults && Array.isArray(step.toolResults)) {
213
248
  for (const toolResult of step.toolResults) {
214
249
  const trRecord = toolResult;
250
+ const toolName = trRecord.toolName || "unknown";
251
+ const toolId = trRecord.toolCallId || trRecord.id;
252
+ // Try to get arguments from the tool result first
253
+ let toolArgs = {};
254
+ if (trRecord.args) {
255
+ toolArgs = trRecord.args;
256
+ }
257
+ else if (trRecord.arguments) {
258
+ toolArgs = trRecord.arguments;
259
+ }
260
+ else if (trRecord.parameters) {
261
+ toolArgs = trRecord.parameters;
262
+ }
263
+ else if (trRecord.input) {
264
+ toolArgs = trRecord.input;
265
+ }
266
+ else {
267
+ // Fallback: get arguments from the corresponding tool call
268
+ toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
269
+ }
215
270
  toolExecutions.push({
216
- name: trRecord.toolName || "unknown",
217
- input: trRecord.args || {},
271
+ name: toolName,
272
+ input: toolArgs,
218
273
  output: trRecord.result || "success",
219
- duration: 0, // AI SDK doesn't track duration
220
274
  });
221
275
  }
222
276
  }
@@ -248,6 +302,12 @@ export class BaseProvider {
248
302
  toolResults: result.toolResults,
249
303
  toolsUsed: uniqueToolsUsed,
250
304
  toolExecutions, // ✅ Add extracted tool executions
305
+ availableTools: Object.keys(tools).map((name) => ({
306
+ name,
307
+ description: tools[name].description || "No description available",
308
+ parameters: tools[name].parameters || {},
309
+ server: tools[name].serverId || "direct",
310
+ })),
251
311
  };
252
312
  // Enhanced result with analytics and evaluation
253
313
  return await this.enhanceResult(enhancedResult, options, startTime);
@@ -339,6 +399,71 @@ export class BaseProvider {
339
399
  // Not an error - custom tools are optional
340
400
  }
341
401
  }
402
+ // ✅ CRITICAL FIX: Add external MCP tools if SDK has external server manager
403
+ if (this.sdk &&
404
+ this.sdk.externalServerManager &&
405
+ typeof this.sdk.externalServerManager.getAllTools === "function") {
406
+ try {
407
+ logger.debug(`[BaseProvider] Loading external MCP tools from SDK via externalServerManager`);
408
+ const externalTools = this.sdk.externalServerManager.getAllTools();
409
+ logger.debug(`[BaseProvider] Found ${externalTools.length} external MCP tools`, {
410
+ tools: externalTools.map((t) => ({
411
+ name: t.name,
412
+ available: t.isAvailable,
413
+ server: t.serverId,
414
+ })),
415
+ });
416
+ for (const externalTool of externalTools) {
417
+ if (externalTool.isAvailable) {
418
+ logger.debug(`[BaseProvider] Converting external MCP tool: ${externalTool.name} from ${externalTool.serverId}`);
419
+ // Convert to AI SDK tool format
420
+ const { tool: createAISDKTool } = await import("ai");
421
+ const { z } = await import("zod");
422
+ tools[externalTool.name] = createAISDKTool({
423
+ description: externalTool.description ||
424
+ `External MCP tool ${externalTool.name}`,
425
+ parameters: await this.convertMCPSchemaToZod(externalTool.inputSchema),
426
+ execute: async (args) => {
427
+ logger.debug(`[BaseProvider] Executing external MCP tool: ${externalTool.name}`, { args });
428
+ // Execute via SDK's external server manager
429
+ if (this.sdk &&
430
+ this.sdk.externalServerManager &&
431
+ typeof this.sdk.externalServerManager.executeTool ===
432
+ "function") {
433
+ return await this.sdk.externalServerManager.executeTool(externalTool.serverId, externalTool.name, args);
434
+ }
435
+ else {
436
+ throw new Error(`Cannot execute external MCP tool: SDK externalServerManager.executeTool not available`);
437
+ }
438
+ },
439
+ });
440
+ logger.debug(`[BaseProvider] Successfully added external MCP tool: ${externalTool.name}`);
441
+ }
442
+ else {
443
+ logger.debug(`[BaseProvider] Skipping unavailable external MCP tool: ${externalTool.name}`);
444
+ }
445
+ }
446
+ logger.debug(`[BaseProvider] External MCP tools loading complete`, {
447
+ totalExternalTools: externalTools.length,
448
+ availableExternalTools: externalTools.filter((t) => t.isAvailable)
449
+ .length,
450
+ addedToTools: externalTools.filter((t) => t.isAvailable).length,
451
+ });
452
+ }
453
+ catch (error) {
454
+ logger.error(`[BaseProvider] Failed to load external MCP tools for ${this.providerName}:`, error);
455
+ // Not an error - external tools are optional
456
+ }
457
+ }
458
+ else {
459
+ logger.debug(`[BaseProvider] No external MCP tools interface available`, {
460
+ hasSDK: !!this.sdk,
461
+ hasExternalServerManager: this.sdk && !!this.sdk.externalServerManager,
462
+ hasGetAllTools: this.sdk &&
463
+ this.sdk.externalServerManager &&
464
+ typeof this.sdk.externalServerManager.getAllTools === "function",
465
+ });
466
+ }
342
467
  // MCP tools loading simplified - removed functionCalling dependency
343
468
  if (!this.mcpTools) {
344
469
  // Set empty tools object - MCP tools are handled at a higher level
@@ -351,6 +476,80 @@ export class BaseProvider {
351
476
  logger.debug(`[BaseProvider] getAllTools returning tools: ${Object.keys(tools).join(", ")}`);
352
477
  return tools;
353
478
  }
479
+ /**
480
+ * Convert MCP JSON Schema to Zod schema for AI SDK tools
481
+ * Handles common MCP schema patterns safely
482
+ */
483
+ async convertMCPSchemaToZod(inputSchema) {
484
+ const { z } = await import("zod");
485
+ if (!inputSchema || typeof inputSchema !== "object") {
486
+ return z.object({});
487
+ }
488
+ try {
489
+ const schema = inputSchema;
490
+ const zodFields = {};
491
+ // Handle JSON Schema properties
492
+ if (schema.properties && typeof schema.properties === "object") {
493
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
494
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
495
+ const prop = propDef;
496
+ let zodType;
497
+ // Convert based on JSON Schema type
498
+ switch (prop.type) {
499
+ case "string":
500
+ zodType = z.string();
501
+ if (prop.description) {
502
+ zodType = zodType.describe(prop.description);
503
+ }
504
+ break;
505
+ case "number":
506
+ case "integer":
507
+ zodType = z.number();
508
+ if (prop.description) {
509
+ zodType = zodType.describe(prop.description);
510
+ }
511
+ break;
512
+ case "boolean":
513
+ zodType = z.boolean();
514
+ if (prop.description) {
515
+ zodType = zodType.describe(prop.description);
516
+ }
517
+ break;
518
+ case "array":
519
+ zodType = z.array(z.unknown());
520
+ if (prop.description) {
521
+ zodType = zodType.describe(prop.description);
522
+ }
523
+ break;
524
+ case "object":
525
+ zodType = z.object({});
526
+ if (prop.description) {
527
+ zodType = zodType.describe(prop.description);
528
+ }
529
+ break;
530
+ default:
531
+ // Unknown type, use string as fallback
532
+ zodType = z.string();
533
+ if (prop.description) {
534
+ zodType = zodType.describe(prop.description);
535
+ }
536
+ }
537
+ // Make optional if not required
538
+ if (!required.has(propName)) {
539
+ zodType = zodType.optional();
540
+ }
541
+ zodFields[propName] = zodType;
542
+ }
543
+ }
544
+ return Object.keys(zodFields).length > 0
545
+ ? z.object(zodFields)
546
+ : z.object({});
547
+ }
548
+ catch (error) {
549
+ logger.warn(`Failed to convert MCP schema to Zod, using empty schema:`, error);
550
+ return z.object({});
551
+ }
552
+ }
354
553
  /**
355
554
  * Set session context for MCP tools
356
555
  */
@@ -3,7 +3,7 @@
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
5
  import { ConversationMemoryError } from "../types/conversationTypes.js";
6
- import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN } from "../config/conversationMemoryConfig.js";
6
+ import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemoryConfig.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  export class ConversationMemoryManager {
9
9
  sessions = new Map();
@@ -49,7 +49,8 @@ export class ConversationMemoryManager {
49
49
  session.messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
50
50
  session.lastActivity = Date.now();
51
51
  // Enforce per-session turn limit (each turn = MESSAGES_PER_TURN messages: user + assistant)
52
- const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) * MESSAGES_PER_TURN;
52
+ const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
53
+ MESSAGES_PER_TURN;
53
54
  if (session.messages.length > maxMessages) {
54
55
  session.messages = session.messages.slice(-maxMessages);
55
56
  }
@@ -109,13 +109,24 @@ export class AIProviderFactory {
109
109
  const finalModelName = resolvedModelName === "default" || resolvedModelName === null
110
110
  ? undefined
111
111
  : resolvedModelName;
112
- const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, sdk);
112
+ // CRITICAL FIX: Pass external MCP tools interface to BaseProvider
113
+ let finalSdk = sdk;
114
+ if (sdk && typeof sdk.getExternalMCPTools === "function") {
115
+ finalSdk = {
116
+ ...sdk,
117
+ externalServerManager: {
118
+ getAllTools: () => sdk.getExternalMCPTools(),
119
+ executeTool: (serverId, toolName, params) => sdk.executeExternalMCPTool(serverId, toolName, params),
120
+ },
121
+ };
122
+ }
123
+ // Create provider with enhanced SDK
124
+ const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, finalSdk);
113
125
  logger.debug(componentIdentifier, "Pure factory pattern provider created", {
114
126
  providerName: normalizedName,
115
127
  modelName: finalModelName,
116
128
  factoryUsed: true,
117
129
  });
118
- // PURE FACTORY PATTERN: All providers handled by ProviderFactory - no switch statements needed
119
130
  // Wrap with MCP if enabled
120
131
  if (enableMCP) {
121
132
  try {
@@ -46,22 +46,16 @@ export class ProviderFactory {
46
46
  model = model || registration.defaultModel;
47
47
  }
48
48
  try {
49
- // Try calling as factory function first, then fallback to constructor
50
49
  let result;
51
50
  try {
52
- // Try as factory function
53
- result = registration.constructor(model, providerName, sdk);
51
+ // Try as async factory function first (most providers are async functions)
52
+ result = await registration.constructor(model, providerName, sdk);
54
53
  }
55
- catch (factoryError) {
56
- // Fallback to constructor
54
+ catch (functionError) {
55
+ // Fallback to constructor - ensure parameters are maintained
57
56
  result = new registration.constructor(model, providerName, sdk);
58
57
  }
59
- // Only await if result is actually a Promise
60
- if (result &&
61
- typeof result === "object" &&
62
- typeof result.then === "function") {
63
- result = await result;
64
- }
58
+ // Return result (no need to await again if already awaited in try block)
65
59
  return result;
66
60
  }
67
61
  catch (error) {
@@ -52,9 +52,9 @@ export class ProviderRegistry {
52
52
  process.env.AZURE_OPENAI_DEPLOYMENT_ID ||
53
53
  "gpt-4o-mini", ["azure", "azureOpenai"]);
54
54
  // Register Google Vertex AI provider
55
- ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName) => {
55
+ ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName, providerName, sdk) => {
56
56
  const { GoogleVertexProvider } = await import("../providers/googleVertex.js");
57
- return new GoogleVertexProvider(modelName);
57
+ return new GoogleVertexProvider(modelName, providerName, sdk);
58
58
  }, "claude-sonnet-4@20250514", ["vertex", "googleVertex"]);
59
59
  // Register Hugging Face provider (Unified Router implementation)
60
60
  ProviderFactory.registerProvider(AIProviderName.HUGGINGFACE, async (modelName) => {
@@ -34,6 +34,7 @@ export function getConversationMemoryDefaults() {
34
34
  return {
35
35
  enabled: process.env.NEUROLINK_MEMORY_ENABLED === "true",
36
36
  maxSessions: Number(process.env.NEUROLINK_MEMORY_MAX_SESSIONS) || DEFAULT_MAX_SESSIONS,
37
- maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) || DEFAULT_MAX_TURNS_PER_SESSION,
37
+ maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
38
+ DEFAULT_MAX_TURNS_PER_SESSION,
38
39
  };
39
40
  }