@juspay/neurolink 7.14.2 → 7.14.4
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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/config.d.ts +66 -66
- package/dist/core/baseProvider.d.ts +12 -7
- package/dist/core/baseProvider.js +118 -125
- package/dist/core/constants.d.ts +5 -0
- package/dist/core/constants.js +6 -0
- package/dist/core/dynamicModels.d.ts +4 -4
- package/dist/core/factory.d.ts +2 -4
- package/dist/core/types.d.ts +8 -22
- package/dist/index.d.ts +1 -4
- package/dist/lib/core/baseProvider.d.ts +12 -7
- package/dist/lib/core/baseProvider.js +118 -125
- package/dist/lib/core/constants.d.ts +5 -0
- package/dist/lib/core/constants.js +6 -0
- package/dist/lib/core/dynamicModels.d.ts +8 -8
- package/dist/lib/core/factory.d.ts +2 -4
- package/dist/lib/core/types.d.ts +8 -22
- package/dist/lib/index.d.ts +1 -4
- package/dist/lib/mcp/contracts/mcpContract.d.ts +6 -19
- package/dist/lib/mcp/externalServerManager.d.ts +2 -4
- package/dist/lib/mcp/externalServerManager.js +7 -8
- package/dist/lib/mcp/factory.d.ts +61 -7
- package/dist/lib/mcp/factory.js +36 -23
- package/dist/lib/mcp/mcpClientFactory.d.ts +2 -1
- package/dist/lib/mcp/mcpClientFactory.js +73 -26
- package/dist/lib/mcp/registry.d.ts +1 -1
- package/dist/lib/mcp/toolDiscoveryService.d.ts +1 -1
- package/dist/lib/mcp/toolDiscoveryService.js +50 -19
- package/dist/lib/mcp/toolRegistry.d.ts +23 -1
- package/dist/lib/mcp/toolRegistry.js +108 -17
- package/dist/lib/models/modelResolver.js +2 -1
- package/dist/lib/neurolink.d.ts +12 -8
- package/dist/lib/neurolink.js +130 -134
- package/dist/lib/providers/amazonBedrock.d.ts +2 -2
- package/dist/lib/providers/anthropic.d.ts +3 -3
- package/dist/lib/providers/googleAiStudio.d.ts +2 -2
- package/dist/lib/providers/mistral.d.ts +3 -3
- package/dist/lib/providers/ollama.d.ts +2 -2
- package/dist/lib/providers/openAI.d.ts +3 -3
- package/dist/lib/providers/openaiCompatible.d.ts +2 -2
- package/dist/lib/providers/sagemaker/client.d.ts +2 -5
- package/dist/lib/providers/sagemaker/language-model.d.ts +4 -6
- package/dist/lib/providers/sagemaker/parsers.js +5 -4
- package/dist/lib/sdk/toolRegistration.d.ts +6 -6
- package/dist/lib/sdk/toolRegistration.js +17 -56
- package/dist/lib/types/generateTypes.d.ts +9 -9
- package/dist/lib/types/streamTypes.d.ts +4 -4
- package/dist/lib/types/tools.d.ts +15 -7
- package/dist/lib/types/typeAliases.d.ts +412 -0
- package/dist/lib/types/typeAliases.js +48 -0
- package/dist/lib/utils/factoryProcessing.d.ts +2 -1
- package/dist/lib/utils/factoryProcessing.js +4 -3
- package/dist/lib/utils/parameterValidation.d.ts +97 -0
- package/dist/lib/utils/parameterValidation.js +452 -0
- package/dist/lib/utils/transformationUtils.d.ts +204 -0
- package/dist/lib/utils/transformationUtils.js +334 -0
- package/dist/lib/utils/typeUtils.d.ts +77 -0
- package/dist/lib/utils/typeUtils.js +97 -0
- package/dist/mcp/contracts/mcpContract.d.ts +6 -19
- package/dist/mcp/externalServerManager.d.ts +2 -4
- package/dist/mcp/externalServerManager.js +7 -8
- package/dist/mcp/factory.d.ts +61 -7
- package/dist/mcp/factory.js +36 -23
- package/dist/mcp/mcpClientFactory.d.ts +2 -1
- package/dist/mcp/mcpClientFactory.js +73 -26
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/toolDiscoveryService.d.ts +1 -1
- package/dist/mcp/toolDiscoveryService.js +50 -19
- package/dist/mcp/toolRegistry.d.ts +23 -1
- package/dist/mcp/toolRegistry.js +108 -17
- package/dist/models/modelResolver.js +2 -1
- package/dist/neurolink.d.ts +12 -8
- package/dist/neurolink.js +130 -134
- package/dist/providers/amazonBedrock.d.ts +2 -2
- package/dist/providers/anthropic.d.ts +3 -3
- package/dist/providers/googleAiStudio.d.ts +2 -2
- package/dist/providers/mistral.d.ts +3 -3
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/openAI.d.ts +3 -3
- package/dist/providers/openaiCompatible.d.ts +2 -2
- package/dist/providers/sagemaker/client.d.ts +2 -5
- package/dist/providers/sagemaker/language-model.d.ts +4 -6
- package/dist/providers/sagemaker/parsers.js +5 -4
- package/dist/sdk/toolRegistration.d.ts +6 -6
- package/dist/sdk/toolRegistration.js +17 -56
- package/dist/types/generateTypes.d.ts +9 -9
- package/dist/types/streamTypes.d.ts +4 -4
- package/dist/types/tools.d.ts +15 -7
- package/dist/types/typeAliases.d.ts +412 -0
- package/dist/types/typeAliases.js +48 -0
- package/dist/utils/factoryProcessing.d.ts +2 -1
- package/dist/utils/factoryProcessing.js +4 -3
- package/dist/utils/parameterValidation.d.ts +97 -0
- package/dist/utils/parameterValidation.js +452 -0
- package/dist/utils/transformationUtils.d.ts +204 -0
- package/dist/utils/transformationUtils.js +334 -0
- package/dist/utils/typeUtils.d.ts +77 -0
- package/dist/utils/typeUtils.js +97 -0
- package/package.json +1 -1
package/dist/mcp/factory.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Factory-First Architecture: MCP servers create tools for internal orchestration
|
|
4
4
|
* Compatible with MCP patterns for seamless integration
|
|
5
5
|
*/
|
|
6
|
-
import { z } from "zod";
|
|
7
6
|
import type { ExecutionContext } from "./contracts/mcpContract.js";
|
|
8
7
|
/**
|
|
9
8
|
* MCP Server Categories for organization and discovery
|
|
@@ -71,19 +70,64 @@ export interface ToolResult {
|
|
|
71
70
|
};
|
|
72
71
|
}
|
|
73
72
|
/**
|
|
74
|
-
* MCP Tool Interface -
|
|
73
|
+
* MCP Tool Interface - Standalone definition to avoid confusion with ToolDefinition execute signature
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* NeuroLink MCP Tool Interface - Standardized tool definition for MCP integration
|
|
77
|
+
*
|
|
78
|
+
* This interface defines the contract for all tools in the NeuroLink ecosystem,
|
|
79
|
+
* ensuring consistent execution patterns and metadata handling across different
|
|
80
|
+
* MCP servers and tool implementations.
|
|
81
|
+
*
|
|
82
|
+
* Key features:
|
|
83
|
+
* - Promise-based execution with ToolResult return type
|
|
84
|
+
* - Rich context support for session management and permissions
|
|
85
|
+
* - Optional schema validation for input/output
|
|
86
|
+
* - Comprehensive metadata support for tool discovery
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const calculatorTool: NeuroLinkMCPTool = {
|
|
91
|
+
* name: "calculator",
|
|
92
|
+
* description: "Performs basic arithmetic operations",
|
|
93
|
+
* category: "math",
|
|
94
|
+
* inputSchema: z.object({ a: z.number(), b: z.number(), op: z.string() }),
|
|
95
|
+
* async execute(params, context) {
|
|
96
|
+
* const { a, b, op } = params as { a: number; b: number; op: string };
|
|
97
|
+
* const result = op === "add" ? a + b : a - b;
|
|
98
|
+
* return { success: true, data: result };
|
|
99
|
+
* }
|
|
100
|
+
* };
|
|
101
|
+
* ```
|
|
75
102
|
*/
|
|
76
103
|
export interface NeuroLinkMCPTool {
|
|
104
|
+
/** Unique tool identifier for MCP registration and execution */
|
|
77
105
|
name: string;
|
|
106
|
+
/** Human-readable description of tool functionality */
|
|
78
107
|
description: string;
|
|
79
|
-
|
|
80
|
-
inputSchema?: z.ZodSchema;
|
|
81
|
-
outputSchema?: z.ZodSchema;
|
|
82
|
-
isImplemented?: boolean;
|
|
108
|
+
/** Optional category for tool organization and discovery */
|
|
83
109
|
category?: string;
|
|
110
|
+
/** Optional input schema for parameter validation (Zod or JSON Schema) */
|
|
111
|
+
inputSchema?: unknown;
|
|
112
|
+
/** Optional output schema for result validation */
|
|
113
|
+
outputSchema?: unknown;
|
|
114
|
+
/** Implementation status flag for development tracking */
|
|
115
|
+
isImplemented?: boolean;
|
|
116
|
+
/** Required permissions for tool execution in secured environments */
|
|
84
117
|
permissions?: string[];
|
|
118
|
+
/** Tool version for compatibility and update management */
|
|
85
119
|
version?: string;
|
|
120
|
+
/** Additional metadata for tool information and capabilities */
|
|
86
121
|
metadata?: Record<string, unknown>;
|
|
122
|
+
/**
|
|
123
|
+
* Tool execution function with standardized signature
|
|
124
|
+
*
|
|
125
|
+
* @param params - Input parameters for the tool (validated against inputSchema if provided)
|
|
126
|
+
* @param context - Execution context with session, user, and environment information
|
|
127
|
+
* @returns Promise resolving to ToolResult with success status, data, and metadata
|
|
128
|
+
* @throws ValidationError if parameters fail validation
|
|
129
|
+
*/
|
|
130
|
+
execute: (params: unknown, context: NeuroLinkExecutionContext) => Promise<ToolResult>;
|
|
87
131
|
}
|
|
88
132
|
/**
|
|
89
133
|
* MCP Server Interface - Standard compatible
|
|
@@ -145,7 +189,8 @@ export interface MCPServerConfig {
|
|
|
145
189
|
*/
|
|
146
190
|
export declare function createMCPServer(config: MCPServerConfig): NeuroLinkMCPServer;
|
|
147
191
|
/**
|
|
148
|
-
* Utility function to validate tool interface
|
|
192
|
+
* Utility function to validate tool interface using centralized validation
|
|
193
|
+
* Ensures proper async patterns and type safety
|
|
149
194
|
*/
|
|
150
195
|
export declare function validateTool(tool: NeuroLinkMCPTool): boolean;
|
|
151
196
|
/**
|
|
@@ -159,3 +204,12 @@ export declare function getServerInfo(server: NeuroLinkMCPServer): {
|
|
|
159
204
|
toolCount: number;
|
|
160
205
|
capabilities: string[];
|
|
161
206
|
};
|
|
207
|
+
/**
|
|
208
|
+
* Async utility function to validate all tools in a server
|
|
209
|
+
* Ensures all registered tools follow proper async patterns
|
|
210
|
+
*/
|
|
211
|
+
export declare function validateServerTools(server: NeuroLinkMCPServer): Promise<{
|
|
212
|
+
isValid: boolean;
|
|
213
|
+
invalidTools: string[];
|
|
214
|
+
errors: string[];
|
|
215
|
+
}>;
|
package/dist/mcp/factory.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Compatible with MCP patterns for seamless integration
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { validateMCPTool, ValidationError, createValidationSummary, } from "../utils/parameterValidation.js";
|
|
7
8
|
/**
|
|
8
9
|
* Input validation schemas
|
|
9
10
|
*/
|
|
@@ -27,7 +28,7 @@ const ServerConfigSchema = z.object({
|
|
|
27
28
|
])
|
|
28
29
|
.optional(),
|
|
29
30
|
visibility: z.enum(["public", "private", "organization"]).optional(),
|
|
30
|
-
metadata: z.record(z.
|
|
31
|
+
metadata: z.record(z.unknown()).optional(),
|
|
31
32
|
dependencies: z.array(z.string()).optional(),
|
|
32
33
|
capabilities: z.array(z.string()).optional(),
|
|
33
34
|
});
|
|
@@ -76,9 +77,11 @@ export function createMCPServer(config) {
|
|
|
76
77
|
tools: {},
|
|
77
78
|
// Tool registration method
|
|
78
79
|
registerTool(tool) {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// Comprehensive tool validation using centralized utilities
|
|
81
|
+
const validation = validateMCPTool(tool);
|
|
82
|
+
if (!validation.isValid) {
|
|
83
|
+
const summary = createValidationSummary(validation);
|
|
84
|
+
throw new ValidationError(`Invalid tool '${tool.name}': ${summary}`, "tool", "VALIDATION_FAILED", validation.suggestions);
|
|
82
85
|
}
|
|
83
86
|
// Check for duplicate tool names
|
|
84
87
|
if (this.tools[tool.name]) {
|
|
@@ -105,28 +108,13 @@ export function createMCPServer(config) {
|
|
|
105
108
|
return server;
|
|
106
109
|
}
|
|
107
110
|
/**
|
|
108
|
-
* Utility function to validate tool interface
|
|
111
|
+
* Utility function to validate tool interface using centralized validation
|
|
112
|
+
* Ensures proper async patterns and type safety
|
|
109
113
|
*/
|
|
110
114
|
export function validateTool(tool) {
|
|
111
115
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
if (!tool.description || typeof tool.description !== "string") {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if (!tool.execute || typeof tool.execute !== "function") {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
// Validate optional schemas if present
|
|
123
|
-
if (tool.inputSchema && !(tool.inputSchema instanceof z.ZodSchema)) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
if (tool.outputSchema && !(tool.outputSchema instanceof z.ZodSchema)) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
return true;
|
|
116
|
+
const validation = validateMCPTool(tool);
|
|
117
|
+
return validation.isValid;
|
|
130
118
|
}
|
|
131
119
|
catch (error) {
|
|
132
120
|
return false;
|
|
@@ -145,4 +133,29 @@ export function getServerInfo(server) {
|
|
|
145
133
|
capabilities: server.capabilities || [],
|
|
146
134
|
};
|
|
147
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Async utility function to validate all tools in a server
|
|
138
|
+
* Ensures all registered tools follow proper async patterns
|
|
139
|
+
*/
|
|
140
|
+
export async function validateServerTools(server) {
|
|
141
|
+
const invalidTools = [];
|
|
142
|
+
const errors = [];
|
|
143
|
+
for (const [toolName, tool] of Object.entries(server.tools)) {
|
|
144
|
+
try {
|
|
145
|
+
if (!validateTool(tool)) {
|
|
146
|
+
invalidTools.push(toolName);
|
|
147
|
+
errors.push(`Tool '${toolName}' does not follow proper async patterns`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
invalidTools.push(toolName);
|
|
152
|
+
errors.push(`Tool '${toolName}' validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
isValid: invalidTools.length === 0,
|
|
157
|
+
invalidTools,
|
|
158
|
+
errors,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
148
161
|
// Types are already exported above via export interface declarations
|
|
@@ -72,7 +72,8 @@ export declare class MCPClientFactory {
|
|
|
72
72
|
*/
|
|
73
73
|
private static extractCapabilities;
|
|
74
74
|
/**
|
|
75
|
-
* Create a timeout promise
|
|
75
|
+
* Create a timeout promise with AbortController support
|
|
76
|
+
* Provides consistent async timeout patterns across the factory
|
|
76
77
|
*/
|
|
77
78
|
private static createTimeoutPromise;
|
|
78
79
|
/**
|
|
@@ -9,7 +9,7 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
|
9
9
|
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
|
|
10
10
|
import { spawn } from "child_process";
|
|
11
11
|
import { mcpLogger } from "../utils/logger.js";
|
|
12
|
-
import { globalCircuitBreakerManager
|
|
12
|
+
import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
13
13
|
/**
|
|
14
14
|
* MCPClientFactory
|
|
15
15
|
* Factory class for creating MCP clients with different transports
|
|
@@ -73,7 +73,11 @@ export class MCPClientFactory {
|
|
|
73
73
|
*/
|
|
74
74
|
static async createClientInternal(config, timeout) {
|
|
75
75
|
// Create transport
|
|
76
|
-
const
|
|
76
|
+
const transportResult = await this.createTransport(config);
|
|
77
|
+
// Extract transport and process with necessary type assertions
|
|
78
|
+
// Note: Type assertions required due to TransportResult using 'unknown' to avoid circular imports
|
|
79
|
+
const transport = transportResult.transport;
|
|
80
|
+
const process = transportResult.process;
|
|
77
81
|
try {
|
|
78
82
|
// Create client
|
|
79
83
|
const client = new Client(this.NEUROLINK_IMPLEMENTATION, {
|
|
@@ -140,10 +144,12 @@ export class MCPClientFactory {
|
|
|
140
144
|
// Spawn the process
|
|
141
145
|
const childProcess = spawn(config.command, config.args || [], {
|
|
142
146
|
stdio: ["pipe", "pipe", "pipe"],
|
|
143
|
-
env: {
|
|
147
|
+
env: Object.fromEntries(Object.entries({
|
|
144
148
|
...process.env,
|
|
145
149
|
...config.env,
|
|
146
|
-
}
|
|
150
|
+
})
|
|
151
|
+
.filter(([, value]) => value !== undefined)
|
|
152
|
+
.map(([k, v]) => [k, String(v)])),
|
|
147
153
|
cwd: config.cwd,
|
|
148
154
|
});
|
|
149
155
|
// Handle process errors
|
|
@@ -157,14 +163,32 @@ export class MCPClientFactory {
|
|
|
157
163
|
}
|
|
158
164
|
});
|
|
159
165
|
});
|
|
160
|
-
// Wait for process to be ready or fail
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
// Wait for process to be ready or fail using AbortController for better async patterns
|
|
167
|
+
const processStartupController = new AbortController();
|
|
168
|
+
const processStartupTimeout = setTimeout(() => {
|
|
169
|
+
processStartupController.abort();
|
|
170
|
+
}, 1000);
|
|
171
|
+
try {
|
|
172
|
+
await Promise.race([
|
|
173
|
+
new Promise((resolve) => {
|
|
174
|
+
const checkReady = () => {
|
|
175
|
+
if (processStartupController.signal.aborted) {
|
|
176
|
+
resolve(); // Timeout reached, continue
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
setTimeout(checkReady, 100);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
checkReady();
|
|
183
|
+
}),
|
|
184
|
+
processErrorPromise,
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
clearTimeout(processStartupTimeout);
|
|
189
|
+
}
|
|
165
190
|
// Check if process is still running
|
|
166
|
-
if (childProcess.killed ||
|
|
167
|
-
childProcess.exitCode !== null) {
|
|
191
|
+
if (childProcess.killed || childProcess.exitCode !== null) {
|
|
168
192
|
throw new Error("Process failed to start or exited immediately");
|
|
169
193
|
}
|
|
170
194
|
// Create transport
|
|
@@ -174,7 +198,9 @@ export class MCPClientFactory {
|
|
|
174
198
|
env: Object.fromEntries(Object.entries({
|
|
175
199
|
...process.env,
|
|
176
200
|
...config.env,
|
|
177
|
-
})
|
|
201
|
+
})
|
|
202
|
+
.filter(([, value]) => value !== undefined)
|
|
203
|
+
.map(([key, value]) => [key, String(value)])),
|
|
178
204
|
cwd: config.cwd,
|
|
179
205
|
});
|
|
180
206
|
return { transport, process: childProcess };
|
|
@@ -250,7 +276,7 @@ export class MCPClientFactory {
|
|
|
250
276
|
capabilities: this.DEFAULT_CAPABILITIES,
|
|
251
277
|
};
|
|
252
278
|
}
|
|
253
|
-
catch
|
|
279
|
+
catch {
|
|
254
280
|
// If listing tools fails, try a simpler ping
|
|
255
281
|
mcpLogger.debug("[MCPClientFactory] Tool listing failed, server may not support tools yet");
|
|
256
282
|
return {
|
|
@@ -267,17 +293,25 @@ export class MCPClientFactory {
|
|
|
267
293
|
// This can be enhanced when MCP servers provide more detailed capability info
|
|
268
294
|
return {
|
|
269
295
|
...this.DEFAULT_CAPABILITIES,
|
|
270
|
-
|
|
296
|
+
...(serverInfo.tools ? { tools: {} } : {}),
|
|
271
297
|
};
|
|
272
298
|
}
|
|
273
299
|
/**
|
|
274
|
-
* Create a timeout promise
|
|
300
|
+
* Create a timeout promise with AbortController support
|
|
301
|
+
* Provides consistent async timeout patterns across the factory
|
|
275
302
|
*/
|
|
276
|
-
static createTimeoutPromise(timeout, message) {
|
|
303
|
+
static createTimeoutPromise(timeout, message, abortSignal) {
|
|
277
304
|
return new Promise((_, reject) => {
|
|
278
|
-
setTimeout(() => {
|
|
305
|
+
const timeoutId = setTimeout(() => {
|
|
279
306
|
reject(new Error(message));
|
|
280
307
|
}, timeout);
|
|
308
|
+
// Support abortion for better async cleanup
|
|
309
|
+
if (abortSignal) {
|
|
310
|
+
abortSignal.addEventListener("abort", () => {
|
|
311
|
+
clearTimeout(timeoutId);
|
|
312
|
+
reject(new Error(`Operation aborted: ${message}`));
|
|
313
|
+
});
|
|
314
|
+
}
|
|
281
315
|
});
|
|
282
316
|
}
|
|
283
317
|
/**
|
|
@@ -299,17 +333,30 @@ export class MCPClientFactory {
|
|
|
299
333
|
catch (error) {
|
|
300
334
|
errors.push(`Transport close error: ${error instanceof Error ? error.message : String(error)}`);
|
|
301
335
|
}
|
|
302
|
-
// Kill process if exists
|
|
336
|
+
// Kill process if exists with proper async cleanup
|
|
303
337
|
if (process && !process.killed) {
|
|
304
338
|
try {
|
|
305
339
|
process.kill("SIGTERM");
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
340
|
+
// Use Promise-based approach for force kill timeout
|
|
341
|
+
await new Promise((resolve) => {
|
|
342
|
+
const forceKillTimeout = setTimeout(() => {
|
|
343
|
+
if (!process.killed) {
|
|
344
|
+
mcpLogger.warn("[MCPClientFactory] Force killing process");
|
|
345
|
+
try {
|
|
346
|
+
process.kill("SIGKILL");
|
|
347
|
+
}
|
|
348
|
+
catch (killError) {
|
|
349
|
+
mcpLogger.debug("[MCPClientFactory] Error in force kill:", killError);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
resolve();
|
|
353
|
+
}, 5000);
|
|
354
|
+
// If process exits gracefully before timeout, clear the force kill
|
|
355
|
+
process.on("exit", () => {
|
|
356
|
+
clearTimeout(forceKillTimeout);
|
|
357
|
+
resolve();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
313
360
|
}
|
|
314
361
|
catch (error) {
|
|
315
362
|
errors.push(`Process kill error: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -339,7 +386,7 @@ export class MCPClientFactory {
|
|
|
339
386
|
try {
|
|
340
387
|
await client.listTools();
|
|
341
388
|
}
|
|
342
|
-
catch
|
|
389
|
+
catch {
|
|
343
390
|
// Tool listing failure doesn't necessarily mean connection failure
|
|
344
391
|
mcpLogger.debug("[MCPClientFactory] Tool listing failed during test, but connection may be valid");
|
|
345
392
|
}
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface McpRegistry {
|
|
|
16
16
|
* Maintains backward compatibility with existing code
|
|
17
17
|
*/
|
|
18
18
|
export declare class MCPRegistry implements McpRegistry {
|
|
19
|
-
plugins: Map<string, DiscoveredMcp<
|
|
19
|
+
plugins: Map<string, DiscoveredMcp<import("../types/typeAliases.js").StandardRecord>>;
|
|
20
20
|
/**
|
|
21
21
|
* Register a plugin
|
|
22
22
|
*/
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { mcpLogger } from "../utils/logger.js";
|
|
8
|
-
import { globalCircuitBreakerManager
|
|
8
|
+
import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
9
|
+
import { isObject, isString, isBoolean, isNullish, } from "../utils/typeUtils.js";
|
|
10
|
+
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
|
|
9
11
|
/**
|
|
10
12
|
* ToolDiscoveryService
|
|
11
13
|
* Handles automatic tool discovery and registration from external MCP servers
|
|
@@ -227,15 +229,15 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
227
229
|
validateTool(toolInfo) {
|
|
228
230
|
const errors = [];
|
|
229
231
|
const warnings = [];
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
// Use centralized validation for name
|
|
233
|
+
const nameError = validateToolName(toolInfo.name);
|
|
234
|
+
if (nameError) {
|
|
235
|
+
errors.push(nameError.message);
|
|
233
236
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
warnings.push("Tool description is empty");
|
|
237
|
+
// Use centralized validation for description
|
|
238
|
+
const descriptionError = validateToolDescription(toolInfo.description);
|
|
239
|
+
if (descriptionError) {
|
|
240
|
+
warnings.push(descriptionError.message);
|
|
239
241
|
}
|
|
240
242
|
if (!toolInfo.serverId) {
|
|
241
243
|
errors.push("Server ID is required");
|
|
@@ -440,19 +442,48 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
440
442
|
}
|
|
441
443
|
}
|
|
442
444
|
/**
|
|
443
|
-
* Validate tool output
|
|
445
|
+
* Validate tool output with enhanced type safety
|
|
444
446
|
*/
|
|
445
447
|
validateToolOutput(result) {
|
|
446
|
-
//
|
|
447
|
-
if (
|
|
448
|
-
throw new Error("Tool returned
|
|
449
|
-
}
|
|
450
|
-
//
|
|
451
|
-
if (result
|
|
452
|
-
|
|
448
|
+
// Check for null/undefined results
|
|
449
|
+
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
|
+
}
|
|
453
483
|
}
|
|
454
|
-
|
|
455
|
-
|
|
484
|
+
// Validate that string results are not empty
|
|
485
|
+
if (isString(result) && result.trim() === "") {
|
|
486
|
+
throw new Error("Tool returned empty string result");
|
|
456
487
|
}
|
|
457
488
|
}
|
|
458
489
|
/**
|
|
@@ -43,7 +43,29 @@ export declare class MCPToolRegistry extends MCPRegistry {
|
|
|
43
43
|
registerServer(serverInfo: MCPServerInfo, context?: ExecutionContext): Promise<void>;
|
|
44
44
|
registerServer(serverId: string, serverConfig?: unknown, context?: ExecutionContext): Promise<void>;
|
|
45
45
|
/**
|
|
46
|
-
* Execute a tool with enhanced context
|
|
46
|
+
* Execute a tool with enhanced context and automatic result wrapping
|
|
47
|
+
*
|
|
48
|
+
* This method handles both raw return values and ToolResult objects:
|
|
49
|
+
* - Raw values (primitives, objects) are automatically wrapped in ToolResult format
|
|
50
|
+
* - Existing ToolResult objects are enhanced with execution metadata
|
|
51
|
+
* - All results include execution timing and context information
|
|
52
|
+
*
|
|
53
|
+
* @param toolName - Name of the tool to execute
|
|
54
|
+
* @param args - Parameters to pass to the tool execution function
|
|
55
|
+
* @param context - Execution context with session, user, and environment info
|
|
56
|
+
* @returns Promise resolving to ToolResult object with data, metadata, and usage info
|
|
57
|
+
* @throws Error if tool is not found or execution fails
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // Tool that returns raw value
|
|
62
|
+
* const result = await toolRegistry.executeTool("calculator", { a: 5, b: 3, op: "add" });
|
|
63
|
+
* // result.data === 8, result.metadata contains execution info
|
|
64
|
+
*
|
|
65
|
+
* // Tool that returns ToolResult
|
|
66
|
+
* const result = await toolRegistry.executeTool("complexTool", { input: "test" });
|
|
67
|
+
* // result is enhanced ToolResult with additional metadata
|
|
68
|
+
* ```
|
|
47
69
|
*/
|
|
48
70
|
executeTool<T = unknown>(toolName: string, args?: unknown, context?: ExecutionContext): Promise<T>;
|
|
49
71
|
/**
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -183,7 +183,29 @@ export class MCPToolRegistry extends MCPRegistry {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
/**
|
|
186
|
-
* Execute a tool with enhanced context
|
|
186
|
+
* Execute a tool with enhanced context and automatic result wrapping
|
|
187
|
+
*
|
|
188
|
+
* This method handles both raw return values and ToolResult objects:
|
|
189
|
+
* - Raw values (primitives, objects) are automatically wrapped in ToolResult format
|
|
190
|
+
* - Existing ToolResult objects are enhanced with execution metadata
|
|
191
|
+
* - All results include execution timing and context information
|
|
192
|
+
*
|
|
193
|
+
* @param toolName - Name of the tool to execute
|
|
194
|
+
* @param args - Parameters to pass to the tool execution function
|
|
195
|
+
* @param context - Execution context with session, user, and environment info
|
|
196
|
+
* @returns Promise resolving to ToolResult object with data, metadata, and usage info
|
|
197
|
+
* @throws Error if tool is not found or execution fails
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* // Tool that returns raw value
|
|
202
|
+
* const result = await toolRegistry.executeTool("calculator", { a: 5, b: 3, op: "add" });
|
|
203
|
+
* // result.data === 8, result.metadata contains execution info
|
|
204
|
+
*
|
|
205
|
+
* // Tool that returns ToolResult
|
|
206
|
+
* const result = await toolRegistry.executeTool("complexTool", { input: "test" });
|
|
207
|
+
* // result is enhanced ToolResult with additional metadata
|
|
208
|
+
* ```
|
|
187
209
|
*/
|
|
188
210
|
async executeTool(toolName, args, context) {
|
|
189
211
|
const startTime = Date.now();
|
|
@@ -221,22 +243,46 @@ export class MCPToolRegistry extends MCPRegistry {
|
|
|
221
243
|
// Execute the actual tool
|
|
222
244
|
registryLogger.debug(`Executing tool '${toolName}' with args:`, args);
|
|
223
245
|
const toolResult = await toolImpl.execute(args, execContext);
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
// Properly wrap raw results in ToolResult format
|
|
247
|
+
let result;
|
|
248
|
+
// Check if result is already a ToolResult object
|
|
249
|
+
if (toolResult &&
|
|
250
|
+
typeof toolResult === "object" &&
|
|
251
|
+
"success" in toolResult &&
|
|
252
|
+
typeof toolResult.success === "boolean") {
|
|
253
|
+
// Result is already a ToolResult, enhance with metadata
|
|
254
|
+
const toolResultObj = toolResult;
|
|
255
|
+
result = {
|
|
256
|
+
...toolResultObj,
|
|
257
|
+
usage: {
|
|
258
|
+
...(toolResultObj.usage || {}),
|
|
259
|
+
executionTime: Date.now() - startTime,
|
|
260
|
+
},
|
|
261
|
+
metadata: {
|
|
262
|
+
...(toolResultObj.metadata || {}),
|
|
263
|
+
toolName,
|
|
264
|
+
serverId: tool.serverId,
|
|
265
|
+
sessionId: execContext.sessionId,
|
|
266
|
+
executionTime: Date.now() - startTime,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Result is a raw value, wrap it in ToolResult format
|
|
272
|
+
result = {
|
|
273
|
+
success: true,
|
|
274
|
+
data: toolResult,
|
|
275
|
+
usage: {
|
|
276
|
+
executionTime: Date.now() - startTime,
|
|
277
|
+
},
|
|
278
|
+
metadata: {
|
|
279
|
+
toolName,
|
|
280
|
+
serverId: tool.serverId,
|
|
281
|
+
sessionId: execContext.sessionId,
|
|
282
|
+
executionTime: Date.now() - startTime,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
240
286
|
// Update statistics
|
|
241
287
|
const duration = Date.now() - startTime;
|
|
242
288
|
this.updateStats(toolName, duration);
|
|
@@ -406,6 +452,51 @@ export class MCPToolRegistry extends MCPRegistry {
|
|
|
406
452
|
*/
|
|
407
453
|
registerTool(toolId, toolInfo, toolImpl) {
|
|
408
454
|
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(", ")}`);
|
|
481
|
+
}
|
|
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;
|
|
499
|
+
}
|
|
409
500
|
this.tools.set(toolId, toolInfo);
|
|
410
501
|
this.toolImpls.set(toolId, toolImpl);
|
|
411
502
|
registryLogger.debug(`Successfully registered tool: ${toolId}`);
|