UNPKG

@toolprint/hypertool-mcp

Version:

HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers

273 lines • 11.2 kB
/** * PersonaToolsetBridge - Bridge between persona toolsets and existing ToolsetManager * * This bridge converts persona toolset definitions to the existing toolset system format, * enabling seamless integration between the persona content pack system and the * established toolset infrastructure without breaking existing functionality. * * @fileoverview Bridge implementation for persona-toolset integration */ import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "persona-toolset-bridge" }); /** * PersonaToolsetBridge - Converts persona toolsets to ToolsetConfig format * * This bridge enables the persona system to work seamlessly with the existing * toolset infrastructure by converting PersonaToolset definitions into the * ToolsetConfig format expected by the ToolsetManager. */ export class PersonaToolsetBridge { getToolDiscoveryEngine; options; constructor(getToolDiscoveryEngine, options = {}) { this.getToolDiscoveryEngine = getToolDiscoveryEngine; // Apply default options this.options = { validateTools: options.validateTools ?? true, allowPartialToolsets: options.allowPartialToolsets ?? false, namePrefix: options.namePrefix ?? "persona", includeMetadata: options.includeMetadata ?? true, ...options, }; logger.debug("PersonaToolsetBridge initialized", { hasDiscoveryEngine: !!this.getToolDiscoveryEngine?.(), options: this.options, }); } /** * Convert a PersonaToolset to ToolsetConfig format */ async convertPersonaToolset(personaToolset, personaName) { try { logger.debug("Converting persona toolset", { personaName, toolsetName: personaToolset.name, toolCount: personaToolset.toolIds.length, }); // Convert tool IDs to DynamicToolReference format const toolReferences = personaToolset.toolIds.map((toolId) => ({ namespacedName: toolId, })); // Initialize statistics const stats = { totalTools: personaToolset.toolIds.length, resolvedTools: 0, failedTools: 0, unresolvableTools: [], }; const warnings = []; // Validate tool resolution if enabled and discovery engine is available if (this.options.validateTools && this.getToolDiscoveryEngine?.()) { const validationResult = await this.validateToolReferences(toolReferences, stats, warnings); // Check if we should fail on unresolvable tools if (!this.options.allowPartialToolsets && stats.failedTools > 0) { return { success: false, error: `Failed to resolve ${stats.failedTools} tools: ${stats.unresolvableTools.join(", ")}. Enable 'allowPartialToolsets' to create toolset with available tools only.`, stats, }; } // Filter out unresolvable tools if partial toolsets are allowed if (this.options.allowPartialToolsets && stats.failedTools > 0) { const validToolIds = new Set(personaToolset.toolIds.filter((toolId) => !stats.unresolvableTools.includes(toolId))); toolReferences.splice(0, toolReferences.length); toolReferences.push(...Array.from(validToolIds).map((toolId) => ({ namespacedName: toolId, }))); warnings.push(`${stats.failedTools} tools could not be resolved and were excluded from the toolset`); } } // Generate toolset name const toolsetName = this.generateToolsetName(personaName, personaToolset.name); // Create ToolsetConfig const toolsetConfig = { name: toolsetName, description: this.generateToolsetDescription(personaName, personaToolset), version: "1.0.0", createdAt: new Date(), tools: toolReferences, }; logger.debug("Persona toolset conversion completed", { personaName, originalToolsetName: personaToolset.name, convertedToolsetName: toolsetName, totalTools: stats.totalTools, resolvedTools: stats.resolvedTools, failedTools: stats.failedTools, }); return { success: true, toolsetConfig, warnings: warnings.length > 0 ? warnings : undefined, stats, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error("Failed to convert persona toolset", { personaName, toolsetName: personaToolset.name, error: errorMessage, }); return { success: false, error: `Failed to convert persona toolset: ${errorMessage}`, }; } } /** * Convert multiple persona toolsets to ToolsetConfig format */ async convertMultiplePersonaToolsets(personaToolsets, personaName) { const toolsetConfigs = []; const errors = []; const allWarnings = []; for (const personaToolset of personaToolsets) { const result = await this.convertPersonaToolset(personaToolset, personaName); if (result.success && result.toolsetConfig) { toolsetConfigs.push(result.toolsetConfig); if (result.warnings) { allWarnings.push(...result.warnings.map((w) => `${personaToolset.name}: ${w}`)); } } else { errors.push({ toolsetName: personaToolset.name, error: result.error || "Unknown conversion error", }); } } return { success: errors.length === 0, toolsetConfigs, errors, warnings: allWarnings, }; } /** * Generate a unique toolset name from persona and toolset names */ generateToolsetName(personaName, toolsetName) { // Convert to lowercase and replace invalid characters const cleanPersonaName = personaName .toLowerCase() .replace(/[^a-z0-9-]/g, "-"); const cleanToolsetName = toolsetName .toLowerCase() .replace(/[^a-z0-9-]/g, "-"); // Combine with prefix return `${this.options.namePrefix}-${cleanPersonaName}-${cleanToolsetName}`; } /** * Generate a descriptive toolset description */ generateToolsetDescription(personaName, personaToolset) { const baseDescription = `Toolset "${personaToolset.name}" from persona "${personaName}"`; if (!this.options.includeMetadata) { return baseDescription; } const toolCount = personaToolset.toolIds.length; const toolCountText = `Contains ${toolCount} tool${toolCount === 1 ? "" : "s"}`; const timestamp = new Date().toISOString(); return `${baseDescription}. ${toolCountText}. Generated on ${timestamp}.`; } /** * Validate tool references against the discovery engine */ async validateToolReferences(toolReferences, stats, warnings) { const toolDiscoveryEngine = this.getToolDiscoveryEngine?.(); if (!toolDiscoveryEngine || !stats) { return; } for (const toolRef of toolReferences) { try { const resolution = toolDiscoveryEngine.resolveToolReference(toolRef, { allowStaleRefs: false, }); if (resolution && resolution.exists) { stats.resolvedTools++; // Log any warnings from resolution if (resolution.warnings.length > 0) { warnings.push(`Tool "${toolRef.namespacedName}": ${resolution.warnings.join(", ")}`); } } else { stats.failedTools++; if (toolRef.namespacedName) { stats.unresolvableTools.push(toolRef.namespacedName); } // Log resolution errors if (resolution && resolution.errors.length > 0) { logger.debug("Tool resolution failed", { toolRef, errors: resolution.errors, }); } } } catch (error) { stats.failedTools++; if (toolRef.namespacedName) { stats.unresolvableTools.push(toolRef.namespacedName); } logger.debug("Tool resolution threw error", { toolRef, error: error instanceof Error ? error.message : String(error), }); } } } /** * Get bridge statistics and configuration */ getConfiguration() { return { hasDiscoveryEngine: !!this.getToolDiscoveryEngine?.(), options: this.options, version: "1.0.0", }; } /** * Test tool resolution capabilities */ async testToolResolution() { const toolDiscoveryEngine = this.getToolDiscoveryEngine?.(); if (!toolDiscoveryEngine) { return { discoveryEngineAvailable: false, totalTools: 0, connectedServers: 0, }; } try { const stats = toolDiscoveryEngine.getStats(); return { discoveryEngineAvailable: true, totalTools: stats.totalTools, connectedServers: stats.connectedServers, }; } catch (error) { logger.warn("Failed to get discovery engine stats", { error }); return { discoveryEngineAvailable: false, totalTools: 0, connectedServers: 0, }; } } } /** * Create a PersonaToolsetBridge instance with default configuration */ export function createPersonaToolsetBridge(toolDiscoveryEngine, options) { return new PersonaToolsetBridge(() => toolDiscoveryEngine, options); } /** * Utility function to convert a single persona toolset */ export async function convertPersonaToolset(personaToolset, personaName, toolDiscoveryEngine, options) { const bridge = new PersonaToolsetBridge(() => toolDiscoveryEngine, options); return bridge.convertPersonaToolset(personaToolset, personaName); } //# sourceMappingURL=toolset-bridge.js.map