@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
482 lines (481 loc) • 16.9 kB
JavaScript
/**
* Enhanced Tool Discovery Service
*
* Extends the base tool discovery service with additional features:
* - Tool annotation support
* - Multi-server coordination
* - Advanced filtering and search
* - Tool versioning and compatibility checking
*
* @module mcp/enhancedToolDiscovery
* @since 8.39.0
*/
import { EventEmitter } from "events";
import { logger } from "../utils/logger.js";
import { withTimeout } from "../utils/async/withTimeout.js";
import { ErrorFactory } from "../utils/errorHandling.js";
import { inferAnnotations } from "./toolAnnotations.js";
import { MultiServerManager } from "./multiServerManager.js";
/**
* Enhanced Tool Discovery Service
*
* Provides advanced tool discovery features including annotation support,
* multi-server coordination, and powerful search capabilities.
*
* @example
* ```typescript
* const discovery = new EnhancedToolDiscovery();
*
* // Discover tools with annotation inference
* const result = await discovery.discoverToolsWithAnnotations(
* "github-server",
* client,
* );
*
* // Search for tools
* const searchResult = await discovery.searchTools({
* category: "file-system",
* annotations: { readOnlyHint: true },
* limit: 10,
* });
*
* // Get tools by safety level
* const safeTools = discovery.getToolsBySafetyLevel("safe");
* ```
*/
export class EnhancedToolDiscovery extends EventEmitter {
toolRegistry = new Map();
serverToolsMap = new Map();
multiServerManager;
discoveryInProgress = new Set();
constructor(multiServerManager) {
super();
this.multiServerManager = multiServerManager ?? new MultiServerManager();
}
/**
* Discover tools with automatic annotation inference
*/
async discoverToolsWithAnnotations(serverId, client, timeout = 10000) {
const startTime = Date.now();
if (this.discoveryInProgress.has(serverId)) {
return {
success: false,
error: `Discovery already in progress for server: ${serverId}`,
toolCount: 0,
tools: [],
duration: Date.now() - startTime,
serverId,
};
}
this.discoveryInProgress.add(serverId);
try {
logger.info(`[EnhancedToolDiscovery] Starting discovery with annotations for: ${serverId}`);
// List tools from server
const listResult = await withTimeout(client.listTools(), timeout, "Discovery timeout");
if (!listResult?.tools) {
throw ErrorFactory.toolExecutionFailed("discoverTools", new Error("No tools returned from server"), serverId);
}
// Clear existing tools for this server
this.clearServerTools(serverId);
const registeredTools = [];
for (const tool of listResult.tools) {
const enhancedTool = this.createEnhancedToolInfo(serverId, tool);
const toolKey = this.createToolKey(serverId, tool.name);
this.toolRegistry.set(toolKey, enhancedTool);
// Track server tools
let serverTools = this.serverToolsMap.get(serverId);
if (!serverTools) {
serverTools = new Set();
this.serverToolsMap.set(serverId, serverTools);
}
serverTools.add(tool.name);
registeredTools.push(enhancedTool);
this.emit("toolDiscovered", {
serverId,
toolName: tool.name,
annotations: enhancedTool.annotations,
timestamp: new Date(),
});
}
logger.info(`[EnhancedToolDiscovery] Discovered ${registeredTools.length} tools with annotations from ${serverId}`);
return {
success: true,
toolCount: registeredTools.length,
tools: registeredTools,
duration: Date.now() - startTime,
serverId,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
logger.error(`[EnhancedToolDiscovery] Discovery failed for ${serverId}:`, error);
return {
success: false,
error: errorMessage,
toolCount: 0,
tools: [],
duration: Date.now() - startTime,
serverId,
};
}
finally {
this.discoveryInProgress.delete(serverId);
}
}
/**
* Create enhanced tool info with annotations
*/
createEnhancedToolInfo(serverId, tool) {
// Infer annotations from tool definition
const annotations = inferAnnotations({
name: tool.name,
description: tool.description ?? "",
});
return {
name: tool.name,
description: tool.description ?? "No description provided",
serverId,
inputSchema: tool.inputSchema,
isAvailable: true,
annotations,
version: "1.0.0",
stats: {
totalCalls: 0,
successfulCalls: 0,
failedCalls: 0,
averageExecutionTime: 0,
lastExecutionTime: 0,
},
metadata: {
category: this.inferCategory(tool),
deprecated: false,
},
};
}
/**
* Infer category from tool definition
*/
inferCategory(tool) {
const name = tool.name.toLowerCase();
const desc = (tool.description ?? "").toLowerCase();
if (name.includes("git") || desc.includes("git")) {
return "version-control";
}
if (name.includes("file") ||
name.includes("read") ||
name.includes("write")) {
return "file-system";
}
if (name.includes("api") || name.includes("http")) {
return "api";
}
if (name.includes("data") || name.includes("query")) {
return "data";
}
if (name.includes("auth") || name.includes("login")) {
return "authentication";
}
if (name.includes("deploy") || name.includes("build")) {
return "deployment";
}
return "general";
}
/**
* Search tools with advanced criteria
*/
searchTools(criteria) {
const startTime = Date.now();
let results = Array.from(this.toolRegistry.values());
// Filter by name
if (criteria.name) {
const searchName = criteria.name.toLowerCase();
results = results.filter((tool) => tool.name.toLowerCase().includes(searchName));
}
// Filter by description
if (criteria.description) {
const keywords = criteria.description.toLowerCase().split(/\s+/);
results = results.filter((tool) => {
const desc = tool.description.toLowerCase();
return keywords.some((keyword) => desc.includes(keyword));
});
}
// Filter by server IDs
if (criteria.serverIds?.length) {
const serverIds = criteria.serverIds;
results = results.filter((tool) => serverIds.includes(tool.serverId));
}
// Filter by category
if (criteria.category) {
results = results.filter((tool) => tool.metadata?.category === criteria.category);
}
// Filter by tags
if (criteria.tags?.length) {
const tags = criteria.tags;
results = results.filter((tool) => {
const toolTags = tool.annotations?.tags ?? [];
return tags.some((tag) => toolTags.includes(tag));
});
}
// Filter by annotations
if (criteria.annotations) {
const annotations = criteria.annotations;
results = results.filter((tool) => {
if (!tool.annotations) {
return false;
}
for (const [key, value] of Object.entries(annotations)) {
const annotationKey = key;
if (tool.annotations[annotationKey] !== value) {
return false;
}
}
return true;
});
}
// Filter unavailable
if (!criteria.includeUnavailable) {
results = results.filter((tool) => tool.isAvailable);
}
// Sort results
if (criteria.sortBy) {
const direction = criteria.sortDirection === "desc" ? -1 : 1;
results.sort((a, b) => {
let comparison = 0;
switch (criteria.sortBy) {
case "name":
comparison = a.name.localeCompare(b.name);
break;
case "calls":
comparison = a.stats.totalCalls - b.stats.totalCalls;
break;
case "successRate": {
const rateA = a.stats.totalCalls > 0
? a.stats.successfulCalls / a.stats.totalCalls
: 0;
const rateB = b.stats.totalCalls > 0
? b.stats.successfulCalls / b.stats.totalCalls
: 0;
comparison = rateA - rateB;
break;
}
case "avgExecutionTime":
comparison =
a.stats.averageExecutionTime - b.stats.averageExecutionTime;
break;
}
return comparison * direction;
});
}
// Apply limit
const totalCount = results.length;
if (criteria.limit && criteria.limit > 0) {
results = results.slice(0, criteria.limit);
}
return {
tools: results,
totalCount,
criteria,
executionTime: Date.now() - startTime,
};
}
/**
* Get tools by safety level
*/
getToolsBySafetyLevel(level) {
return Array.from(this.toolRegistry.values()).filter((tool) => {
const annotations = tool.annotations ?? {};
switch (level) {
case "dangerous":
return annotations.destructiveHint === true;
case "safe":
return annotations.readOnlyHint === true;
case "moderate":
return !annotations.destructiveHint && !annotations.readOnlyHint;
default:
return false;
}
});
}
/**
* Get tools requiring confirmation
*/
getToolsRequiringConfirmation() {
return Array.from(this.toolRegistry.values()).filter((tool) => tool.annotations?.requiresConfirmation === true ||
tool.annotations?.destructiveHint === true);
}
/**
* Get read-only tools
*/
getReadOnlyTools() {
return Array.from(this.toolRegistry.values()).filter((tool) => tool.annotations?.readOnlyHint === true);
}
/**
* Get unified tools from all servers
*/
getUnifiedTools() {
return this.multiServerManager.getUnifiedTools();
}
/**
* Register a server with the multi-server manager
*/
registerServer(server) {
this.multiServerManager.addServer(server);
}
/**
* Update tool annotations
*/
updateToolAnnotations(serverId, toolName, annotations) {
const toolKey = this.createToolKey(serverId, toolName);
const tool = this.toolRegistry.get(toolKey);
if (!tool) {
return false;
}
tool.annotations = {
...tool.annotations,
...annotations,
};
this.emit("annotationsUpdated", {
serverId,
toolName,
annotations: tool.annotations,
timestamp: new Date(),
});
return true;
}
/**
* Check tool compatibility
*/
checkCompatibility(toolName, serverId, targetVersion) {
const toolKey = this.createToolKey(serverId, toolName);
const tool = this.toolRegistry.get(toolKey);
const issues = [];
const warnings = [];
const recommendations = [];
if (!tool) {
return {
compatible: false,
issues: [`Tool '${toolName}' not found on server '${serverId}'`],
warnings: [],
recommendations: [],
};
}
// Check version compatibility
if (targetVersion && tool.version) {
const toolVersion = tool.version.split(".").map(Number);
const target = targetVersion.split(".").map(Number);
if (toolVersion.some(isNaN) || target.some(isNaN)) {
warnings.push(`Non-standard version format: tool=${tool.version}, target=${targetVersion}`);
}
else if (toolVersion[0] !== target[0]) {
issues.push(`Major version mismatch: tool is v${tool.version}, target is v${targetVersion}`);
}
else if (toolVersion[1] < target[1]) {
warnings.push(`Minor version mismatch: tool is v${tool.version}, target is v${targetVersion}`);
}
}
// Check if tool is deprecated
if (tool.metadata?.deprecated) {
warnings.push("This tool is marked as deprecated");
recommendations.push("Consider using an alternative tool if available");
}
// Check if tool requires authentication
if (tool.annotations?.securityLevel === "restricted") {
recommendations.push("This tool requires elevated permissions");
}
return {
compatible: issues.length === 0,
issues,
warnings,
recommendations,
};
}
/**
* Get tool by key
*/
getTool(serverId, toolName) {
return this.toolRegistry.get(this.createToolKey(serverId, toolName));
}
/**
* Get all tools
*/
getAllTools() {
return Array.from(this.toolRegistry.values());
}
/**
* Get tools for a server
*/
getServerTools(serverId) {
const toolNames = this.serverToolsMap.get(serverId);
if (!toolNames) {
return [];
}
return Array.from(toolNames)
.map((name) => this.getTool(serverId, name))
.filter((tool) => tool !== undefined);
}
/**
* Clear tools for a server
*/
clearServerTools(serverId) {
const toolNames = this.serverToolsMap.get(serverId);
if (toolNames) {
for (const toolName of toolNames) {
this.toolRegistry.delete(this.createToolKey(serverId, toolName));
}
this.serverToolsMap.delete(serverId);
}
}
/**
* Create tool key
*/
createToolKey(serverId, toolName) {
return `${serverId}:${toolName}`;
}
/**
* Get statistics
*/
getStatistics() {
const toolsByServer = {};
const toolsByCategory = {};
const toolsBySafetyLevel = {
safe: 0,
moderate: 0,
dangerous: 0,
};
let toolsWithAnnotations = 0;
let deprecatedTools = 0;
for (const tool of this.toolRegistry.values()) {
// By server
toolsByServer[tool.serverId] = (toolsByServer[tool.serverId] ?? 0) + 1;
// By category
const category = tool.metadata?.category ?? "general";
toolsByCategory[category] = (toolsByCategory[category] ?? 0) + 1;
// By safety level
if (tool.annotations?.destructiveHint) {
toolsBySafetyLevel.dangerous++;
}
else if (tool.annotations?.readOnlyHint) {
toolsBySafetyLevel.safe++;
}
else {
toolsBySafetyLevel.moderate++;
}
// Count annotations
if (tool.annotations && Object.keys(tool.annotations).length > 0) {
toolsWithAnnotations++;
}
// Count deprecated
if (tool.metadata?.deprecated) {
deprecatedTools++;
}
}
return {
totalTools: this.toolRegistry.size,
toolsByServer,
toolsByCategory,
toolsBySafetyLevel,
toolsWithAnnotations,
deprecatedTools,
};
}
}