Skip to main content
Glama

hypertool-mcp

toolset-bridge.tsโ€ข12.5 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 type { IToolDiscoveryEngine } from "../discovery/types.js"; import type { ToolsetConfig, DynamicToolReference, } from "../server/tools/toolset/types.js"; import type { PersonaToolset } from "./types.js"; import { createChildLogger } from "../utils/logging.js"; const logger = createChildLogger({ module: "persona-toolset-bridge" }); /** * Result of persona toolset conversion */ export interface ToolsetConversionResult { /** Whether the conversion was successful */ success: boolean; /** Converted toolset configuration if successful */ toolsetConfig?: ToolsetConfig; /** Error message if conversion failed */ error?: string; /** Validation warnings (non-blocking) */ warnings?: string[]; /** Tool resolution statistics */ stats?: { /** Total tools in persona toolset */ totalTools: number; /** Successfully resolved tools */ resolvedTools: number; /** Failed tool resolutions */ failedTools: number; /** Tools that couldn't be resolved */ unresolvableTools: string[]; }; } /** * Bridge configuration options */ export interface BridgeOptions { /** Whether to validate tool resolution during conversion */ validateTools?: boolean; /** Whether to allow partial toolset creation even if some tools fail */ allowPartialToolsets?: boolean; /** Custom toolset name prefix for converted persona toolsets */ namePrefix?: string; /** Whether to include detailed metadata in the converted toolset */ includeMetadata?: boolean; } /** * 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 { private readonly getToolDiscoveryEngine?: () => | IToolDiscoveryEngine | undefined; private readonly options: Required<BridgeOptions>; constructor( getToolDiscoveryEngine?: () => IToolDiscoveryEngine | undefined, options: BridgeOptions = {} ) { 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: PersonaToolset, personaName: string ): Promise<ToolsetConversionResult> { try { logger.debug("Converting persona toolset", { personaName, toolsetName: personaToolset.name, toolCount: personaToolset.toolIds.length, }); // Convert tool IDs to DynamicToolReference format const toolReferences: DynamicToolReference[] = personaToolset.toolIds.map( (toolId) => ({ namespacedName: toolId, }) ); // Initialize statistics const stats = { totalTools: personaToolset.toolIds.length, resolvedTools: 0, failedTools: 0, unresolvableTools: [] as string[], }; const warnings: string[] = []; // 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: 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: PersonaToolset[], personaName: string ): Promise<{ success: boolean; toolsetConfigs: ToolsetConfig[]; errors: Array<{ toolsetName: string; error: string }>; warnings: string[]; }> { const toolsetConfigs: ToolsetConfig[] = []; const errors: Array<{ toolsetName: string; error: string }> = []; const allWarnings: string[] = []; 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 */ private generateToolsetName( personaName: string, toolsetName: string ): string { // 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 */ private generateToolsetDescription( personaName: string, personaToolset: PersonaToolset ): string { 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 */ private async validateToolReferences( toolReferences: DynamicToolReference[], stats: ToolsetConversionResult["stats"], warnings: string[] ): Promise<void> { 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(): { hasDiscoveryEngine: boolean; options: Required<BridgeOptions>; version: string; } { return { hasDiscoveryEngine: !!this.getToolDiscoveryEngine?.(), options: this.options, version: "1.0.0", }; } /** * Test tool resolution capabilities */ async testToolResolution(): Promise<{ discoveryEngineAvailable: boolean; totalTools: number; connectedServers: number; }> { 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?: IToolDiscoveryEngine, options?: BridgeOptions ): PersonaToolsetBridge { return new PersonaToolsetBridge(() => toolDiscoveryEngine, options); } /** * Utility function to convert a single persona toolset */ export async function convertPersonaToolset( personaToolset: PersonaToolset, personaName: string, toolDiscoveryEngine?: IToolDiscoveryEngine, options?: BridgeOptions ): Promise<ToolsetConversionResult> { const bridge = new PersonaToolsetBridge(() => toolDiscoveryEngine, options); return bridge.convertPersonaToolset(personaToolset, personaName); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toolprint/hypertool-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server