@juspay/neurolink 4.0.0 → 4.1.1
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 +20 -5
- package/README.md +59 -1
- package/dist/lib/mcp/dynamic-chain-executor.d.ts +201 -0
- package/dist/lib/mcp/dynamic-chain-executor.js +489 -0
- package/dist/lib/mcp/dynamic-orchestrator.d.ts +109 -0
- package/dist/lib/mcp/dynamic-orchestrator.js +351 -0
- package/dist/lib/mcp/error-manager.d.ts +254 -0
- package/dist/lib/mcp/error-manager.js +501 -0
- package/dist/lib/mcp/error-recovery.d.ts +158 -0
- package/dist/lib/mcp/error-recovery.js +405 -0
- package/dist/lib/mcp/health-monitor.d.ts +256 -0
- package/dist/lib/mcp/health-monitor.js +621 -0
- package/dist/lib/mcp/orchestrator.d.ts +136 -5
- package/dist/lib/mcp/orchestrator.js +316 -9
- package/dist/lib/mcp/registry.d.ts +22 -0
- package/dist/lib/mcp/registry.js +24 -0
- package/dist/lib/mcp/semaphore-manager.d.ts +137 -0
- package/dist/lib/mcp/semaphore-manager.js +329 -0
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/lib/mcp/session-manager.d.ts +186 -0
- package/dist/lib/mcp/session-manager.js +400 -0
- package/dist/lib/mcp/session-persistence.d.ts +93 -0
- package/dist/lib/mcp/session-persistence.js +298 -0
- package/dist/lib/mcp/transport-manager.d.ts +153 -0
- package/dist/lib/mcp/transport-manager.js +330 -0
- package/dist/lib/mcp/unified-registry.d.ts +42 -1
- package/dist/lib/mcp/unified-registry.js +122 -2
- package/dist/lib/neurolink.d.ts +75 -0
- package/dist/lib/neurolink.js +104 -0
- package/dist/mcp/dynamic-chain-executor.d.ts +201 -0
- package/dist/mcp/dynamic-chain-executor.js +489 -0
- package/dist/mcp/dynamic-orchestrator.d.ts +109 -0
- package/dist/mcp/dynamic-orchestrator.js +351 -0
- package/dist/mcp/error-manager.d.ts +254 -0
- package/dist/mcp/error-manager.js +501 -0
- package/dist/mcp/error-recovery.d.ts +158 -0
- package/dist/mcp/error-recovery.js +405 -0
- package/dist/mcp/health-monitor.d.ts +256 -0
- package/dist/mcp/health-monitor.js +621 -0
- package/dist/mcp/orchestrator.d.ts +136 -5
- package/dist/mcp/orchestrator.js +316 -9
- package/dist/mcp/registry.d.ts +22 -0
- package/dist/mcp/registry.js +24 -0
- package/dist/mcp/semaphore-manager.d.ts +137 -0
- package/dist/mcp/semaphore-manager.js +329 -0
- package/dist/mcp/session-manager.d.ts +186 -0
- package/dist/mcp/session-manager.js +400 -0
- package/dist/mcp/session-persistence.d.ts +93 -0
- package/dist/mcp/session-persistence.js +299 -0
- package/dist/mcp/transport-manager.d.ts +153 -0
- package/dist/mcp/transport-manager.js +331 -0
- package/dist/mcp/unified-registry.d.ts +42 -1
- package/dist/mcp/unified-registry.js +122 -2
- package/dist/neurolink.d.ts +75 -0
- package/dist/neurolink.js +104 -0
- package/package.json +2 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport Manager for MCP connections
|
|
3
|
+
* Supports stdio, SSE, and HTTP transports with reconnection logic
|
|
4
|
+
*/
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { ErrorCategory, ErrorSeverity } from "./error-manager.js";
|
|
9
|
+
// Transport configuration schemas
|
|
10
|
+
export const TransportConfigSchema = z.discriminatedUnion("type", [
|
|
11
|
+
z.object({
|
|
12
|
+
type: z.literal("stdio"),
|
|
13
|
+
command: z.string(),
|
|
14
|
+
args: z.array(z.string()).optional(),
|
|
15
|
+
cwd: z.string().optional(),
|
|
16
|
+
env: z.record(z.string()).optional(),
|
|
17
|
+
}),
|
|
18
|
+
z.object({
|
|
19
|
+
type: z.literal("sse"),
|
|
20
|
+
url: z.string().url(),
|
|
21
|
+
headers: z.record(z.string()).optional(),
|
|
22
|
+
timeout: z.number().min(5).default(30),
|
|
23
|
+
maxRetryTime: z.number().default(5000),
|
|
24
|
+
withCredentials: z.boolean().default(false),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("http"),
|
|
28
|
+
url: z.string().url(),
|
|
29
|
+
headers: z.record(z.string()).optional(),
|
|
30
|
+
timeout: z.number().min(5).default(30),
|
|
31
|
+
}),
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Manages MCP transport connections with automatic reconnection and health monitoring
|
|
35
|
+
*/
|
|
36
|
+
export class TransportManager {
|
|
37
|
+
errorManager;
|
|
38
|
+
options;
|
|
39
|
+
client;
|
|
40
|
+
config;
|
|
41
|
+
status;
|
|
42
|
+
reconnectTimer;
|
|
43
|
+
healthCheckTimer;
|
|
44
|
+
reconnectPromise;
|
|
45
|
+
constructor(errorManager, options = {}) {
|
|
46
|
+
this.errorManager = errorManager;
|
|
47
|
+
this.options = options;
|
|
48
|
+
// Initialize default options
|
|
49
|
+
this.options = {
|
|
50
|
+
jitterEnabled: true,
|
|
51
|
+
maxJitter: 1000,
|
|
52
|
+
...this.options,
|
|
53
|
+
};
|
|
54
|
+
this.status = {
|
|
55
|
+
connected: false,
|
|
56
|
+
type: "stdio",
|
|
57
|
+
reconnectAttempts: 0,
|
|
58
|
+
};
|
|
59
|
+
// Apply defaults
|
|
60
|
+
this.options = {
|
|
61
|
+
autoReconnect: true,
|
|
62
|
+
maxReconnectAttempts: 5,
|
|
63
|
+
reconnectDelay: 1000,
|
|
64
|
+
healthCheckInterval: 30000,
|
|
65
|
+
...options,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Connect to MCP server using specified transport
|
|
70
|
+
*/
|
|
71
|
+
async connect(config) {
|
|
72
|
+
try {
|
|
73
|
+
// Validate configuration
|
|
74
|
+
const validatedConfig = TransportConfigSchema.parse(config);
|
|
75
|
+
this.config = validatedConfig;
|
|
76
|
+
this.status.type = validatedConfig.type;
|
|
77
|
+
// Disconnect existing client if any
|
|
78
|
+
if (this.client) {
|
|
79
|
+
await this.disconnect();
|
|
80
|
+
}
|
|
81
|
+
// Create transport based on type
|
|
82
|
+
const transport = await this.createTransport(validatedConfig);
|
|
83
|
+
// Create MCP client
|
|
84
|
+
this.client = new Client({
|
|
85
|
+
name: "neurolink-mcp-client",
|
|
86
|
+
version: "1.0.0",
|
|
87
|
+
}, {
|
|
88
|
+
capabilities: {},
|
|
89
|
+
});
|
|
90
|
+
// Connect client
|
|
91
|
+
await this.client.connect(transport);
|
|
92
|
+
// Update status
|
|
93
|
+
this.status.connected = true;
|
|
94
|
+
this.status.lastConnected = new Date();
|
|
95
|
+
this.status.reconnectAttempts = 0;
|
|
96
|
+
// Set up health monitoring if enabled
|
|
97
|
+
if (this.options.healthCheckInterval &&
|
|
98
|
+
this.options.healthCheckInterval > 0) {
|
|
99
|
+
this.setupHealthMonitoring();
|
|
100
|
+
}
|
|
101
|
+
return this.client;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.handleConnectionError(error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create transport based on configuration
|
|
110
|
+
*/
|
|
111
|
+
async createTransport(config) {
|
|
112
|
+
switch (config.type) {
|
|
113
|
+
case "stdio":
|
|
114
|
+
return this.createStdioTransport(config);
|
|
115
|
+
case "sse":
|
|
116
|
+
return this.createSSETransport(config);
|
|
117
|
+
case "http":
|
|
118
|
+
return this.createHTTPTransport(config);
|
|
119
|
+
default:
|
|
120
|
+
throw new Error(`Unsupported transport type: ${config.type}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create stdio transport
|
|
125
|
+
*/
|
|
126
|
+
createStdioTransport(config) {
|
|
127
|
+
// Filter out undefined values from process.env
|
|
128
|
+
const envWithoutUndefined = Object.fromEntries(Object.entries(process.env).filter(([_, value]) => value !== undefined));
|
|
129
|
+
// StdioClientTransport handles process creation internally
|
|
130
|
+
return new StdioClientTransport({
|
|
131
|
+
command: config.command,
|
|
132
|
+
args: config.args ?? [],
|
|
133
|
+
env: { ...envWithoutUndefined, ...config.env },
|
|
134
|
+
cwd: config.cwd,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create SSE transport with reconnection support
|
|
139
|
+
*/
|
|
140
|
+
async createSSETransport(config) {
|
|
141
|
+
// Dynamically import SSE transport
|
|
142
|
+
const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
|
|
143
|
+
// Use ReconnectingEventSource for reliability
|
|
144
|
+
const ReconnectingEventSource = await import("reconnecting-eventsource").then((m) => m.default);
|
|
145
|
+
return new SSEClientTransport(new URL(config.url), {
|
|
146
|
+
requestInit: {
|
|
147
|
+
headers: config.headers,
|
|
148
|
+
...(config.timeout && { timeout: config.timeout }),
|
|
149
|
+
},
|
|
150
|
+
eventSourceInit: {
|
|
151
|
+
eventSource: ReconnectingEventSource,
|
|
152
|
+
max_retry_time: config.maxRetryTime,
|
|
153
|
+
withCredentials: config.withCredentials,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create HTTP transport
|
|
159
|
+
*/
|
|
160
|
+
async createHTTPTransport(config) {
|
|
161
|
+
// Dynamically import HTTP transport
|
|
162
|
+
const httpModule = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
163
|
+
const HTTPClientTransport = httpModule.default || httpModule.HTTPClientTransport;
|
|
164
|
+
return new HTTPClientTransport(new URL(config.url), {
|
|
165
|
+
requestInit: {
|
|
166
|
+
headers: config.headers,
|
|
167
|
+
timeout: config.timeout * 1000, // Convert seconds to milliseconds
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Set up health monitoring
|
|
173
|
+
*/
|
|
174
|
+
setupHealthMonitoring() {
|
|
175
|
+
if (!this.client) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Clear any existing health check timer
|
|
179
|
+
if (this.healthCheckTimer) {
|
|
180
|
+
clearInterval(this.healthCheckTimer);
|
|
181
|
+
}
|
|
182
|
+
// Set up periodic health checks
|
|
183
|
+
this.healthCheckTimer = setInterval(async () => {
|
|
184
|
+
if (!this.client) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
// Simple health check - list tools
|
|
189
|
+
await this.client.request({ method: "tools/list" }, z.object({ tools: z.array(z.any()) }));
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
// Health check failed, trigger reconnection if enabled
|
|
193
|
+
if (this.options.autoReconnect && this.config) {
|
|
194
|
+
this.handleConnectionError(error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}, this.options.healthCheckInterval);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Handle connection errors
|
|
201
|
+
*/
|
|
202
|
+
handleConnectionError(error) {
|
|
203
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
204
|
+
this.status.connected = false;
|
|
205
|
+
this.status.lastError = err;
|
|
206
|
+
// Record error
|
|
207
|
+
this.errorManager.recordError(err, {
|
|
208
|
+
category: ErrorCategory.CONNECTION_ERROR,
|
|
209
|
+
severity: ErrorSeverity.HIGH,
|
|
210
|
+
toolName: "transport-manager",
|
|
211
|
+
parameters: {
|
|
212
|
+
transport: this.status.type,
|
|
213
|
+
reconnectAttempts: this.status.reconnectAttempts,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
// Attempt reconnection if enabled
|
|
217
|
+
if (this.options.autoReconnect &&
|
|
218
|
+
this.status.reconnectAttempts < this.options.maxReconnectAttempts) {
|
|
219
|
+
// Schedule reconnection attempt
|
|
220
|
+
this.scheduleReconnect();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Schedule reconnection attempt
|
|
225
|
+
*/
|
|
226
|
+
scheduleReconnect() {
|
|
227
|
+
if (this.reconnectTimer) {
|
|
228
|
+
clearTimeout(this.reconnectTimer);
|
|
229
|
+
}
|
|
230
|
+
// Increment attempts BEFORE calculating delay (uses updated attempt count)
|
|
231
|
+
this.status.reconnectAttempts++;
|
|
232
|
+
const delay = this.calculateReconnectDelay();
|
|
233
|
+
this.reconnectTimer = setTimeout(() => {
|
|
234
|
+
this.reconnect().catch((error) => {
|
|
235
|
+
console.error("Reconnection failed:", error);
|
|
236
|
+
});
|
|
237
|
+
}, delay);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Calculate reconnect delay with exponential backoff
|
|
241
|
+
*/
|
|
242
|
+
calculateReconnectDelay() {
|
|
243
|
+
const baseDelay = this.options.reconnectDelay || 1000;
|
|
244
|
+
const maxDelay = 30000; // 30 seconds max
|
|
245
|
+
const delay = Math.min(baseDelay * Math.pow(2, this.status.reconnectAttempts), maxDelay);
|
|
246
|
+
// Add configurable jitter to prevent thundering herd
|
|
247
|
+
if (this.options.jitterEnabled === true) {
|
|
248
|
+
const maxJitter = this.options.maxJitter || 1000;
|
|
249
|
+
return delay + Math.random() * maxJitter;
|
|
250
|
+
}
|
|
251
|
+
return delay;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Reconnect to server
|
|
255
|
+
*/
|
|
256
|
+
async reconnect() {
|
|
257
|
+
// Prevent concurrent reconnection attempts
|
|
258
|
+
if (this.reconnectPromise) {
|
|
259
|
+
return this.reconnectPromise;
|
|
260
|
+
}
|
|
261
|
+
this.reconnectPromise = this._reconnect();
|
|
262
|
+
try {
|
|
263
|
+
await this.reconnectPromise;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
this.reconnectPromise = undefined;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async _reconnect() {
|
|
270
|
+
if (!this.config) {
|
|
271
|
+
throw new Error("No configuration available for reconnection");
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
await this.connect(this.config);
|
|
275
|
+
console.log(`Reconnected successfully after ${this.status.reconnectAttempts} attempts`);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
if (this.status.reconnectAttempts >= this.options.maxReconnectAttempts) {
|
|
279
|
+
throw new Error(`Max reconnection attempts (${this.options.maxReconnectAttempts}) reached`);
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Disconnect from server
|
|
286
|
+
*/
|
|
287
|
+
async disconnect() {
|
|
288
|
+
// Clear reconnection timer
|
|
289
|
+
if (this.reconnectTimer) {
|
|
290
|
+
clearTimeout(this.reconnectTimer);
|
|
291
|
+
this.reconnectTimer = undefined;
|
|
292
|
+
}
|
|
293
|
+
// Stop health monitoring
|
|
294
|
+
if (this.healthCheckTimer) {
|
|
295
|
+
clearInterval(this.healthCheckTimer);
|
|
296
|
+
this.healthCheckTimer = undefined;
|
|
297
|
+
}
|
|
298
|
+
// Close client connection
|
|
299
|
+
if (this.client) {
|
|
300
|
+
await this.client.close();
|
|
301
|
+
this.client = undefined;
|
|
302
|
+
}
|
|
303
|
+
// Update status
|
|
304
|
+
this.status.connected = false;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get current transport status
|
|
308
|
+
*/
|
|
309
|
+
getStatus() {
|
|
310
|
+
return { ...this.status };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get connected client
|
|
314
|
+
*/
|
|
315
|
+
getClient() {
|
|
316
|
+
return this.client;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Check if connected
|
|
320
|
+
*/
|
|
321
|
+
isConnected() {
|
|
322
|
+
return this.status.connected && this.client !== undefined;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Reset reconnection attempts
|
|
326
|
+
*/
|
|
327
|
+
resetReconnectAttempts() {
|
|
328
|
+
this.status.reconnectAttempts = 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
import type { DiscoveredMCP, ExecutionContext } from "./contracts/mcp-contract.js";
|
|
5
5
|
import type { DiscoveryOptions } from "./auto-discovery.js";
|
|
6
6
|
import { MCPToolRegistry, type ToolInfo } from "./tool-registry.js";
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
+
import { ErrorManager } from "./error-manager.js";
|
|
7
9
|
/**
|
|
8
10
|
* Unified registry combining multiple sources
|
|
9
11
|
*/
|
|
10
12
|
export declare class UnifiedMCPRegistry extends MCPToolRegistry {
|
|
13
|
+
private errorManager;
|
|
11
14
|
private autoDiscoveryEnabled;
|
|
12
15
|
private autoDiscoveredServers;
|
|
13
16
|
private manualServers;
|
|
14
17
|
private availableServers;
|
|
18
|
+
private transportManager;
|
|
19
|
+
private activeConnections;
|
|
20
|
+
constructor(errorManager?: ErrorManager);
|
|
15
21
|
/**
|
|
16
22
|
* Initialize with auto-discovery
|
|
17
23
|
*/
|
|
@@ -80,9 +86,44 @@ export declare class UnifiedMCPRegistry extends MCPToolRegistry {
|
|
|
80
86
|
tools?: number;
|
|
81
87
|
}>;
|
|
82
88
|
/**
|
|
83
|
-
*
|
|
89
|
+
* Add external MCP server programmatically
|
|
90
|
+
*
|
|
91
|
+
* @param serverId - Unique server identifier
|
|
92
|
+
* @param config - Server configuration (stdio, sse, or http)
|
|
93
|
+
*/
|
|
94
|
+
addExternalServer(serverId: string, config: {
|
|
95
|
+
type: "stdio" | "sse" | "http";
|
|
96
|
+
command?: string;
|
|
97
|
+
args?: string[];
|
|
98
|
+
env?: Record<string, string>;
|
|
99
|
+
cwd?: string;
|
|
100
|
+
url?: string;
|
|
101
|
+
headers?: Record<string, string>;
|
|
102
|
+
timeout?: number;
|
|
103
|
+
}): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Get active connection for a server
|
|
106
|
+
*/
|
|
107
|
+
getConnection(serverId: string): Client | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Check if server is actively connected
|
|
110
|
+
*/
|
|
111
|
+
isConnected(serverId: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Clear all registries and active connections (synchronous, preserves base API contract)
|
|
114
|
+
*/
|
|
115
|
+
/**
|
|
116
|
+
* Clear registries without closing connections (internal use)
|
|
117
|
+
*/
|
|
118
|
+
private clearRegistriesOnly;
|
|
119
|
+
/**
|
|
120
|
+
* Clear all registries and initiate async connection cleanup
|
|
84
121
|
*/
|
|
85
122
|
clear(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Clear all registries and close active connections asynchronously
|
|
125
|
+
*/
|
|
126
|
+
clearAsync(): Promise<void>;
|
|
86
127
|
}
|
|
87
128
|
/**
|
|
88
129
|
* Default unified registry instance
|
|
@@ -4,14 +4,25 @@
|
|
|
4
4
|
import { autoRegisterMCPServers, } from "./auto-discovery.js";
|
|
5
5
|
import { unifiedRegistryLogger } from "./logging.js";
|
|
6
6
|
import { MCPToolRegistry, } from "./tool-registry.js";
|
|
7
|
+
import { TransportManager, TransportConfigSchema, } from "./transport-manager.js";
|
|
8
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
|
+
import { ErrorManager } from "./error-manager.js";
|
|
7
10
|
/**
|
|
8
11
|
* Unified registry combining multiple sources
|
|
9
12
|
*/
|
|
10
13
|
export class UnifiedMCPRegistry extends MCPToolRegistry {
|
|
14
|
+
errorManager;
|
|
11
15
|
autoDiscoveryEnabled = true;
|
|
12
16
|
autoDiscoveredServers = [];
|
|
13
17
|
manualServers = new Map();
|
|
14
18
|
availableServers = new Set();
|
|
19
|
+
transportManager;
|
|
20
|
+
activeConnections = new Map();
|
|
21
|
+
constructor(errorManager = new ErrorManager()) {
|
|
22
|
+
super();
|
|
23
|
+
this.errorManager = errorManager;
|
|
24
|
+
this.transportManager = new TransportManager(this.errorManager);
|
|
25
|
+
}
|
|
15
26
|
/**
|
|
16
27
|
* Initialize with auto-discovery
|
|
17
28
|
*/
|
|
@@ -159,14 +170,123 @@ export class UnifiedMCPRegistry extends MCPToolRegistry {
|
|
|
159
170
|
};
|
|
160
171
|
}
|
|
161
172
|
/**
|
|
162
|
-
*
|
|
173
|
+
* Add external MCP server programmatically
|
|
174
|
+
*
|
|
175
|
+
* @param serverId - Unique server identifier
|
|
176
|
+
* @param config - Server configuration (stdio, sse, or http)
|
|
163
177
|
*/
|
|
164
|
-
|
|
178
|
+
async addExternalServer(serverId, config) {
|
|
179
|
+
unifiedRegistryLogger.info(`Adding external server: ${serverId} (${config.type})`);
|
|
180
|
+
// Create server metadata
|
|
181
|
+
const serverMeta = {
|
|
182
|
+
metadata: {
|
|
183
|
+
name: serverId,
|
|
184
|
+
version: "1.0.0",
|
|
185
|
+
main: "index.js",
|
|
186
|
+
engine: { neurolink: ">=4.0.0" },
|
|
187
|
+
description: `External ${config.type} server: ${serverId}`,
|
|
188
|
+
permissions: ["network", "filesystem"],
|
|
189
|
+
},
|
|
190
|
+
entryPath: "",
|
|
191
|
+
source: "installed",
|
|
192
|
+
constructor: undefined,
|
|
193
|
+
};
|
|
194
|
+
// Register in internal registry
|
|
195
|
+
this.register(serverMeta);
|
|
196
|
+
this.manualServers.set(serverId, config);
|
|
197
|
+
this.availableServers.add(serverId);
|
|
198
|
+
// Establish actual connection to make server immediately reachable
|
|
199
|
+
try {
|
|
200
|
+
// Validate config for stdio transport (most common case)
|
|
201
|
+
if (config.type === "stdio" && !config.command) {
|
|
202
|
+
throw new Error("Command is required for stdio transport");
|
|
203
|
+
}
|
|
204
|
+
// Create transport with proper type validation
|
|
205
|
+
// Validate config shape before creating transport
|
|
206
|
+
const validatedConfig = TransportConfigSchema.parse(config);
|
|
207
|
+
const transport = await this.transportManager.createTransport(validatedConfig);
|
|
208
|
+
const client = new Client({
|
|
209
|
+
name: "neurolink-client",
|
|
210
|
+
version: "4.1.0",
|
|
211
|
+
}, {
|
|
212
|
+
capabilities: {
|
|
213
|
+
tools: {},
|
|
214
|
+
logging: {},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
// Connect the client
|
|
218
|
+
await client.connect(transport);
|
|
219
|
+
this.activeConnections.set(serverId, client);
|
|
220
|
+
unifiedRegistryLogger.info(`Successfully connected to external server: ${serverId}`);
|
|
221
|
+
unifiedRegistryLogger.info(`Successfully added external server: ${serverId}`);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
225
|
+
unifiedRegistryLogger.warn(`Failed to establish connection to ${serverId}: ${errorMessage}. Server registered but not connected.`);
|
|
226
|
+
unifiedRegistryLogger.info(`Successfully registered external server: ${serverId} but connection failed.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get active connection for a server
|
|
231
|
+
*/
|
|
232
|
+
getConnection(serverId) {
|
|
233
|
+
return this.activeConnections.get(serverId);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if server is actively connected
|
|
237
|
+
*/
|
|
238
|
+
isConnected(serverId) {
|
|
239
|
+
return this.activeConnections.has(serverId);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Clear all registries and active connections (synchronous, preserves base API contract)
|
|
243
|
+
*/
|
|
244
|
+
/**
|
|
245
|
+
* Clear registries without closing connections (internal use)
|
|
246
|
+
*/
|
|
247
|
+
clearRegistriesOnly() {
|
|
165
248
|
super.clear();
|
|
166
249
|
this.autoDiscoveredServers = [];
|
|
167
250
|
this.manualServers.clear();
|
|
168
251
|
this.availableServers.clear();
|
|
169
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Clear all registries and initiate async connection cleanup
|
|
255
|
+
*/
|
|
256
|
+
clear() {
|
|
257
|
+
// Close all active connections before clearing registries to prevent resource leaks
|
|
258
|
+
const closePromises = [];
|
|
259
|
+
for (const [serverId, client] of this.activeConnections) {
|
|
260
|
+
closePromises.push(client.close().catch((error) => {
|
|
261
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
262
|
+
unifiedRegistryLogger.warn(`Failed to close connection for ${serverId}: ${errorMessage}`);
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
// Handle async cleanup without blocking synchronous clear()
|
|
266
|
+
Promise.allSettled(closePromises).then(() => {
|
|
267
|
+
this.activeConnections.clear();
|
|
268
|
+
});
|
|
269
|
+
// Clear registries after initiating connection cleanup
|
|
270
|
+
this.clearRegistriesOnly();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Clear all registries and close active connections asynchronously
|
|
274
|
+
*/
|
|
275
|
+
async clearAsync() {
|
|
276
|
+
// Close all active connections first
|
|
277
|
+
for (const [serverId, client] of this.activeConnections) {
|
|
278
|
+
try {
|
|
279
|
+
await client.close();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
283
|
+
unifiedRegistryLogger.warn(`Failed to close connection for ${serverId}: ${errorMessage}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
this.activeConnections.clear();
|
|
287
|
+
// Clear registries without attempting to close connections again
|
|
288
|
+
this.clearRegistriesOnly();
|
|
289
|
+
}
|
|
170
290
|
}
|
|
171
291
|
/**
|
|
172
292
|
* Default unified registry instance
|
package/dist/lib/neurolink.d.ts
CHANGED
|
@@ -129,6 +129,47 @@ export declare class NeuroLink {
|
|
|
129
129
|
hasServer: boolean;
|
|
130
130
|
}[];
|
|
131
131
|
}>;
|
|
132
|
+
/**
|
|
133
|
+
* Add a new MCP server programmatically
|
|
134
|
+
*
|
|
135
|
+
* Allows dynamic registration of MCP servers at runtime for enhanced
|
|
136
|
+
* tool ecosystem management. Perfect for integrating external services
|
|
137
|
+
* like Bitbucket, Slack, databases, etc.
|
|
138
|
+
*
|
|
139
|
+
* @param serverId - Unique identifier for the server (e.g., 'bitbucket', 'slack-api')
|
|
140
|
+
* @param config - Server configuration with command and execution parameters
|
|
141
|
+
* @returns Promise that resolves when server is successfully added and connected
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* // Add Bitbucket MCP server
|
|
146
|
+
* await neurolink.addMCPServer('bitbucket', {
|
|
147
|
+
* command: 'npx',
|
|
148
|
+
* args: ['-y', '@nexus2520/bitbucket-mcp-server'],
|
|
149
|
+
* env: {
|
|
150
|
+
* BITBUCKET_USERNAME: 'your-username',
|
|
151
|
+
* BITBUCKET_APP_PASSWORD: 'your-app-password'
|
|
152
|
+
* }
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* // Add custom database connector
|
|
156
|
+
* await neurolink.addMCPServer('database', {
|
|
157
|
+
* command: 'node',
|
|
158
|
+
* args: ['./custom-db-mcp-server.js'],
|
|
159
|
+
* env: { DB_CONNECTION_STRING: 'postgresql://...' }
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
addMCPServer(serverId: string, config: {
|
|
164
|
+
command: string;
|
|
165
|
+
args?: string[];
|
|
166
|
+
env?: Record<string, string>;
|
|
167
|
+
cwd?: string;
|
|
168
|
+
type?: "stdio" | "sse" | "http";
|
|
169
|
+
url?: string;
|
|
170
|
+
headers?: Record<string, string>;
|
|
171
|
+
timeout?: number;
|
|
172
|
+
}): Promise<void>;
|
|
132
173
|
/**
|
|
133
174
|
* Alias for generateText() - CLI-SDK consistency
|
|
134
175
|
* @param options - Text generation options
|
|
@@ -141,4 +182,38 @@ export declare class NeuroLink {
|
|
|
141
182
|
* @returns Promise resolving to text generation result
|
|
142
183
|
*/
|
|
143
184
|
gen(options: TextGenerationOptions): Promise<TextGenerationResult>;
|
|
185
|
+
/**
|
|
186
|
+
* Get the connection client for a specific MCP server
|
|
187
|
+
* @param serverId - The ID of the server to get connection for
|
|
188
|
+
* @returns Client connection object or undefined if not connected
|
|
189
|
+
*/
|
|
190
|
+
getConnection(serverId: string): import("@modelcontextprotocol/sdk/client/index.js").Client<{
|
|
191
|
+
method: string;
|
|
192
|
+
params?: {
|
|
193
|
+
[x: string]: unknown;
|
|
194
|
+
_meta?: {
|
|
195
|
+
[x: string]: unknown;
|
|
196
|
+
progressToken?: string | number | undefined;
|
|
197
|
+
} | undefined;
|
|
198
|
+
} | undefined;
|
|
199
|
+
}, {
|
|
200
|
+
method: string;
|
|
201
|
+
params?: {
|
|
202
|
+
[x: string]: unknown;
|
|
203
|
+
_meta?: {
|
|
204
|
+
[x: string]: unknown;
|
|
205
|
+
} | undefined;
|
|
206
|
+
} | undefined;
|
|
207
|
+
}, {
|
|
208
|
+
[x: string]: unknown;
|
|
209
|
+
_meta?: {
|
|
210
|
+
[x: string]: unknown;
|
|
211
|
+
} | undefined;
|
|
212
|
+
}> | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* Check if a specific MCP server is currently connected
|
|
215
|
+
* @param serverId - The ID of the server to check
|
|
216
|
+
* @returns True if server is connected, false otherwise
|
|
217
|
+
*/
|
|
218
|
+
isConnected(serverId: string): boolean;
|
|
144
219
|
}
|