@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and
426 lines (425 loc) • 16.5 kB
JavaScript
/**
* MCP Tool Registry - Extended Registry with Tool Management
* Updated to match industry standard camelCase interfaces
*/
import { MCPRegistry } from "./registry.js";
import { registryLogger } from "../utils/logger.js";
import { randomUUID } from "crypto";
import { directAgentTools } from "../agent/directTools.js";
export class MCPToolRegistry extends MCPRegistry {
tools = new Map();
toolImpls = new Map(); // Store actual tool implementations
toolExecutionStats = new Map();
constructor() {
super();
// Auto-register direct tools on initialization
this.registerDirectTools();
}
/**
* Register all direct tools from directAgentTools
*/
registerDirectTools() {
registryLogger.info("Auto-registering direct tools...");
for (const [toolName, toolDef] of Object.entries(directAgentTools)) {
const toolId = `direct.${toolName}`;
const toolInfo = {
name: toolName,
description: toolDef.description || `Direct tool: ${toolName}`,
inputSchema: {},
serverId: "direct",
category: "built-in",
};
this.tools.set(toolId, toolInfo);
this.toolImpls.set(toolId, {
execute: async (params, context) => {
try {
// Direct tools from AI SDK expect their specific parameter structure
// Each tool validates its own parameters, so we safely pass them through
const result = await toolDef.execute(params, {
toolCallId: context?.sessionId || "unknown",
messages: [],
});
// Return the result wrapped in our standard format
return {
success: true,
data: result,
metadata: {
toolName,
serverId: "direct",
executionTime: 0,
},
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
metadata: {
toolName,
serverId: "direct",
executionTime: 0,
},
};
}
},
description: toolDef.description,
inputSchema: {},
});
registryLogger.debug(`Registered direct tool: ${toolName} as ${toolId}`);
}
registryLogger.info(`Auto-registered ${Object.keys(directAgentTools).length} direct tools`);
}
/**
* Register a server with its tools (updated signature)
*/
async registerServer(serverOrId, serverConfig, context) {
let serverId;
let plugin;
if (typeof serverOrId === "string") {
// Original behavior: register by ID and config
serverId = serverOrId;
registryLogger.info(`Registering server by ID: ${serverId}`);
plugin = {
metadata: {
name: serverId,
description: typeof serverConfig === "object" && serverConfig
? serverConfig.description ||
"No description"
: "No description",
},
tools: typeof serverConfig === "object" && serverConfig
? serverConfig.tools
: {},
configuration: typeof serverConfig === "object" && serverConfig
? serverConfig
: {},
};
}
else {
// New behavior: register server object
const server = serverOrId;
serverId = String(server.id || server.serverId || "unknown-server");
registryLogger.info(`Registering server object: ${serverId}`);
plugin = {
metadata: {
name: serverId,
description: String(server.description || server.title || "No description"),
category: String(server.category || ""),
},
tools: server.tools || {},
configuration: server.configuration ||
{},
};
}
// Call the parent register method
this.register(plugin);
// Extract tools from server info if available
const tools = plugin.tools || {};
registryLogger.debug(`Registering ${Object.keys(tools).length} tools for server ${serverId}:`, Object.keys(tools));
for (const [toolName, toolDef] of Object.entries(tools)) {
const toolId = `${serverId}.${toolName}`;
const toolInfo = {
name: toolName,
description: toolDef?.description,
inputSchema: toolDef?.inputSchema,
outputSchema: toolDef?.outputSchema,
serverId,
category: toolDef?.category || "general",
permissions: toolDef?.permissions || [],
};
// Register only with fully-qualified toolId to avoid collisions
this.tools.set(toolId, toolInfo);
// Store the actual tool implementation for execution using toolId as key
this.toolImpls.set(toolId, toolDef);
registryLogger.debug(`Registered tool '${toolName}' with execute function:`, typeof toolDef?.execute);
}
}
/**
* Execute a tool with enhanced context
*/
async executeTool(toolName, args, context) {
const startTime = Date.now();
try {
registryLogger.info(`Executing tool: ${toolName}`);
// Try to find the tool by fully-qualified name first
let tool = this.tools.get(toolName);
// If not found, search for tool by name across all entries (for backward compatibility)
let toolId = toolName;
if (!tool) {
for (const [candidateToolId, toolInfo] of this.tools.entries()) {
if (toolInfo.name === toolName) {
tool = toolInfo;
toolId = candidateToolId;
break;
}
}
}
if (!tool) {
throw new Error(`Tool '${toolName}' not found in registry`);
}
// Create execution context if not provided
const execContext = {
sessionId: context?.sessionId || randomUUID(),
userId: context?.userId,
...context,
};
// Get the tool implementation using the resolved toolId
const toolImpl = this.toolImpls.get(toolId);
registryLogger.debug(`Looking for tool '${toolName}' (toolId: '${toolId}'), found: ${!!toolImpl}, type: ${typeof toolImpl?.execute}`);
registryLogger.debug(`Available tools:`, Array.from(this.toolImpls.keys()));
if (!toolImpl || typeof toolImpl?.execute !== "function") {
throw new Error(`Tool '${toolName}' implementation not found or not executable`);
}
// Execute the actual tool
registryLogger.debug(`Executing tool '${toolName}' with args:`, args);
const toolResult = await toolImpl.execute(args, execContext);
// Add metadata to the tool result (don't double-wrap)
const toolResultObj = toolResult;
const result = {
...toolResultObj,
usage: {
...(toolResultObj.usage || {}),
executionTime: Date.now() - startTime,
},
metadata: {
...(toolResultObj.metadata || {}),
toolName,
serverId: tool.serverId,
sessionId: execContext.sessionId,
executionTime: Date.now() - startTime,
},
};
// Update statistics
const duration = Date.now() - startTime;
this.updateStats(toolName, duration);
registryLogger.debug(`Tool '${toolName}' executed successfully in ${duration}ms`);
return result;
}
catch (error) {
registryLogger.error(`Tool execution failed: ${toolName}`, error);
// Return error in ToolResult format
const errorResult = {
success: false,
data: null,
error: error instanceof Error ? error.message : String(error),
usage: {
executionTime: Date.now() - startTime,
},
metadata: {
toolName,
sessionId: context?.sessionId,
},
};
return errorResult;
}
}
async listTools(filterOrContext) {
// FIXED: Return unique tools (avoid duplicates from dual registration)
const uniqueTools = new Map();
for (const tool of this.tools.values()) {
const key = `${tool.serverId || "unknown"}.${tool.name}`;
if (!uniqueTools.has(key)) {
uniqueTools.set(key, tool);
}
}
let result = Array.from(uniqueTools.values());
// Determine if parameter is a filter object or just context
let filter;
if (filterOrContext) {
// Check if it's a filter object (has filter-specific properties) or just context
if ("sessionId" in filterOrContext || "userId" in filterOrContext) {
// It's an ExecutionContext, treat as no filter
filter = undefined;
}
else {
// It's a filter object
filter = filterOrContext;
}
}
// Apply filters if provided
if (filter) {
if (filter.category) {
result = result.filter((tool) => tool.category === filter.category);
}
if (filter.serverId) {
result = result.filter((tool) => tool.serverId === filter.serverId);
}
if (filter.serverCategory) {
result = result.filter((tool) => {
const server = this.get(tool.serverId || "");
return server?.metadata?.category === filter.serverCategory;
});
}
if (filter.permissions && filter.permissions.length > 0) {
result = result.filter((tool) => {
const toolPermissions = tool.permissions || [];
return filter.permissions.some((perm) => toolPermissions.includes(perm));
});
}
}
registryLogger.debug(`Listed ${result.length} unique tools (${filter ? "filtered" : "unfiltered"})`);
return result;
}
/**
* Get tool information with server details
*/
getToolInfo(toolName) {
// Try to find the tool by fully-qualified name first
let tool = this.tools.get(toolName);
// If not found, search for tool by name across all entries (for backward compatibility)
if (!tool) {
for (const toolInfo of this.tools.values()) {
if (toolInfo.name === toolName) {
tool = toolInfo;
break;
}
}
}
if (!tool) {
return undefined;
}
return {
tool,
server: {
id: tool.serverId || "unknown-server",
},
};
}
/**
* Update execution statistics
*/
updateStats(toolName, executionTime) {
const stats = this.toolExecutionStats.get(toolName) || {
count: 0,
totalTime: 0,
};
stats.count += 1;
stats.totalTime += executionTime;
this.toolExecutionStats.set(toolName, stats);
}
/**
* Get execution statistics
*/
getExecutionStats() {
const result = {};
for (const [toolName, stats] of this.toolExecutionStats.entries()) {
result[toolName] = {
count: stats.count,
totalTime: stats.totalTime,
averageTime: stats.totalTime / stats.count,
};
}
return result;
}
/**
* Clear execution statistics
*/
clearStats() {
this.toolExecutionStats.clear();
}
/**
* Get tools by category
*/
getToolsByCategory(category) {
// Return unique tools by fully-qualified toolId
const uniqueTools = new Map();
for (const [toolId, tool] of this.tools.entries()) {
if (tool.category === category && !uniqueTools.has(toolId)) {
uniqueTools.set(toolId, tool);
}
}
return Array.from(uniqueTools.values());
}
/**
* Check if tool exists
*/
hasTool(toolName) {
// Check by fully-qualified name first, then fallback to any matching tool name
if (this.tools.has(toolName)) {
return true;
}
for (const tool of this.tools.values()) {
if (tool.name === toolName) {
return true;
}
}
return false;
}
/**
* Remove a tool
*/
removeTool(toolName) {
// Remove by fully-qualified name first, then fallback to any matching tool name
let removed = false;
if (this.tools.has(toolName)) {
this.tools.delete(toolName);
this.toolExecutionStats.delete(toolName);
registryLogger.info(`Removed tool: ${toolName}`);
removed = true;
}
else {
// Remove all tools with matching name
for (const [toolId, tool] of Array.from(this.tools.entries())) {
if (tool.name === toolName) {
this.tools.delete(toolId);
this.toolExecutionStats.delete(toolId);
registryLogger.info(`Removed tool: ${toolId}`);
removed = true;
}
}
}
return removed;
}
/**
* Get tool count
*/
getToolCount() {
return this.tools.size;
}
/**
* Get comprehensive statistics
*/
getStats() {
const servers = this.list(); // Get all registered servers
const allTools = Array.from(this.tools.values());
// Count servers by category
const serversByCategory = {};
for (const server of servers) {
const category = server.metadata?.category || "uncategorized";
serversByCategory[category] = (serversByCategory[category] || 0) + 1;
}
// Count tools by category
const toolsByCategory = {};
for (const tool of allTools) {
const category = tool.category || "uncategorized";
toolsByCategory[category] = (toolsByCategory[category] || 0) + 1;
}
return {
totalServers: servers.length,
totalTools: allTools.length,
serversByCategory,
toolsByCategory,
executionStats: this.getExecutionStats(),
};
}
/**
* Unregister a server
*/
unregisterServer(serverId) {
// Remove all tools for this server
const removedTools = [];
for (const [toolId, tool] of this.tools.entries()) {
if (tool.serverId === serverId) {
this.tools.delete(toolId);
removedTools.push(toolId);
}
}
// Remove from parent registry
const removed = this.unregister(serverId);
registryLogger.info(`Unregistered server ${serverId}, removed ${removedTools.length} tools`);
return removed;
}
}
// Create default instance
export const toolRegistry = new MCPToolRegistry();
export const defaultToolRegistry = toolRegistry;