@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
273 lines • 11.2 kB
JavaScript
/**
* 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