claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
434 lines • 15.5 kB
JavaScript
/**
* Enhanced Tool registry for MCP with capability negotiation and discovery
*/
import { MCPError } from '../utils/errors.js';
import { EventEmitter } from 'node:events';
/**
* Enhanced Tool registry implementation with capability negotiation
*/
export class ToolRegistry extends EventEmitter {
logger;
tools = new Map();
capabilities = new Map();
metrics = new Map();
categories = new Set();
tags = new Set();
constructor(logger) {
super();
this.logger = logger;
}
/**
* Registers a new tool with enhanced capability information
*/
register(tool, capability) {
if (this.tools.has(tool.name)) {
throw new MCPError(`Tool already registered: ${tool.name}`);
}
// Validate tool schema
this.validateTool(tool);
// Register tool
this.tools.set(tool.name, tool);
// Register capability if provided
if (capability) {
this.registerCapability(tool.name, capability);
}
else {
// Create default capability
const defaultCapability = {
name: tool.name,
version: '1.0.0',
description: tool.description,
category: this.extractCategory(tool.name),
tags: this.extractTags(tool),
supportedProtocolVersions: [{ major: 2024, minor: 11, patch: 5 }],
};
this.registerCapability(tool.name, defaultCapability);
}
// Initialize metrics
this.metrics.set(tool.name, {
name: tool.name,
totalInvocations: 0,
successfulInvocations: 0,
failedInvocations: 0,
averageExecutionTime: 0,
totalExecutionTime: 0,
});
this.logger.debug('Tool registered', { name: tool.name });
this.emit('toolRegistered', { name: tool.name, capability });
}
/**
* Unregisters a tool
*/
unregister(name) {
if (!this.tools.has(name)) {
throw new MCPError(`Tool not found: ${name}`);
}
this.tools.delete(name);
this.logger.debug('Tool unregistered', { name });
}
/**
* Gets a tool by name
*/
getTool(name) {
return this.tools.get(name);
}
/**
* Lists all registered tools
*/
listTools() {
return Array.from(this.tools.values()).map((tool) => ({
name: tool.name,
description: tool.description,
}));
}
/**
* Gets the number of registered tools
*/
getToolCount() {
return this.tools.size;
}
/**
* Executes a tool with metrics tracking
*/
async executeTool(name, input, context) {
const tool = this.tools.get(name);
if (!tool) {
throw new MCPError(`Tool not found: ${name}`);
}
const startTime = Date.now();
const metrics = this.metrics.get(name);
this.logger.debug('Executing tool', { name, input });
try {
// Validate input against schema
this.validateInput(tool, input);
// Check tool capabilities and permissions
await this.checkToolCapabilities(name, context);
// Execute tool handler
const result = await tool.handler(input, context);
// Update success metrics
if (metrics) {
const executionTime = Date.now() - startTime;
metrics.totalInvocations++;
metrics.successfulInvocations++;
metrics.totalExecutionTime += executionTime;
metrics.averageExecutionTime = metrics.totalExecutionTime / metrics.totalInvocations;
metrics.lastInvoked = new Date();
}
this.logger.debug('Tool executed successfully', {
name,
executionTime: Date.now() - startTime,
});
this.emit('toolExecuted', { name, success: true, executionTime: Date.now() - startTime });
return result;
}
catch (error) {
// Update failure metrics
if (metrics) {
const executionTime = Date.now() - startTime;
metrics.totalInvocations++;
metrics.failedInvocations++;
metrics.totalExecutionTime += executionTime;
metrics.averageExecutionTime = metrics.totalExecutionTime / metrics.totalInvocations;
metrics.lastInvoked = new Date();
}
this.logger.error('Tool execution failed', {
name,
error,
executionTime: Date.now() - startTime,
});
this.emit('toolExecuted', {
name,
success: false,
error,
executionTime: Date.now() - startTime,
});
throw error;
}
}
/**
* Validates tool definition
*/
validateTool(tool) {
if (!tool.name || typeof tool.name !== 'string') {
throw new MCPError('Tool name must be a non-empty string');
}
if (!tool.description || typeof tool.description !== 'string') {
throw new MCPError('Tool description must be a non-empty string');
}
if (typeof tool.handler !== 'function') {
throw new MCPError('Tool handler must be a function');
}
if (!tool.inputSchema || typeof tool.inputSchema !== 'object') {
throw new MCPError('Tool inputSchema must be an object');
}
// Validate tool name format (namespace/name)
if (!tool.name.includes('/')) {
throw new MCPError('Tool name must be in format: namespace/name');
}
}
/**
* Validates input against tool schema
*/
validateInput(tool, input) {
// Simple validation - in production, use a JSON Schema validator
const schema = tool.inputSchema;
if (schema.type === 'object' && schema.properties) {
if (typeof input !== 'object' || input === null) {
throw new MCPError('Input must be an object');
}
const inputObj = input;
// Check required properties
if (schema.required && Array.isArray(schema.required)) {
for (const prop of schema.required) {
if (!(prop in inputObj)) {
throw new MCPError(`Missing required property: ${prop}`);
}
}
}
// Check property types
for (const [prop, propSchema] of Object.entries(schema.properties)) {
if (prop in inputObj) {
const value = inputObj[prop];
const expectedType = propSchema.type;
if (expectedType && !this.checkType(value, expectedType)) {
throw new MCPError(`Invalid type for property ${prop}: expected ${expectedType}`);
}
}
}
}
}
/**
* Checks if a value matches a JSON Schema type
*/
checkType(value, type) {
switch (type) {
case 'string':
return typeof value === 'string';
case 'number':
return typeof value === 'number';
case 'boolean':
return typeof value === 'boolean';
case 'object':
return typeof value === 'object' && value !== null && !Array.isArray(value);
case 'array':
return Array.isArray(value);
case 'null':
return value === null;
default:
return true;
}
}
/**
* Register tool capability information
*/
registerCapability(toolName, capability) {
this.capabilities.set(toolName, capability);
this.categories.add(capability.category);
capability.tags.forEach((tag) => this.tags.add(tag));
}
/**
* Extract category from tool name
*/
extractCategory(toolName) {
const parts = toolName.split('/');
return parts.length > 1 ? parts[0] : 'general';
}
/**
* Extract tags from tool definition
*/
extractTags(tool) {
const tags = [];
// Extract from description
if (tool.description.toLowerCase().includes('file'))
tags.push('filesystem');
if (tool.description.toLowerCase().includes('search'))
tags.push('search');
if (tool.description.toLowerCase().includes('memory'))
tags.push('memory');
if (tool.description.toLowerCase().includes('swarm'))
tags.push('swarm');
if (tool.description.toLowerCase().includes('task'))
tags.push('orchestration');
return tags.length > 0 ? tags : ['general'];
}
/**
* Check tool capabilities and permissions
*/
async checkToolCapabilities(toolName, context) {
const capability = this.capabilities.get(toolName);
if (!capability) {
return; // No capability checks needed
}
// Check if tool is deprecated
if (capability.deprecated) {
this.logger.warn('Using deprecated tool', {
name: toolName,
message: capability.deprecationMessage,
});
}
// Check required permissions
if (capability.requiredPermissions && context?.permissions) {
const hasAllPermissions = capability.requiredPermissions.every((permission) => context.permissions.includes(permission));
if (!hasAllPermissions) {
throw new MCPError(`Insufficient permissions for tool ${toolName}. Required: ${capability.requiredPermissions.join(', ')}`);
}
}
// Check protocol version compatibility
if (context?.protocolVersion) {
const isCompatible = capability.supportedProtocolVersions.some((version) => this.isProtocolVersionCompatible(context.protocolVersion, version));
if (!isCompatible) {
throw new MCPError(`Tool ${toolName} is not compatible with protocol version ${context.protocolVersion.major}.${context.protocolVersion.minor}.${context.protocolVersion.patch}`);
}
}
}
/**
* Check protocol version compatibility
*/
isProtocolVersionCompatible(client, supported) {
if (client.major !== supported.major) {
return false;
}
if (client.minor > supported.minor) {
return false;
}
return true;
}
/**
* Discover tools based on query criteria
*/
discoverTools(query = {}) {
const results = [];
for (const [name, tool] of this.tools) {
const capability = this.capabilities.get(name);
if (!capability)
continue;
// Filter by category
if (query.category && capability.category !== query.category) {
continue;
}
// Filter by tags
if (query.tags && !query.tags.some((tag) => capability.tags.includes(tag))) {
continue;
}
// Filter by capabilities
if (query.capabilities && !query.capabilities.every((cap) => capability.tags.includes(cap))) {
continue;
}
// Filter by protocol version
if (query.protocolVersion) {
const isCompatible = capability.supportedProtocolVersions.some((version) => this.isProtocolVersionCompatible(query.protocolVersion, version));
if (!isCompatible)
continue;
}
// Filter deprecated tools
if (!query.includeDeprecated && capability.deprecated) {
continue;
}
// Filter by permissions
if (query.permissions && capability.requiredPermissions) {
const hasAllPermissions = capability.requiredPermissions.every((permission) => query.permissions.includes(permission));
if (!hasAllPermissions)
continue;
}
results.push({ tool, capability });
}
return results;
}
/**
* Get tool capability information
*/
getToolCapability(name) {
return this.capabilities.get(name);
}
/**
* Get tool metrics
*/
getToolMetrics(name) {
if (name) {
const metrics = this.metrics.get(name);
if (!metrics) {
throw new MCPError(`Metrics not found for tool: ${name}`);
}
return metrics;
}
return Array.from(this.metrics.values());
}
/**
* Get all available categories
*/
getCategories() {
return Array.from(this.categories);
}
/**
* Get all available tags
*/
getTags() {
return Array.from(this.tags);
}
/**
* Reset metrics for a tool or all tools
*/
resetMetrics(toolName) {
if (toolName) {
const metrics = this.metrics.get(toolName);
if (metrics) {
Object.assign(metrics, {
totalInvocations: 0,
successfulInvocations: 0,
failedInvocations: 0,
averageExecutionTime: 0,
totalExecutionTime: 0,
lastInvoked: undefined,
});
}
}
else {
for (const metrics of this.metrics.values()) {
Object.assign(metrics, {
totalInvocations: 0,
successfulInvocations: 0,
failedInvocations: 0,
averageExecutionTime: 0,
totalExecutionTime: 0,
lastInvoked: undefined,
});
}
}
this.emit('metricsReset', { toolName });
}
/**
* Get comprehensive registry statistics
*/
getRegistryStats() {
const stats = {
totalTools: this.tools.size,
toolsByCategory: {},
toolsByTag: {},
totalInvocations: 0,
successRate: 0,
averageExecutionTime: 0,
};
// Count by category
for (const capability of this.capabilities.values()) {
stats.toolsByCategory[capability.category] =
(stats.toolsByCategory[capability.category] || 0) + 1;
for (const tag of capability.tags) {
stats.toolsByTag[tag] = (stats.toolsByTag[tag] || 0) + 1;
}
}
// Calculate execution stats
let totalExecutionTime = 0;
let totalSuccessful = 0;
for (const metrics of this.metrics.values()) {
stats.totalInvocations += metrics.totalInvocations;
totalSuccessful += metrics.successfulInvocations;
totalExecutionTime += metrics.totalExecutionTime;
}
if (stats.totalInvocations > 0) {
stats.successRate = (totalSuccessful / stats.totalInvocations) * 100;
stats.averageExecutionTime = totalExecutionTime / stats.totalInvocations;
}
return stats;
}
}
//# sourceMappingURL=tools.js.map