@juspay/neurolink 1.10.0 → 1.11.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.
- package/CHANGELOG.md +36 -33
- package/README.md +16 -0
- package/dist/agent/direct-tools.d.ts +9 -9
- package/dist/cli/commands/agent-generate.d.ts +1 -2
- package/dist/cli/commands/agent-generate.js +5 -8
- package/dist/cli/commands/config.d.ts +2 -2
- package/dist/cli/commands/config.js +1 -1
- package/dist/cli/commands/mcp.js +91 -100
- package/dist/cli/commands/ollama.d.ts +2 -7
- package/dist/cli/commands/ollama.js +5 -8
- package/dist/cli/index.js +185 -276
- package/dist/core/factory.js +9 -10
- package/dist/index.d.ts +23 -0
- package/dist/index.js +35 -0
- package/dist/lib/agent/direct-tools.d.ts +9 -9
- package/dist/lib/core/factory.js +9 -10
- package/dist/lib/index.d.ts +23 -0
- package/dist/lib/index.js +35 -0
- package/dist/lib/mcp/adapters/plugin-bridge.d.ts +39 -0
- package/dist/lib/mcp/adapters/plugin-bridge.js +82 -0
- package/dist/lib/mcp/auto-discovery.d.ts +38 -96
- package/dist/lib/mcp/auto-discovery.js +100 -744
- package/dist/lib/mcp/client.js +4 -4
- package/dist/lib/mcp/context-manager.js +72 -1
- package/dist/lib/mcp/contracts/mcp-contract.d.ts +162 -0
- package/dist/lib/mcp/contracts/mcp-contract.js +58 -0
- package/dist/lib/mcp/core/plugin-manager.d.ts +45 -0
- package/dist/lib/mcp/core/plugin-manager.js +110 -0
- package/dist/lib/mcp/demo/plugin-demo.d.ts +20 -0
- package/dist/lib/mcp/demo/plugin-demo.js +116 -0
- package/dist/lib/mcp/ecosystem.d.ts +75 -0
- package/dist/lib/mcp/ecosystem.js +161 -0
- package/dist/lib/mcp/external-client.d.ts +88 -0
- package/dist/lib/mcp/external-client.js +323 -0
- package/dist/lib/mcp/external-manager.d.ts +112 -0
- package/dist/lib/mcp/external-manager.js +302 -0
- package/dist/lib/mcp/factory.d.ts +4 -4
- package/dist/lib/mcp/function-calling.js +59 -34
- package/dist/lib/mcp/index.d.ts +39 -184
- package/dist/lib/mcp/index.js +72 -150
- package/dist/lib/mcp/initialize.js +5 -5
- package/dist/lib/mcp/logging.d.ts +27 -60
- package/dist/lib/mcp/logging.js +77 -165
- package/dist/lib/mcp/neurolink-mcp-client.js +31 -3
- package/dist/lib/mcp/orchestrator.d.ts +1 -1
- package/dist/lib/mcp/orchestrator.js +13 -12
- package/dist/lib/mcp/plugin-manager.d.ts +98 -0
- package/dist/lib/mcp/plugin-manager.js +294 -0
- package/dist/lib/mcp/plugins/core/filesystem-mcp.d.ts +35 -0
- package/dist/lib/mcp/plugins/core/filesystem-mcp.js +139 -0
- package/dist/lib/mcp/plugins/filesystem-mcp.d.ts +36 -0
- package/dist/lib/mcp/plugins/filesystem-mcp.js +54 -0
- package/dist/lib/mcp/registry.d.ts +27 -176
- package/dist/lib/mcp/registry.js +31 -372
- package/dist/lib/mcp/security-manager.d.ts +85 -0
- package/dist/lib/mcp/security-manager.js +344 -0
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/lib/mcp/tool-integration.d.ts +4 -14
- package/dist/lib/mcp/tool-integration.js +43 -21
- package/dist/lib/mcp/tool-registry.d.ts +66 -0
- package/dist/lib/mcp/tool-registry.js +160 -0
- package/dist/lib/mcp/unified-mcp.d.ts +123 -0
- package/dist/lib/mcp/unified-mcp.js +246 -0
- package/dist/lib/mcp/unified-registry.d.ts +42 -229
- package/dist/lib/mcp/unified-registry.js +96 -1346
- package/dist/lib/neurolink.d.ts +3 -4
- package/dist/lib/neurolink.js +17 -18
- package/dist/lib/providers/agent-enhanced-provider.js +2 -2
- package/dist/lib/providers/amazonBedrock.js +2 -2
- package/dist/lib/providers/anthropic.js +3 -3
- package/dist/lib/providers/azureOpenAI.js +3 -3
- package/dist/lib/providers/function-calling-provider.js +34 -25
- package/dist/lib/providers/googleAIStudio.js +3 -3
- package/dist/lib/providers/googleVertexAI.js +2 -2
- package/dist/lib/providers/huggingFace.js +2 -2
- package/dist/lib/providers/mcp-provider.js +33 -5
- package/dist/lib/providers/mistralAI.js +2 -2
- package/dist/lib/providers/ollama.js +2 -2
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/utils/providerUtils-fixed.js +9 -9
- package/dist/mcp/adapters/plugin-bridge.d.ts +39 -0
- package/dist/mcp/adapters/plugin-bridge.js +82 -0
- package/dist/mcp/auto-discovery.d.ts +38 -96
- package/dist/mcp/auto-discovery.js +100 -745
- package/dist/mcp/client.js +4 -4
- package/dist/mcp/context-manager.js +72 -1
- package/dist/mcp/contracts/mcp-contract.d.ts +162 -0
- package/dist/mcp/contracts/mcp-contract.js +58 -0
- package/dist/mcp/core/plugin-manager.d.ts +45 -0
- package/dist/mcp/core/plugin-manager.js +110 -0
- package/dist/mcp/demo/plugin-demo.d.ts +20 -0
- package/dist/mcp/demo/plugin-demo.js +116 -0
- package/dist/mcp/ecosystem.d.ts +75 -0
- package/dist/mcp/ecosystem.js +162 -0
- package/dist/mcp/external-client.d.ts +88 -0
- package/dist/mcp/external-client.js +323 -0
- package/dist/mcp/external-manager.d.ts +112 -0
- package/dist/mcp/external-manager.js +302 -0
- package/dist/mcp/factory.d.ts +4 -4
- package/dist/mcp/function-calling.js +59 -34
- package/dist/mcp/index.d.ts +39 -184
- package/dist/mcp/index.js +72 -150
- package/dist/mcp/initialize.js +5 -5
- package/dist/mcp/logging.d.ts +27 -60
- package/dist/mcp/logging.js +77 -165
- package/dist/mcp/neurolink-mcp-client.js +31 -3
- package/dist/mcp/orchestrator.d.ts +1 -1
- package/dist/mcp/orchestrator.js +13 -12
- package/dist/mcp/plugin-manager.d.ts +98 -0
- package/dist/mcp/plugin-manager.js +295 -0
- package/dist/mcp/plugins/core/filesystem-mcp.d.ts +35 -0
- package/dist/mcp/plugins/core/filesystem-mcp.js +139 -0
- package/dist/mcp/plugins/core/neurolink-mcp.json +17 -0
- package/dist/mcp/plugins/filesystem-mcp.d.ts +36 -0
- package/dist/mcp/plugins/filesystem-mcp.js +54 -0
- package/dist/mcp/registry.d.ts +27 -176
- package/dist/mcp/registry.js +31 -372
- package/dist/mcp/security-manager.d.ts +85 -0
- package/dist/mcp/security-manager.js +344 -0
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/mcp/tool-integration.d.ts +4 -14
- package/dist/mcp/tool-integration.js +43 -21
- package/dist/mcp/tool-registry.d.ts +66 -0
- package/dist/mcp/tool-registry.js +160 -0
- package/dist/mcp/unified-mcp.d.ts +123 -0
- package/dist/mcp/unified-mcp.js +246 -0
- package/dist/mcp/unified-registry.d.ts +42 -229
- package/dist/mcp/unified-registry.js +96 -1345
- package/dist/neurolink.d.ts +3 -4
- package/dist/neurolink.js +17 -18
- package/dist/providers/agent-enhanced-provider.js +2 -2
- package/dist/providers/amazonBedrock.js +2 -2
- package/dist/providers/anthropic.js +3 -3
- package/dist/providers/azureOpenAI.js +3 -3
- package/dist/providers/function-calling-provider.js +34 -25
- package/dist/providers/googleAIStudio.js +3 -3
- package/dist/providers/googleVertexAI.js +2 -2
- package/dist/providers/huggingFace.js +2 -2
- package/dist/providers/mcp-provider.js +33 -5
- package/dist/providers/mistralAI.js +2 -2
- package/dist/providers/ollama.js +2 -2
- package/dist/providers/openAI.js +2 -2
- package/dist/utils/providerUtils-fixed.js +9 -9
- package/package.json +1 -1
|
@@ -1,836 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Combines manual configuration, auto-discovery, and default tool registry
|
|
4
|
-
* Provides intelligent server selection with configurable priorities
|
|
2
|
+
* Unified MCP Registry - Combines Multiple Registration Sources
|
|
5
3
|
*/
|
|
6
|
-
import
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { z } from "zod";
|
|
4
|
+
import { MCPRegistry } from "./registry.js";
|
|
9
5
|
import { discoverMCPServers, autoRegisterMCPServers, } from "./auto-discovery.js";
|
|
10
|
-
import { defaultToolRegistry, MCPToolRegistry, } from "./registry.js";
|
|
11
6
|
import { unifiedRegistryLogger } from "./logging.js";
|
|
7
|
+
import { MCPToolRegistry, } from "./tool-registry.js";
|
|
12
8
|
/**
|
|
13
|
-
* Unified
|
|
9
|
+
* Unified registry combining multiple sources
|
|
14
10
|
*/
|
|
15
|
-
export class UnifiedMCPRegistry {
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
export class UnifiedMCPRegistry extends MCPToolRegistry {
|
|
12
|
+
autoDiscoveryEnabled = true;
|
|
13
|
+
autoDiscoveredServers = [];
|
|
18
14
|
manualServers = new Map();
|
|
19
|
-
|
|
20
|
-
defaultServers = new Map();
|
|
21
|
-
autoRegistryInstance;
|
|
22
|
-
lastAutoDiscovery = 0;
|
|
23
|
-
autoDiscoveryCacheMs = 30000; // 30 seconds
|
|
24
|
-
_isInitialized = false;
|
|
25
|
-
constructor(configPath) {
|
|
26
|
-
this.configPath =
|
|
27
|
-
configPath || path.join(process.cwd(), ".mcp-config.json");
|
|
28
|
-
this.config = this.loadConfig();
|
|
29
|
-
this.autoRegistryInstance = new MCPToolRegistry();
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Check if the registry has been initialized
|
|
33
|
-
*/
|
|
34
|
-
get isInitialized() {
|
|
35
|
-
return this._isInitialized;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Load MCP configuration with defaults
|
|
39
|
-
*/
|
|
40
|
-
loadConfig() {
|
|
41
|
-
const defaultConfig = {
|
|
42
|
-
mcpServers: {},
|
|
43
|
-
autoDiscovery: {
|
|
44
|
-
enabled: true,
|
|
45
|
-
sources: ["claude", "vscode", "cursor", "windsurf"],
|
|
46
|
-
autoRegister: true,
|
|
47
|
-
excludeServers: [],
|
|
48
|
-
preferManualConfig: true,
|
|
49
|
-
},
|
|
50
|
-
defaultRegistry: {
|
|
51
|
-
enabled: true,
|
|
52
|
-
includeBuiltInTools: true,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
if (!fs.existsSync(this.configPath)) {
|
|
56
|
-
return defaultConfig;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const content = fs.readFileSync(this.configPath, "utf-8");
|
|
60
|
-
const userConfig = JSON.parse(content);
|
|
61
|
-
// Merge with defaults
|
|
62
|
-
return {
|
|
63
|
-
...defaultConfig,
|
|
64
|
-
...userConfig,
|
|
65
|
-
autoDiscovery: {
|
|
66
|
-
...defaultConfig.autoDiscovery,
|
|
67
|
-
...userConfig.autoDiscovery,
|
|
68
|
-
},
|
|
69
|
-
defaultRegistry: {
|
|
70
|
-
...defaultConfig.defaultRegistry,
|
|
71
|
-
...userConfig.defaultRegistry,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
unifiedRegistryLogger.warn(`Failed to load config from ${this.configPath}, using defaults:`, error);
|
|
77
|
-
return defaultConfig;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Save configuration to file
|
|
82
|
-
*/
|
|
83
|
-
saveConfig() {
|
|
84
|
-
try {
|
|
85
|
-
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
unifiedRegistryLogger.error(`Failed to save config to ${this.configPath}:`, error);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Initialize the unified registry by loading all server sources
|
|
93
|
-
*/
|
|
94
|
-
async initialize() {
|
|
95
|
-
if (this._isInitialized) {
|
|
96
|
-
return; // Already initialized
|
|
97
|
-
}
|
|
98
|
-
unifiedRegistryLogger.debug("Initializing unified registry...");
|
|
99
|
-
// 1. Load manual servers
|
|
100
|
-
await this.loadManualServers();
|
|
101
|
-
// 2. Auto-discover servers if enabled
|
|
102
|
-
if (this.config.autoDiscovery?.enabled) {
|
|
103
|
-
await this.loadAutoDiscoveredServers();
|
|
104
|
-
}
|
|
105
|
-
// 3. Load default registry tools if enabled
|
|
106
|
-
if (this.config.defaultRegistry?.enabled) {
|
|
107
|
-
await this.loadDefaultRegistryTools();
|
|
108
|
-
}
|
|
109
|
-
// 4. Lazy activation - mark all discovered servers as available but test on demand
|
|
110
|
-
await this.markServersAsAvailable();
|
|
111
|
-
this._isInitialized = true;
|
|
112
|
-
unifiedRegistryLogger.info(`Initialization complete: ${this.getTotalServerCount()} servers discovered (${this.getAvailableServerCount()} marked as available)`);
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Mark all discovered servers as available for lazy activation
|
|
116
|
-
*/
|
|
117
|
-
async markServersAsAvailable() {
|
|
118
|
-
unifiedRegistryLogger.debug("Marking discovered servers as available for lazy activation...");
|
|
119
|
-
// Mark manual servers as available
|
|
120
|
-
for (const [serverId, entry] of this.manualServers.entries()) {
|
|
121
|
-
if (entry.config) {
|
|
122
|
-
entry.status = "available"; // Will be tested on first use
|
|
123
|
-
unifiedRegistryLogger.debug(`Marked manual server '${serverId}' as available`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
// Mark auto-discovered servers as available
|
|
127
|
-
for (const [serverId, entry] of this.autoServers.entries()) {
|
|
128
|
-
if (entry.config) {
|
|
129
|
-
entry.status = "available"; // Will be tested on first use
|
|
130
|
-
unifiedRegistryLogger.debug(`Marked auto-discovered server '${serverId}' as available`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
const availableCount = this.getAvailableServerCount();
|
|
134
|
-
unifiedRegistryLogger.info(`Marked ${availableCount} servers as available for lazy activation`);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Load manual servers from configuration
|
|
138
|
-
*/
|
|
139
|
-
async loadManualServers() {
|
|
140
|
-
this.manualServers.clear();
|
|
141
|
-
for (const [serverId, serverConfig] of Object.entries(this.config.mcpServers)) {
|
|
142
|
-
const entry = {
|
|
143
|
-
id: serverId,
|
|
144
|
-
config: serverConfig,
|
|
145
|
-
source: {
|
|
146
|
-
type: "manual",
|
|
147
|
-
priority: 10, // Highest priority
|
|
148
|
-
metadata: { configPath: this.configPath },
|
|
149
|
-
},
|
|
150
|
-
status: "unknown",
|
|
151
|
-
};
|
|
152
|
-
this.manualServers.set(serverId, entry);
|
|
153
|
-
}
|
|
154
|
-
unifiedRegistryLogger.debug(`Loaded ${this.manualServers.size} manual servers`);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Load auto-discovered servers
|
|
158
|
-
*/
|
|
159
|
-
async loadAutoDiscoveredServers() {
|
|
160
|
-
// Check cache
|
|
161
|
-
if (Date.now() - this.lastAutoDiscovery < this.autoDiscoveryCacheMs) {
|
|
162
|
-
unifiedRegistryLogger.debug("Using cached auto-discovery results");
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const discoveryOptions = {
|
|
167
|
-
searchGlobal: true,
|
|
168
|
-
searchWorkspace: true,
|
|
169
|
-
searchCommonPaths: true,
|
|
170
|
-
includeInactive: true,
|
|
171
|
-
preferredTools: this.config.autoDiscovery?.sources || [],
|
|
172
|
-
};
|
|
173
|
-
unifiedRegistryLogger.debug("Starting auto-discovery with options:", discoveryOptions);
|
|
174
|
-
const discoveryResult = await discoverMCPServers(discoveryOptions);
|
|
175
|
-
unifiedRegistryLogger.debug(`Auto-discovery completed: found ${discoveryResult.discovered.length} servers, ${discoveryResult.errors.length} errors`);
|
|
176
|
-
// Clear previous auto-discovered servers
|
|
177
|
-
this.autoServers.clear();
|
|
178
|
-
// Auto-register discovered servers
|
|
179
|
-
if (this.config.autoDiscovery?.autoRegister) {
|
|
180
|
-
unifiedRegistryLogger.debug("Auto-registering discovered servers...");
|
|
181
|
-
const registrationResult = await autoRegisterMCPServers(this.autoRegistryInstance, discoveryOptions);
|
|
182
|
-
unifiedRegistryLogger.debug(`Auto-registered ${registrationResult.registered.length} servers, ` +
|
|
183
|
-
`${registrationResult.failed.length} failed`);
|
|
184
|
-
}
|
|
185
|
-
// Add discovered servers to registry
|
|
186
|
-
let addedCount = 0;
|
|
187
|
-
let skippedCount = 0;
|
|
188
|
-
for (const discoveredServer of discoveryResult.discovered) {
|
|
189
|
-
// Skip if manually configured and preferManualConfig is true
|
|
190
|
-
if (this.config.autoDiscovery?.preferManualConfig &&
|
|
191
|
-
this.manualServers.has(discoveredServer.id)) {
|
|
192
|
-
unifiedRegistryLogger.debug(`Skipping auto-discovered '${discoveredServer.id}' - manual config takes precedence`);
|
|
193
|
-
skippedCount++;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
// Skip if explicitly excluded
|
|
197
|
-
if (this.config.autoDiscovery?.excludeServers?.includes(discoveredServer.id)) {
|
|
198
|
-
unifiedRegistryLogger.debug(`Skipping excluded server '${discoveredServer.id}'`);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const entry = {
|
|
202
|
-
id: discoveredServer.id,
|
|
203
|
-
config: {
|
|
204
|
-
name: discoveredServer.id,
|
|
205
|
-
command: discoveredServer.command,
|
|
206
|
-
args: discoveredServer.args,
|
|
207
|
-
env: discoveredServer.env,
|
|
208
|
-
cwd: discoveredServer.cwd,
|
|
209
|
-
transport: "stdio",
|
|
210
|
-
},
|
|
211
|
-
source: {
|
|
212
|
-
type: "auto",
|
|
213
|
-
priority: 7, // Medium priority
|
|
214
|
-
metadata: {
|
|
215
|
-
discoverySource: discoveredServer.source,
|
|
216
|
-
configPath: discoveredServer.configPath,
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
status: "unknown",
|
|
220
|
-
};
|
|
221
|
-
this.autoServers.set(discoveredServer.id, entry);
|
|
222
|
-
addedCount++;
|
|
223
|
-
}
|
|
224
|
-
this.lastAutoDiscovery = Date.now();
|
|
225
|
-
unifiedRegistryLogger.debug(`Auto-discovery complete: added ${addedCount} servers, skipped ${skippedCount}, total auto servers: ${this.autoServers.size}`);
|
|
226
|
-
}
|
|
227
|
-
catch (error) {
|
|
228
|
-
unifiedRegistryLogger.error(`Auto-discovery failed: ${error}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
15
|
+
availableServers = new Set();
|
|
231
16
|
/**
|
|
232
|
-
*
|
|
17
|
+
* Initialize with auto-discovery
|
|
233
18
|
*/
|
|
234
|
-
async
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
await initializeNeuroLinkMCP();
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
unifiedRegistryLogger.warn("Failed to initialize built-in NeuroLink MCP servers:", error);
|
|
249
|
-
}
|
|
250
|
-
// Get tools from default registry
|
|
251
|
-
const tools = defaultToolRegistry.listTools();
|
|
252
|
-
const serverGroups = new Map();
|
|
253
|
-
// Group tools by server
|
|
254
|
-
for (const tool of tools) {
|
|
255
|
-
if (!serverGroups.has(tool.server)) {
|
|
256
|
-
serverGroups.set(tool.server, []);
|
|
257
|
-
}
|
|
258
|
-
serverGroups.get(tool.server).push(tool);
|
|
259
|
-
}
|
|
260
|
-
// Create server entries for each server
|
|
261
|
-
for (const [serverId, serverTools] of serverGroups) {
|
|
262
|
-
const entry = {
|
|
263
|
-
id: serverId,
|
|
264
|
-
source: {
|
|
265
|
-
type: "default",
|
|
266
|
-
priority: 5, // Lowest priority
|
|
267
|
-
metadata: { toolCount: serverTools.length },
|
|
268
|
-
},
|
|
269
|
-
status: "available",
|
|
270
|
-
};
|
|
271
|
-
this.defaultServers.set(serverId, entry);
|
|
272
|
-
}
|
|
273
|
-
unifiedRegistryLogger.debug(`Loaded ${this.defaultServers.size} default registry servers with ${tools.length} total tools`);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Execute a tool using unified registry with fallback
|
|
277
|
-
*/
|
|
278
|
-
async executeTool(toolName, params, context, options = {}) {
|
|
279
|
-
const { preferredSource, fallbackEnabled = true, validateBeforeExecution = true, timeoutMs = 30000, } = options;
|
|
280
|
-
// Determine execution order based on preferences and tool type
|
|
281
|
-
const executionOrder = this.getExecutionOrder(preferredSource, toolName);
|
|
282
|
-
for (const sourceType of executionOrder) {
|
|
283
|
-
try {
|
|
284
|
-
const result = await this.executeFromSource(sourceType, toolName, params, context, { timeoutMs, validateBeforeExecution });
|
|
285
|
-
if (result.success || !fallbackEnabled) {
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
unifiedRegistryLogger.debug(`Execution failed from ${sourceType}, trying next source...`);
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
unifiedRegistryLogger.debug(`Error executing from ${sourceType}: ${error}`);
|
|
292
|
-
if (!fallbackEnabled) {
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// All sources failed
|
|
298
|
-
throw new Error(`Tool '${toolName}' execution failed from all available sources`);
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Execute tool from specific source
|
|
302
|
-
*/
|
|
303
|
-
async executeFromSource(sourceType, toolName, params, context, options) {
|
|
304
|
-
switch (sourceType) {
|
|
305
|
-
case "manual":
|
|
306
|
-
return this.executeFromManualConfig(toolName, params, context, options);
|
|
307
|
-
case "auto":
|
|
308
|
-
return this.executeFromAutoRegistry(toolName, params, context, options);
|
|
309
|
-
case "default":
|
|
310
|
-
return this.executeFromDefaultRegistry(toolName, params, context, options);
|
|
311
|
-
default:
|
|
312
|
-
throw new Error(`Unknown source type: ${sourceType}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Execute tool from manual configuration
|
|
317
|
-
*/
|
|
318
|
-
async executeFromManualConfig(toolName, params, context, options) {
|
|
319
|
-
// Find manual servers that might have this tool
|
|
320
|
-
for (const [serverId, serverEntry] of this.manualServers) {
|
|
321
|
-
if (!serverEntry.config) {
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
try {
|
|
325
|
-
unifiedRegistryLogger.debug(`Trying tool '${toolName}' on manual server '${serverId}'...`);
|
|
326
|
-
// First, try to lazy activate the server if not already activated
|
|
327
|
-
if (serverEntry.status !== "activated") {
|
|
328
|
-
unifiedRegistryLogger.debug(`Lazy activating manual server '${serverId}'...`);
|
|
329
|
-
// Add timeout to prevent hanging on server activation
|
|
330
|
-
const activated = await Promise.race([
|
|
331
|
-
this.lazyActivateServer(serverId, serverEntry),
|
|
332
|
-
new Promise((resolve) => {
|
|
333
|
-
const timer = setTimeout(() => resolve(false), 3000); // 3 second timeout
|
|
334
|
-
timer.unref();
|
|
335
|
-
}),
|
|
336
|
-
]);
|
|
337
|
-
if (!activated) {
|
|
338
|
-
unifiedRegistryLogger.debug(`Failed to activate manual server '${serverId}' within timeout`);
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// If server is activated and has a server instance, use it
|
|
343
|
-
if (serverEntry.status === "activated" && serverEntry.server) {
|
|
344
|
-
unifiedRegistryLogger.debug(`Using activated server instance for '${toolName}' on '${serverId}'`);
|
|
345
|
-
// Check if the tool exists on this server
|
|
346
|
-
const tool = serverEntry.server.tools[toolName];
|
|
347
|
-
if (tool) {
|
|
348
|
-
// Execute using the activated server instance
|
|
349
|
-
const result = await tool.execute(params, context);
|
|
350
|
-
unifiedRegistryLogger.debug(`Successfully executed '${toolName}' on activated server '${serverId}'`);
|
|
351
|
-
return {
|
|
352
|
-
success: true,
|
|
353
|
-
data: result,
|
|
354
|
-
metadata: {
|
|
355
|
-
toolName,
|
|
356
|
-
serverId,
|
|
357
|
-
sessionId: context.sessionId,
|
|
358
|
-
timestamp: Date.now(),
|
|
359
|
-
executionTime: 0,
|
|
360
|
-
},
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
unifiedRegistryLogger.debug(`Tool '${toolName}' not found on activated server '${serverId}'`);
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// Fallback: execute directly if activation failed but server config exists
|
|
369
|
-
unifiedRegistryLogger.debug(`Fallback: executing '${toolName}' directly on manual server '${serverId}'`);
|
|
370
|
-
const result = await this.executeMCPTool(serverEntry.config, toolName, params, options.timeoutMs);
|
|
371
|
-
unifiedRegistryLogger.debug(`Successfully executed '${toolName}' on manual server '${serverId}' (direct)`);
|
|
372
|
-
return {
|
|
373
|
-
success: true,
|
|
374
|
-
data: result,
|
|
375
|
-
metadata: {
|
|
376
|
-
toolName,
|
|
377
|
-
serverId,
|
|
378
|
-
sessionId: context.sessionId,
|
|
379
|
-
timestamp: Date.now(),
|
|
380
|
-
executionTime: 0,
|
|
381
|
-
},
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
unifiedRegistryLogger.debug(`Failed to execute '${toolName}' on manual server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
386
|
-
continue;
|
|
19
|
+
async initialize(options = {}) {
|
|
20
|
+
unifiedRegistryLogger.info("Initializing unified MCP registry...");
|
|
21
|
+
if (this.autoDiscoveryEnabled) {
|
|
22
|
+
const result = await autoRegisterMCPServers(options);
|
|
23
|
+
unifiedRegistryLogger.info(`Auto-discovery complete: ${result.registered} registered, ${result.failed} failed`);
|
|
24
|
+
// Register discovered plugins
|
|
25
|
+
for (const plugin of result.plugins) {
|
|
26
|
+
this.register(plugin);
|
|
27
|
+
this.autoDiscoveredServers.push(plugin);
|
|
28
|
+
this.availableServers.add(plugin.metadata.name);
|
|
387
29
|
}
|
|
388
30
|
}
|
|
389
|
-
throw new Error(`Tool '${toolName}' not found on any manual servers`);
|
|
390
31
|
}
|
|
391
32
|
/**
|
|
392
|
-
*
|
|
33
|
+
* Enable or disable auto-discovery
|
|
393
34
|
*/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const child = spawn(serverConfig.command, serverConfig.args || [], {
|
|
398
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
399
|
-
env: { ...process.env, ...serverConfig.env },
|
|
400
|
-
cwd: serverConfig.cwd,
|
|
401
|
-
});
|
|
402
|
-
return new Promise((resolve, reject) => {
|
|
403
|
-
let isResolved = false;
|
|
404
|
-
const cleanup = () => {
|
|
405
|
-
if (!isResolved) {
|
|
406
|
-
isResolved = true;
|
|
407
|
-
try {
|
|
408
|
-
child.kill("SIGTERM");
|
|
409
|
-
setTimeout(() => {
|
|
410
|
-
if (!child.killed) {
|
|
411
|
-
child.kill("SIGKILL");
|
|
412
|
-
}
|
|
413
|
-
}, 1000);
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
// Ignore cleanup errors
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
const timeout = setTimeout(() => {
|
|
421
|
-
cleanup();
|
|
422
|
-
reject(new Error(`Timeout executing MCP tool '${toolName}' after ${timeoutMs}ms`));
|
|
423
|
-
}, timeoutMs);
|
|
424
|
-
const resolveOnce = (value) => {
|
|
425
|
-
if (!isResolved) {
|
|
426
|
-
isResolved = true;
|
|
427
|
-
clearTimeout(timeout);
|
|
428
|
-
cleanup();
|
|
429
|
-
resolve(value);
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
const rejectOnce = (error) => {
|
|
433
|
-
if (!isResolved) {
|
|
434
|
-
isResolved = true;
|
|
435
|
-
clearTimeout(timeout);
|
|
436
|
-
cleanup();
|
|
437
|
-
reject(error);
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
let responseData = "";
|
|
441
|
-
let initialized = false;
|
|
442
|
-
let initTimeout;
|
|
443
|
-
child.stdout?.on("data", (data) => {
|
|
444
|
-
responseData += data.toString();
|
|
445
|
-
try {
|
|
446
|
-
const lines = responseData.split("\n");
|
|
447
|
-
for (const line of lines) {
|
|
448
|
-
if (line.trim() && line.includes('"result"')) {
|
|
449
|
-
const response = JSON.parse(line.trim());
|
|
450
|
-
if (response.id === 1 && response.result?.capabilities) {
|
|
451
|
-
// Initialize successful, clear init timeout and send notifications/initialized
|
|
452
|
-
if (initTimeout) {
|
|
453
|
-
clearTimeout(initTimeout);
|
|
454
|
-
}
|
|
455
|
-
initialized = true;
|
|
456
|
-
// Send notifications/initialized first
|
|
457
|
-
const initializedNotification = {
|
|
458
|
-
jsonrpc: "2.0",
|
|
459
|
-
method: "notifications/initialized",
|
|
460
|
-
};
|
|
461
|
-
child.stdin?.write(JSON.stringify(initializedNotification) + "\n");
|
|
462
|
-
// Then execute the tool
|
|
463
|
-
const toolCallRequest = {
|
|
464
|
-
jsonrpc: "2.0",
|
|
465
|
-
id: 2,
|
|
466
|
-
method: "tools/call",
|
|
467
|
-
params: {
|
|
468
|
-
name: toolName,
|
|
469
|
-
arguments: toolParams,
|
|
470
|
-
},
|
|
471
|
-
};
|
|
472
|
-
child.stdin?.write(JSON.stringify(toolCallRequest) + "\n");
|
|
473
|
-
}
|
|
474
|
-
else if (response.id === 2) {
|
|
475
|
-
if (response.result) {
|
|
476
|
-
// Extract the text content from MCP result format
|
|
477
|
-
if (response.result.content &&
|
|
478
|
-
Array.isArray(response.result.content)) {
|
|
479
|
-
const textContent = response.result.content.find((item) => item.type === "text");
|
|
480
|
-
if (textContent) {
|
|
481
|
-
try {
|
|
482
|
-
resolveOnce(JSON.parse(textContent.text));
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
resolveOnce(textContent.text);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
resolveOnce(response.result);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
resolveOnce(response.result);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else if (response.error) {
|
|
497
|
-
rejectOnce(new Error(`MCP Error: ${response.error.message || "Unknown error"}`));
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
rejectOnce(new Error("Unknown MCP response format"));
|
|
501
|
-
}
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
else if (line.trim() && line.includes('"error"')) {
|
|
506
|
-
const response = JSON.parse(line.trim());
|
|
507
|
-
if (response.error) {
|
|
508
|
-
rejectOnce(new Error(`MCP Error: ${response.error.message || "Unknown error"}`));
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
catch (parseError) {
|
|
515
|
-
// Continue parsing - don't fail on parse errors
|
|
516
|
-
unifiedRegistryLogger.debug(`Parse error (continuing): ${parseError}`);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
child.stderr?.on("data", (data) => {
|
|
520
|
-
const output = data.toString();
|
|
521
|
-
if (output.includes("running on stdio") ||
|
|
522
|
-
output.includes("Allowed directories")) {
|
|
523
|
-
unifiedRegistryLogger.debug(`MCP server status: ${output.trim()}`);
|
|
524
|
-
// Start initialization once server is ready (only if not already initialized)
|
|
525
|
-
if (!initialized) {
|
|
526
|
-
initialized = true;
|
|
527
|
-
if (initTimeout) {
|
|
528
|
-
clearTimeout(initTimeout);
|
|
529
|
-
}
|
|
530
|
-
setTimeout(() => {
|
|
531
|
-
const initRequest = {
|
|
532
|
-
jsonrpc: "2.0",
|
|
533
|
-
id: 1,
|
|
534
|
-
method: "initialize",
|
|
535
|
-
params: {
|
|
536
|
-
protocolVersion: "2024-11-05",
|
|
537
|
-
capabilities: {},
|
|
538
|
-
clientInfo: {
|
|
539
|
-
name: "neurolink-unified-registry",
|
|
540
|
-
version: "1.0.0",
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
try {
|
|
545
|
-
child.stdin?.write(JSON.stringify(initRequest) + "\n");
|
|
546
|
-
}
|
|
547
|
-
catch (writeError) {
|
|
548
|
-
rejectOnce(new Error(`Failed to write to MCP server: ${writeError}`));
|
|
549
|
-
}
|
|
550
|
-
}, 200);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
unifiedRegistryLogger.error(`MCP Server Error: ${output.trim()}`);
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
child.on("error", (error) => {
|
|
558
|
-
rejectOnce(new Error(`MCP server spawn error: ${error.message}`));
|
|
559
|
-
});
|
|
560
|
-
child.on("exit", (code, signal) => {
|
|
561
|
-
if (!isResolved) {
|
|
562
|
-
rejectOnce(new Error(`MCP server exited unexpectedly with code ${code}, signal ${signal}`));
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
child.on("close", (code) => {
|
|
566
|
-
if (!isResolved) {
|
|
567
|
-
rejectOnce(new Error(`MCP server closed unexpectedly with code ${code}`));
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
throw new Error("SSE transport not yet implemented for unified registry");
|
|
35
|
+
setAutoDiscovery(enabled) {
|
|
36
|
+
this.autoDiscoveryEnabled = enabled;
|
|
37
|
+
unifiedRegistryLogger.info(`Auto-discovery ${enabled ? "enabled" : "disabled"}`);
|
|
573
38
|
}
|
|
574
39
|
/**
|
|
575
|
-
*
|
|
40
|
+
* Refresh discovery
|
|
576
41
|
*/
|
|
577
|
-
async
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
});
|
|
42
|
+
async refresh(options = {}) {
|
|
43
|
+
this.clear();
|
|
44
|
+
this.autoDiscoveredServers = [];
|
|
45
|
+
this.availableServers.clear();
|
|
46
|
+
await this.initialize(options);
|
|
583
47
|
}
|
|
584
48
|
/**
|
|
585
|
-
*
|
|
586
|
-
*/
|
|
587
|
-
async executeFromDefaultRegistry(toolName, params, context, options) {
|
|
588
|
-
return defaultToolRegistry.executeTool(toolName, params, context, {
|
|
589
|
-
validateInput: options.validateBeforeExecution,
|
|
590
|
-
validatePermissions: options.validateBeforeExecution,
|
|
591
|
-
timeoutMs: options.timeoutMs,
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Get execution order based on preferences and tool type
|
|
596
|
-
*/
|
|
597
|
-
getExecutionOrder(preferredSource, toolName) {
|
|
598
|
-
// For AI-related tools, prioritize built-in NeuroLink tools first
|
|
599
|
-
const aiToolNames = [
|
|
600
|
-
"generate-text",
|
|
601
|
-
"select-provider",
|
|
602
|
-
"check-provider-status",
|
|
603
|
-
"analyze-ai-usage",
|
|
604
|
-
"benchmark-provider-performance",
|
|
605
|
-
"optimize-prompt-parameters",
|
|
606
|
-
"generate-test-cases",
|
|
607
|
-
"refactor-code",
|
|
608
|
-
"generate-documentation",
|
|
609
|
-
"debug-ai-output",
|
|
610
|
-
"get-current-time",
|
|
611
|
-
"format-text",
|
|
612
|
-
];
|
|
613
|
-
// Built-in utility tools that should use default registry to avoid hanging
|
|
614
|
-
const builtinUtilityTools = [
|
|
615
|
-
"calculate-date-difference",
|
|
616
|
-
"format-number",
|
|
617
|
-
"get-current-time",
|
|
618
|
-
];
|
|
619
|
-
const isAITool = toolName && aiToolNames.includes(toolName);
|
|
620
|
-
const isBuiltinTool = toolName && builtinUtilityTools.includes(toolName);
|
|
621
|
-
unifiedRegistryLogger.debug(`Tool: ${toolName}, isAITool: ${isAITool}, isBuiltinTool: ${isBuiltinTool}, preferredSource: ${preferredSource}`);
|
|
622
|
-
// For builtin utility tools, always prioritize default registry to avoid hanging manual servers
|
|
623
|
-
if (isBuiltinTool) {
|
|
624
|
-
const builtinOrder = [
|
|
625
|
-
"default",
|
|
626
|
-
"auto",
|
|
627
|
-
"manual",
|
|
628
|
-
];
|
|
629
|
-
unifiedRegistryLogger.debug(`Using builtin tool execution order: [${builtinOrder.join(", ")}]`);
|
|
630
|
-
return builtinOrder;
|
|
631
|
-
}
|
|
632
|
-
// Default order prioritizes built-in AI tools for AI-related commands
|
|
633
|
-
const defaultOrder = isAITool
|
|
634
|
-
? ["default", "manual", "auto"] // AI tools: try built-in first
|
|
635
|
-
: ["manual", "auto", "default"]; // Other tools: try manual config first
|
|
636
|
-
if (!preferredSource) {
|
|
637
|
-
unifiedRegistryLogger.debug(`Using default execution order: [${defaultOrder.join(", ")}]`);
|
|
638
|
-
return defaultOrder;
|
|
639
|
-
}
|
|
640
|
-
// Put preferred source first
|
|
641
|
-
const order = [preferredSource];
|
|
642
|
-
for (const source of defaultOrder) {
|
|
643
|
-
if (source !== preferredSource) {
|
|
644
|
-
order.push(source);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
unifiedRegistryLogger.debug(`Using custom execution order: [${order.join(", ")}]`);
|
|
648
|
-
return order;
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* List all available tools from all sources
|
|
652
|
-
*/
|
|
653
|
-
async listAllTools(criteria = {}) {
|
|
654
|
-
const tools = [];
|
|
655
|
-
// Add tools from each source based on criteria
|
|
656
|
-
if (!criteria.source || criteria.source === "manual") {
|
|
657
|
-
// For manual servers, just list placeholders without trying to activate
|
|
658
|
-
// Activation will happen on-demand during tool execution
|
|
659
|
-
for (const [serverId, entry] of this.manualServers) {
|
|
660
|
-
if (entry.status === "activated" && entry.server) {
|
|
661
|
-
// Get tools from already activated servers
|
|
662
|
-
const serverTools = Object.values(entry.server.tools);
|
|
663
|
-
tools.push(...serverTools.map((tool) => ({
|
|
664
|
-
name: tool.name,
|
|
665
|
-
server: serverId,
|
|
666
|
-
source: "manual",
|
|
667
|
-
description: tool.description,
|
|
668
|
-
category: "manual",
|
|
669
|
-
isImplemented: true,
|
|
670
|
-
})));
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
// Add placeholder for non-activated servers (no activation attempt)
|
|
674
|
-
tools.push({
|
|
675
|
-
name: `${serverId}-placeholder`,
|
|
676
|
-
server: serverId,
|
|
677
|
-
source: "manual",
|
|
678
|
-
description: `Manual server: ${serverId} (not activated)`,
|
|
679
|
-
category: "manual",
|
|
680
|
-
isImplemented: false,
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
if (!criteria.source || criteria.source === "auto") {
|
|
686
|
-
// For auto-discovered servers, don't try to activate them all during listing
|
|
687
|
-
// Instead, just show placeholder tools and activate on demand
|
|
688
|
-
for (const [serverId, entry] of this.autoServers) {
|
|
689
|
-
if (entry.status === "activated" && entry.server) {
|
|
690
|
-
// Get tools from already activated server
|
|
691
|
-
const serverTools = Object.values(entry.server.tools);
|
|
692
|
-
tools.push(...serverTools.map((tool) => ({
|
|
693
|
-
name: tool.name,
|
|
694
|
-
server: serverId,
|
|
695
|
-
source: "auto",
|
|
696
|
-
description: tool.description,
|
|
697
|
-
category: "auto",
|
|
698
|
-
isImplemented: true,
|
|
699
|
-
})));
|
|
700
|
-
}
|
|
701
|
-
else if (entry.status === "available") {
|
|
702
|
-
// For available but not activated servers, add placeholder tools
|
|
703
|
-
const serverName = entry.config?.name || serverId;
|
|
704
|
-
tools.push({
|
|
705
|
-
name: `${serverName}-tools`,
|
|
706
|
-
server: serverId,
|
|
707
|
-
source: "auto",
|
|
708
|
-
description: `Tools from ${serverName} server (will be activated on first use)`,
|
|
709
|
-
category: "auto-placeholder",
|
|
710
|
-
isImplemented: false,
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
// Also include default auto registry tools
|
|
715
|
-
const autoTools = this.autoRegistryInstance.listTools(criteria);
|
|
716
|
-
tools.push(...autoTools.map((tool) => ({
|
|
717
|
-
...tool,
|
|
718
|
-
source: "auto",
|
|
719
|
-
})));
|
|
720
|
-
}
|
|
721
|
-
if (!criteria.source || criteria.source === "default") {
|
|
722
|
-
const defaultTools = defaultToolRegistry.listTools(criteria);
|
|
723
|
-
tools.push(...defaultTools.map((tool) => ({
|
|
724
|
-
...tool,
|
|
725
|
-
source: "default",
|
|
726
|
-
})));
|
|
727
|
-
}
|
|
728
|
-
return tools;
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* List all servers from all sources
|
|
732
|
-
*/
|
|
733
|
-
listAllServers() {
|
|
734
|
-
const servers = [];
|
|
735
|
-
// Add servers from all sources
|
|
736
|
-
servers.push(...Array.from(this.manualServers.values()));
|
|
737
|
-
servers.push(...Array.from(this.autoServers.values()));
|
|
738
|
-
servers.push(...Array.from(this.defaultServers.values()));
|
|
739
|
-
// Sort by priority (higher priority first)
|
|
740
|
-
return servers.sort((a, b) => b.source.priority - a.source.priority);
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Get total server count across all sources
|
|
49
|
+
* Get total server count
|
|
744
50
|
*/
|
|
745
51
|
getTotalServerCount() {
|
|
746
|
-
return
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Add a manual server configuration
|
|
750
|
-
*/
|
|
751
|
-
addManualServer(serverId, config) {
|
|
752
|
-
this.config.mcpServers[serverId] = config;
|
|
753
|
-
this.saveConfig();
|
|
754
|
-
const entry = {
|
|
755
|
-
id: serverId,
|
|
756
|
-
config,
|
|
757
|
-
source: {
|
|
758
|
-
type: "manual",
|
|
759
|
-
priority: 10,
|
|
760
|
-
metadata: { configPath: this.configPath },
|
|
761
|
-
},
|
|
762
|
-
status: "unknown",
|
|
763
|
-
};
|
|
764
|
-
this.manualServers.set(serverId, entry);
|
|
765
|
-
unifiedRegistryLogger.info(`Added manual server: ${serverId}`);
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Remove a manual server configuration
|
|
769
|
-
*/
|
|
770
|
-
removeManualServer(serverId) {
|
|
771
|
-
if (this.config.mcpServers[serverId]) {
|
|
772
|
-
delete this.config.mcpServers[serverId];
|
|
773
|
-
this.saveConfig();
|
|
774
|
-
this.manualServers.delete(serverId);
|
|
775
|
-
unifiedRegistryLogger.info(`Removed manual server: ${serverId}`);
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* Update auto-discovery configuration
|
|
782
|
-
*/
|
|
783
|
-
updateAutoDiscoveryConfig(config) {
|
|
784
|
-
this.config.autoDiscovery = {
|
|
785
|
-
...this.config.autoDiscovery,
|
|
786
|
-
...config,
|
|
787
|
-
};
|
|
788
|
-
this.saveConfig();
|
|
789
|
-
unifiedRegistryLogger.info("Updated auto-discovery configuration");
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Force refresh of auto-discovered servers
|
|
793
|
-
*/
|
|
794
|
-
async refreshAutoDiscovery() {
|
|
795
|
-
this.lastAutoDiscovery = 0; // Reset cache
|
|
796
|
-
await this.loadAutoDiscoveredServers();
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Get current configuration
|
|
800
|
-
*/
|
|
801
|
-
getConfig() {
|
|
802
|
-
return { ...this.config };
|
|
52
|
+
return this.list().length + this.manualServers.size;
|
|
803
53
|
}
|
|
804
54
|
/**
|
|
805
|
-
* Get
|
|
55
|
+
* Get available server count
|
|
806
56
|
*/
|
|
807
|
-
|
|
808
|
-
return
|
|
809
|
-
manual: {
|
|
810
|
-
servers: this.manualServers.size,
|
|
811
|
-
tools: 0, // Would be calculated from actual manual servers
|
|
812
|
-
},
|
|
813
|
-
auto: {
|
|
814
|
-
servers: this.autoServers.size,
|
|
815
|
-
tools: this.autoRegistryInstance.listTools().length,
|
|
816
|
-
},
|
|
817
|
-
default: {
|
|
818
|
-
servers: this.defaultServers.size,
|
|
819
|
-
tools: defaultToolRegistry.listTools().length,
|
|
820
|
-
},
|
|
821
|
-
total: {
|
|
822
|
-
servers: this.getTotalServerCount(),
|
|
823
|
-
tools: this.autoRegistryInstance.listTools().length +
|
|
824
|
-
defaultToolRegistry.listTools().length +
|
|
825
|
-
this.getActivatedToolCount(),
|
|
826
|
-
},
|
|
827
|
-
};
|
|
57
|
+
getAvailableServerCount() {
|
|
58
|
+
return this.availableServers.size;
|
|
828
59
|
}
|
|
829
60
|
/**
|
|
830
61
|
* Get auto-discovered servers
|
|
831
62
|
*/
|
|
832
63
|
getAutoDiscoveredServers() {
|
|
833
|
-
return this.
|
|
64
|
+
return this.autoDiscoveredServers;
|
|
834
65
|
}
|
|
835
66
|
/**
|
|
836
67
|
* Get manual servers
|
|
@@ -839,573 +70,93 @@ export class UnifiedMCPRegistry {
|
|
|
839
70
|
return this.manualServers;
|
|
840
71
|
}
|
|
841
72
|
/**
|
|
842
|
-
*
|
|
73
|
+
* List all tools from all registered plugins
|
|
843
74
|
*/
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
tools: this.autoRegistryInstance.listTools().length,
|
|
853
|
-
},
|
|
854
|
-
default: {
|
|
855
|
-
servers: this.defaultServers.size,
|
|
856
|
-
tools: defaultToolRegistry.listTools().length,
|
|
857
|
-
},
|
|
858
|
-
total: {
|
|
859
|
-
servers: this.getTotalServerCount(),
|
|
860
|
-
tools: this.autoRegistryInstance.listTools().length +
|
|
861
|
-
defaultToolRegistry.listTools().length +
|
|
862
|
-
this.getActivatedToolCount(),
|
|
863
|
-
},
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Activate discovered servers by testing connectivity and loading available tools
|
|
868
|
-
*/
|
|
869
|
-
async activateDiscoveredServers() {
|
|
870
|
-
unifiedRegistryLogger.debug("Activating discovered servers...");
|
|
871
|
-
// const activationPromises: Promise<void>[] = [];
|
|
872
|
-
const maxConcurrentActivations = 3; // Reduced from 5 to 3 to avoid overwhelming system
|
|
873
|
-
// Collect all servers to activate (manual + auto-discovered)
|
|
874
|
-
const serversToActivate = [
|
|
875
|
-
...Array.from(this.manualServers.entries()),
|
|
876
|
-
...Array.from(this.autoServers.entries()),
|
|
877
|
-
];
|
|
878
|
-
unifiedRegistryLogger.debug(`Found ${serversToActivate.length} servers to activate`);
|
|
879
|
-
// Process servers in batches
|
|
880
|
-
for (let i = 0; i < serversToActivate.length; i += maxConcurrentActivations) {
|
|
881
|
-
const batch = serversToActivate.slice(i, i + maxConcurrentActivations);
|
|
882
|
-
unifiedRegistryLogger.debug(`Activating batch ${Math.floor(i / maxConcurrentActivations) + 1}: servers ${i + 1}-${Math.min(i + maxConcurrentActivations, serversToActivate.length)}`);
|
|
883
|
-
const batchPromises = batch.map(async ([serverId, entry]) => {
|
|
884
|
-
try {
|
|
885
|
-
await this.activateServer(serverId, entry);
|
|
886
|
-
}
|
|
887
|
-
catch (error) {
|
|
888
|
-
unifiedRegistryLogger.warn(`Failed to activate server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
await Promise.allSettled(batchPromises);
|
|
892
|
-
}
|
|
893
|
-
const availableCount = this.getAvailableServerCount();
|
|
894
|
-
unifiedRegistryLogger.info(`Server activation complete: ${availableCount} servers activated`);
|
|
895
|
-
}
|
|
896
|
-
/**
|
|
897
|
-
* Activate a single server by testing connectivity and loading tools
|
|
898
|
-
*/
|
|
899
|
-
async activateServer(serverId, entry) {
|
|
900
|
-
if (!entry.config) {
|
|
901
|
-
return; // Skip servers without config (default registry servers)
|
|
902
|
-
}
|
|
903
|
-
const activationTimeout = 5000; // Reduced from 10 seconds to 5 seconds
|
|
904
|
-
try {
|
|
905
|
-
// Test server connectivity with timeout
|
|
906
|
-
const isConnected = await Promise.race([
|
|
907
|
-
this.testServerConnectivity(entry.config),
|
|
908
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("Activation timeout")), activationTimeout)),
|
|
909
|
-
]);
|
|
910
|
-
if (isConnected) {
|
|
911
|
-
// Try to load tools from the server
|
|
912
|
-
const tools = await this.loadServerTools(entry.config);
|
|
913
|
-
if (tools && tools.length > 0) {
|
|
914
|
-
entry.status = "activated"; // Changed from 'available' to 'activated'
|
|
915
|
-
entry.server = await this.createServerInstance(serverId, entry.config, tools);
|
|
916
|
-
// Register tools with the auto registry instance
|
|
917
|
-
if (entry.server) {
|
|
918
|
-
await this.autoRegistryInstance.registerServer(entry.server);
|
|
919
|
-
}
|
|
920
|
-
unifiedRegistryLogger.debug(`Activated server '${serverId}' with ${tools.length} tools`);
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
entry.status = "unavailable";
|
|
924
|
-
unifiedRegistryLogger.debug(`Server '${serverId}' connected but no tools available`);
|
|
925
|
-
}
|
|
75
|
+
async listAllTools() {
|
|
76
|
+
const allTools = [];
|
|
77
|
+
const plugins = this.list();
|
|
78
|
+
for (const plugin of plugins) {
|
|
79
|
+
try {
|
|
80
|
+
// Get tools from plugin metadata if available
|
|
81
|
+
const tools = await this.listTools();
|
|
82
|
+
allTools.push(...tools);
|
|
926
83
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
unifiedRegistryLogger.debug(`Server '${serverId}' connectivity test failed`);
|
|
84
|
+
catch (error) {
|
|
85
|
+
unifiedRegistryLogger.warn(`Failed to get tools from ${plugin.metadata.name}:`, error);
|
|
930
86
|
}
|
|
931
87
|
}
|
|
932
|
-
|
|
933
|
-
entry.status = "unavailable";
|
|
934
|
-
unifiedRegistryLogger.debug(`Server '${serverId}' activation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
935
|
-
}
|
|
88
|
+
return allTools;
|
|
936
89
|
}
|
|
937
90
|
/**
|
|
938
|
-
*
|
|
91
|
+
* Execute a tool through the registry
|
|
939
92
|
*/
|
|
940
|
-
async
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (serverConfig.transport === "stdio") {
|
|
944
|
-
return new Promise((resolve) => {
|
|
945
|
-
let resolved = false;
|
|
946
|
-
let child;
|
|
947
|
-
// Much shorter timeout for basic connectivity test
|
|
948
|
-
const timeout = setTimeout(() => {
|
|
949
|
-
if (!resolved) {
|
|
950
|
-
resolved = true;
|
|
951
|
-
if (child) {
|
|
952
|
-
this.cleanupChildProcess(child);
|
|
953
|
-
}
|
|
954
|
-
resolve(false);
|
|
955
|
-
}
|
|
956
|
-
}, 2000); // Reduced from 5000ms to 2000ms
|
|
957
|
-
// CRITICAL FIX: Unref the timeout to prevent event loop hanging
|
|
958
|
-
timeout.unref();
|
|
959
|
-
const safeResolve = (value) => {
|
|
960
|
-
if (!resolved) {
|
|
961
|
-
resolved = true;
|
|
962
|
-
clearTimeout(timeout);
|
|
963
|
-
if (child) {
|
|
964
|
-
this.cleanupChildProcess(child);
|
|
965
|
-
}
|
|
966
|
-
resolve(value);
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
child = spawn(serverConfig.command, serverConfig.args || [], {
|
|
970
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
971
|
-
env: { ...process.env, ...serverConfig.env },
|
|
972
|
-
cwd: serverConfig.cwd,
|
|
973
|
-
});
|
|
974
|
-
// CRITICAL FIX: Unref the child process to prevent event loop hanging
|
|
975
|
-
child.unref();
|
|
976
|
-
child.on("spawn", () => {
|
|
977
|
-
safeResolve(true);
|
|
978
|
-
});
|
|
979
|
-
child.on("error", (error) => {
|
|
980
|
-
// Log specific error types that are common
|
|
981
|
-
if (error.message.includes("ENOENT")) {
|
|
982
|
-
unifiedRegistryLogger.debug(`Server command not found: ${serverConfig.command}`);
|
|
983
|
-
}
|
|
984
|
-
safeResolve(false);
|
|
985
|
-
});
|
|
986
|
-
child.on("exit", () => {
|
|
987
|
-
safeResolve(true); // If it exits cleanly, consider it working
|
|
988
|
-
});
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
// TODO: Add SSE transport connectivity testing
|
|
992
|
-
return false;
|
|
993
|
-
}
|
|
994
|
-
catch (error) {
|
|
995
|
-
unifiedRegistryLogger.debug(`Connectivity test error for ${serverConfig.command}: ${error instanceof Error ? error.message : String(error)}`);
|
|
996
|
-
return false;
|
|
997
|
-
}
|
|
93
|
+
async executeTool(toolName, args, context) {
|
|
94
|
+
unifiedRegistryLogger.info(`Executing tool: ${toolName}`);
|
|
95
|
+
return super.executeTool(toolName, args, context);
|
|
998
96
|
}
|
|
999
97
|
/**
|
|
1000
|
-
*
|
|
98
|
+
* Lazily activate a server by ID
|
|
1001
99
|
*/
|
|
1002
|
-
async
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
const child = spawn(serverConfig.command, serverConfig.args || [], {
|
|
1008
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1009
|
-
env: { ...process.env, ...serverConfig.env },
|
|
1010
|
-
cwd: serverConfig.cwd,
|
|
1011
|
-
});
|
|
1012
|
-
// CRITICAL FIX: Unref the child process to prevent event loop hanging
|
|
1013
|
-
child.unref();
|
|
1014
|
-
const timeout = setTimeout(() => {
|
|
1015
|
-
this.cleanupChildProcess(child);
|
|
1016
|
-
reject(new Error("Tool loading timeout"));
|
|
1017
|
-
}, 8000);
|
|
1018
|
-
// CRITICAL FIX: Unref the timeout to prevent event loop hanging
|
|
1019
|
-
timeout.unref();
|
|
1020
|
-
let responseData = "";
|
|
1021
|
-
let initialized = false;
|
|
1022
|
-
let resolved = false;
|
|
1023
|
-
const safeResolve = (result) => {
|
|
1024
|
-
if (resolved) {
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
resolved = true;
|
|
1028
|
-
clearTimeout(timeout);
|
|
1029
|
-
this.cleanupChildProcess(child);
|
|
1030
|
-
resolve(result);
|
|
1031
|
-
};
|
|
1032
|
-
const safeReject = (error) => {
|
|
1033
|
-
if (resolved) {
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1036
|
-
resolved = true;
|
|
1037
|
-
clearTimeout(timeout);
|
|
1038
|
-
this.cleanupChildProcess(child);
|
|
1039
|
-
reject(error);
|
|
1040
|
-
};
|
|
1041
|
-
// Handle server status messages on stderr
|
|
1042
|
-
child.stderr?.on("data", (data) => {
|
|
1043
|
-
const output = data.toString();
|
|
1044
|
-
if (output.includes("running on stdio") ||
|
|
1045
|
-
output.includes("Allowed directories")) {
|
|
1046
|
-
unifiedRegistryLogger.debug(`MCP server status: ${output.trim()}`);
|
|
1047
|
-
// Start initialization once server is ready
|
|
1048
|
-
if (!initialized) {
|
|
1049
|
-
initialized = true;
|
|
1050
|
-
// Use unref timeout for non-critical initialization delay
|
|
1051
|
-
const initTimeout = setTimeout(() => {
|
|
1052
|
-
const initRequest = {
|
|
1053
|
-
jsonrpc: "2.0",
|
|
1054
|
-
id: 1,
|
|
1055
|
-
method: "initialize",
|
|
1056
|
-
params: {
|
|
1057
|
-
protocolVersion: "2024-11-05",
|
|
1058
|
-
capabilities: { tools: {} },
|
|
1059
|
-
clientInfo: { name: "neurolink", version: "1.0.0" },
|
|
1060
|
-
},
|
|
1061
|
-
};
|
|
1062
|
-
child.stdin?.write(JSON.stringify(initRequest) + "\n");
|
|
1063
|
-
}, 200);
|
|
1064
|
-
initTimeout.unref();
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
child.stdout?.on("data", (data) => {
|
|
1069
|
-
responseData += data.toString();
|
|
1070
|
-
const lines = responseData.split("\n");
|
|
1071
|
-
for (const line of lines) {
|
|
1072
|
-
if (line.trim() && line.includes('"result"')) {
|
|
1073
|
-
try {
|
|
1074
|
-
const response = JSON.parse(line.trim());
|
|
1075
|
-
if (response.id === 1 && response.result?.capabilities) {
|
|
1076
|
-
// Initialize successful, send notifications/initialized
|
|
1077
|
-
const initializedNotification = {
|
|
1078
|
-
jsonrpc: "2.0",
|
|
1079
|
-
method: "notifications/initialized",
|
|
1080
|
-
};
|
|
1081
|
-
child.stdin?.write(JSON.stringify(initializedNotification) + "\n");
|
|
1082
|
-
// Then list tools with unref timeout
|
|
1083
|
-
const toolsTimeout = setTimeout(() => {
|
|
1084
|
-
const listToolsRequest = {
|
|
1085
|
-
jsonrpc: "2.0",
|
|
1086
|
-
id: 2,
|
|
1087
|
-
method: "tools/list",
|
|
1088
|
-
};
|
|
1089
|
-
child.stdin?.write(JSON.stringify(listToolsRequest) + "\n");
|
|
1090
|
-
}, 100);
|
|
1091
|
-
toolsTimeout.unref();
|
|
1092
|
-
}
|
|
1093
|
-
else if (response.id === 2 && response.result?.tools) {
|
|
1094
|
-
safeResolve(response.result.tools || []);
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
catch {
|
|
1099
|
-
// Ignore JSON parsing errors for this line
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
child.on("error", (error) => {
|
|
1105
|
-
safeReject(new Error(`Server process error: ${error.message}`));
|
|
1106
|
-
});
|
|
1107
|
-
child.on("exit", (code) => {
|
|
1108
|
-
if (!resolved) {
|
|
1109
|
-
safeReject(new Error(`Server exited with code ${code}`));
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
return [];
|
|
1115
|
-
}
|
|
1116
|
-
catch (error) {
|
|
1117
|
-
unifiedRegistryLogger.debug(`loadServerTools error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1118
|
-
return [];
|
|
100
|
+
async lazyActivateServer(serverId) {
|
|
101
|
+
unifiedRegistryLogger.info(`Lazy activating server: ${serverId}`);
|
|
102
|
+
// Check if already activated
|
|
103
|
+
if (this.availableServers.has(serverId)) {
|
|
104
|
+
return true;
|
|
1119
105
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
if (child.stdin && !child.stdin.destroyed) {
|
|
1128
|
-
child.stdin.end(); // Close stdin explicitly first
|
|
1129
|
-
child.stdin.destroy();
|
|
1130
|
-
}
|
|
1131
|
-
// Destroy stdout/stderr streams to release handles
|
|
1132
|
-
if (child.stdout && !child.stdout.destroyed) {
|
|
1133
|
-
child.stdout.destroy();
|
|
1134
|
-
}
|
|
1135
|
-
if (child.stderr && !child.stderr.destroyed) {
|
|
1136
|
-
child.stderr.destroy();
|
|
1137
|
-
}
|
|
1138
|
-
// Kill the process if still running
|
|
1139
|
-
if (!child.killed) {
|
|
1140
|
-
child.kill("SIGTERM");
|
|
1141
|
-
// Force kill after 1 second if SIGTERM doesn't work
|
|
1142
|
-
const forceKillTimeout = setTimeout(() => {
|
|
1143
|
-
if (!child.killed) {
|
|
1144
|
-
child.kill("SIGKILL");
|
|
1145
|
-
}
|
|
1146
|
-
}, 1000);
|
|
1147
|
-
forceKillTimeout.unref();
|
|
106
|
+
// Try to find and activate
|
|
107
|
+
const plugin = this.get(serverId);
|
|
108
|
+
if (plugin) {
|
|
109
|
+
try {
|
|
110
|
+
// Mark as available (initialization happens elsewhere)
|
|
111
|
+
this.availableServers.add(serverId);
|
|
112
|
+
return true;
|
|
1148
113
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
// Ignore cleanup errors
|
|
1152
|
-
unifiedRegistryLogger.debug(`Child process cleanup error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
|
-
* Create a server instance from config and tools
|
|
1157
|
-
*/
|
|
1158
|
-
async createServerInstance(serverId, config, tools) {
|
|
1159
|
-
try {
|
|
1160
|
-
const { createMCPServer } = await import("./factory.js");
|
|
1161
|
-
const server = createMCPServer({
|
|
1162
|
-
id: serverId,
|
|
1163
|
-
title: config.name || serverId,
|
|
1164
|
-
description: `Auto-discovered MCP server from ${config.command}`,
|
|
1165
|
-
category: "automation",
|
|
1166
|
-
version: "1.0.0",
|
|
1167
|
-
capabilities: ["tools"],
|
|
1168
|
-
});
|
|
1169
|
-
// Register discovered tools with direct MCP execution
|
|
1170
|
-
for (const tool of tools) {
|
|
1171
|
-
if (tool.name && tool.description) {
|
|
1172
|
-
server.registerTool({
|
|
1173
|
-
name: tool.name,
|
|
1174
|
-
description: tool.description,
|
|
1175
|
-
inputSchema: tool.inputSchema
|
|
1176
|
-
? z.object(tool.inputSchema)
|
|
1177
|
-
: z.record(z.unknown()).optional(),
|
|
1178
|
-
execute: async (params, context) => {
|
|
1179
|
-
const startTime = Date.now();
|
|
1180
|
-
try {
|
|
1181
|
-
unifiedRegistryLogger.debug(`Executing external MCP tool ${tool.name} on server ${serverId}`);
|
|
1182
|
-
// Execute tool directly on external MCP server
|
|
1183
|
-
const result = await this.executeMCPTool(config, tool.name, params);
|
|
1184
|
-
const executionTime = Date.now() - startTime;
|
|
1185
|
-
return {
|
|
1186
|
-
success: true,
|
|
1187
|
-
data: result,
|
|
1188
|
-
metadata: {
|
|
1189
|
-
toolName: tool.name,
|
|
1190
|
-
serverId: serverId,
|
|
1191
|
-
sessionId: context.sessionId,
|
|
1192
|
-
timestamp: Date.now(),
|
|
1193
|
-
executionTime,
|
|
1194
|
-
isExternalMCP: true,
|
|
1195
|
-
},
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
catch (error) {
|
|
1199
|
-
const executionTime = Date.now() - startTime;
|
|
1200
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1201
|
-
unifiedRegistryLogger.debug(`External MCP tool execution failed: ${errorMessage}`);
|
|
1202
|
-
return {
|
|
1203
|
-
success: false,
|
|
1204
|
-
error: `External MCP tool '${tool.name}' failed: ${errorMessage}`,
|
|
1205
|
-
metadata: {
|
|
1206
|
-
toolName: tool.name,
|
|
1207
|
-
serverId: serverId,
|
|
1208
|
-
sessionId: context.sessionId,
|
|
1209
|
-
timestamp: Date.now(),
|
|
1210
|
-
executionTime,
|
|
1211
|
-
isExternalMCP: true,
|
|
1212
|
-
error: errorMessage,
|
|
1213
|
-
},
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
},
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
unifiedRegistryLogger.error(`Failed to activate server ${serverId}:`, error);
|
|
1219
116
|
}
|
|
1220
|
-
return server;
|
|
1221
|
-
}
|
|
1222
|
-
catch (error) {
|
|
1223
|
-
unifiedRegistryLogger.debug(`Failed to create server instance: ${error instanceof Error ? error.message : String(error)}`);
|
|
1224
|
-
return undefined;
|
|
1225
117
|
}
|
|
118
|
+
return false;
|
|
1226
119
|
}
|
|
1227
120
|
/**
|
|
1228
|
-
*
|
|
121
|
+
* Register a manual server
|
|
1229
122
|
*/
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (entry.status === "activated") {
|
|
1234
|
-
// Changed from 'available' to 'activated'
|
|
1235
|
-
count++;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
for (const entry of this.autoServers.values()) {
|
|
1239
|
-
if (entry.status === "activated") {
|
|
1240
|
-
// Changed from 'available' to 'activated'
|
|
1241
|
-
count++;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
for (const entry of this.defaultServers.values()) {
|
|
1245
|
-
if (entry.status === "activated") {
|
|
1246
|
-
// Changed from 'available' to 'activated'
|
|
1247
|
-
count++;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
return count;
|
|
123
|
+
registerManualServer(id, server) {
|
|
124
|
+
this.manualServers.set(id, server);
|
|
125
|
+
this.availableServers.add(id);
|
|
1251
126
|
}
|
|
1252
127
|
/**
|
|
1253
|
-
* Get
|
|
128
|
+
* Get registry statistics
|
|
1254
129
|
*/
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
130
|
+
async getStats() {
|
|
131
|
+
const plugins = this.list();
|
|
132
|
+
const bySource = {};
|
|
133
|
+
const byType = {};
|
|
134
|
+
for (const plugin of plugins) {
|
|
135
|
+
bySource[plugin.source] = (bySource[plugin.source] || 0) + 1;
|
|
136
|
+
// Extract type from name or metadata
|
|
137
|
+
const type = plugin.metadata.name.split("/")[1]?.split("-")[0] || "unknown";
|
|
138
|
+
byType[type] = (byType[type] || 0) + 1;
|
|
1261
139
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
140
|
+
return {
|
|
141
|
+
total: plugins.length,
|
|
142
|
+
bySource,
|
|
143
|
+
byType,
|
|
144
|
+
manual: { servers: this.manualServers.size },
|
|
145
|
+
auto: { servers: this.autoDiscoveredServers.length },
|
|
146
|
+
tools: 0, // Will be populated when tools are registered
|
|
147
|
+
};
|
|
1268
148
|
}
|
|
1269
149
|
/**
|
|
1270
|
-
*
|
|
150
|
+
* Clear all registries
|
|
1271
151
|
*/
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
try {
|
|
1278
|
-
// Handle known servers with predefined tools (bypass hanging tool discovery)
|
|
1279
|
-
if (serverId === "filesystem" &&
|
|
1280
|
-
entry.config.command === "mcp-server-filesystem") {
|
|
1281
|
-
unifiedRegistryLogger.debug(`Using predefined tools for filesystem server '${serverId}'`);
|
|
1282
|
-
// Quick connectivity test first
|
|
1283
|
-
const isConnected = await Promise.race([
|
|
1284
|
-
this.testServerConnectivity(entry.config),
|
|
1285
|
-
new Promise((resolve) => setTimeout(() => resolve(false), 1500)),
|
|
1286
|
-
]);
|
|
1287
|
-
if (isConnected) {
|
|
1288
|
-
// Create predefined filesystem tools with correct tool names from the actual MCP server
|
|
1289
|
-
const predefinedTools = [
|
|
1290
|
-
{
|
|
1291
|
-
name: "list_directory",
|
|
1292
|
-
description: "Get a detailed listing of all files and directories in a specified path. Results clearly distinguish between files and directories with [FILE] and [DIR] prefixes.",
|
|
1293
|
-
inputSchema: {
|
|
1294
|
-
type: "object",
|
|
1295
|
-
properties: {
|
|
1296
|
-
path: {
|
|
1297
|
-
type: "string",
|
|
1298
|
-
description: "Path to list directory contents from",
|
|
1299
|
-
},
|
|
1300
|
-
},
|
|
1301
|
-
required: ["path"],
|
|
1302
|
-
},
|
|
1303
|
-
},
|
|
1304
|
-
{
|
|
1305
|
-
name: "read_file",
|
|
1306
|
-
description: "Read the complete contents of a file from the file system. Handles various text encodings and provides detailed error messages if the file cannot be read.",
|
|
1307
|
-
inputSchema: {
|
|
1308
|
-
type: "object",
|
|
1309
|
-
properties: {
|
|
1310
|
-
path: {
|
|
1311
|
-
type: "string",
|
|
1312
|
-
description: "Path to the file to read",
|
|
1313
|
-
},
|
|
1314
|
-
},
|
|
1315
|
-
required: ["path"],
|
|
1316
|
-
},
|
|
1317
|
-
},
|
|
1318
|
-
{
|
|
1319
|
-
name: "write_file",
|
|
1320
|
-
description: "Create a new file or completely overwrite an existing file with new content. Use with caution as it will overwrite existing files without warning.",
|
|
1321
|
-
inputSchema: {
|
|
1322
|
-
type: "object",
|
|
1323
|
-
properties: {
|
|
1324
|
-
path: {
|
|
1325
|
-
type: "string",
|
|
1326
|
-
description: "Path to the file to write",
|
|
1327
|
-
},
|
|
1328
|
-
content: {
|
|
1329
|
-
type: "string",
|
|
1330
|
-
description: "Content to write to the file",
|
|
1331
|
-
},
|
|
1332
|
-
},
|
|
1333
|
-
required: ["path", "content"],
|
|
1334
|
-
},
|
|
1335
|
-
},
|
|
1336
|
-
{
|
|
1337
|
-
name: "create_directory",
|
|
1338
|
-
description: "Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation.",
|
|
1339
|
-
inputSchema: {
|
|
1340
|
-
type: "object",
|
|
1341
|
-
properties: {
|
|
1342
|
-
path: {
|
|
1343
|
-
type: "string",
|
|
1344
|
-
description: "Path to the directory to create",
|
|
1345
|
-
},
|
|
1346
|
-
},
|
|
1347
|
-
required: ["path"],
|
|
1348
|
-
},
|
|
1349
|
-
},
|
|
1350
|
-
{
|
|
1351
|
-
name: "search_files",
|
|
1352
|
-
description: "Recursively search for files and directories matching a pattern. Searches through all subdirectories from the starting path.",
|
|
1353
|
-
inputSchema: {
|
|
1354
|
-
type: "object",
|
|
1355
|
-
properties: {
|
|
1356
|
-
path: {
|
|
1357
|
-
type: "string",
|
|
1358
|
-
description: "Starting path to search from",
|
|
1359
|
-
},
|
|
1360
|
-
pattern: {
|
|
1361
|
-
type: "string",
|
|
1362
|
-
description: "Pattern to search for",
|
|
1363
|
-
},
|
|
1364
|
-
},
|
|
1365
|
-
required: ["path", "pattern"],
|
|
1366
|
-
},
|
|
1367
|
-
},
|
|
1368
|
-
];
|
|
1369
|
-
entry.status = "activated";
|
|
1370
|
-
entry.server = await this.createServerInstance(serverId, entry.config, predefinedTools);
|
|
1371
|
-
if (entry.server) {
|
|
1372
|
-
await this.autoRegistryInstance.registerServer(entry.server);
|
|
1373
|
-
}
|
|
1374
|
-
unifiedRegistryLogger.debug(`Lazy activated filesystem server '${serverId}' with ${predefinedTools.length} predefined tools`);
|
|
1375
|
-
return true;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
// Original logic for other servers
|
|
1379
|
-
// Quick connectivity test with short timeout
|
|
1380
|
-
const isConnected = await Promise.race([
|
|
1381
|
-
this.testServerConnectivity(entry.config),
|
|
1382
|
-
new Promise((resolve) => setTimeout(() => resolve(false), 1500)),
|
|
1383
|
-
]);
|
|
1384
|
-
if (isConnected) {
|
|
1385
|
-
// Try to load tools
|
|
1386
|
-
const tools = await this.loadServerTools(entry.config);
|
|
1387
|
-
if (tools && tools.length > 0) {
|
|
1388
|
-
entry.status = "activated";
|
|
1389
|
-
entry.server = await this.createServerInstance(serverId, entry.config, tools);
|
|
1390
|
-
if (entry.server) {
|
|
1391
|
-
await this.autoRegistryInstance.registerServer(entry.server);
|
|
1392
|
-
}
|
|
1393
|
-
unifiedRegistryLogger.debug(`Lazy activated server '${serverId}' with ${tools.length} tools`);
|
|
1394
|
-
return true;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
entry.status = "unavailable";
|
|
1398
|
-
unifiedRegistryLogger.debug(`Server '${serverId}' lazy activation failed`);
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
catch (error) {
|
|
1402
|
-
entry.status = "unavailable";
|
|
1403
|
-
unifiedRegistryLogger.debug(`Server '${serverId}' lazy activation error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1404
|
-
return false;
|
|
1405
|
-
}
|
|
152
|
+
clear() {
|
|
153
|
+
super.clear();
|
|
154
|
+
this.autoDiscoveredServers = [];
|
|
155
|
+
this.manualServers.clear();
|
|
156
|
+
this.availableServers.clear();
|
|
1406
157
|
}
|
|
1407
158
|
}
|
|
1408
159
|
/**
|
|
1409
160
|
* Default unified registry instance
|
|
1410
161
|
*/
|
|
1411
|
-
export const
|
|
162
|
+
export const unifiedRegistry = new UnifiedMCPRegistry();
|