UNPKG

@toolprint/hypertool-mcp

Version:

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

413 lines â€ĸ 14.9 kB
/** * Toolsets menu implementation for interactive navigation */ import inquirer from "inquirer"; import { execSync } from "child_process"; import { theme } from "../../../utils/theme.js"; import { output } from "../../../utils/output.js"; import { ViewType } from "./types.js"; /** * Format toolset choice for inquirer list */ function formatToolsetChoice(toolset) { let name = `🧰 ${theme.primary(toolset.name)}`; // Add auto-generated indicator if (toolset.autoGenerated) { name += ` ${theme.warning("[auto]")}`; } // Add tool count name += ` ${theme.muted(`(${toolset.toolCount} tool${toolset.toolCount !== 1 ? "s" : ""})`)}`; // Add description or apps using it if (toolset.description) { const shortDesc = toolset.description.length > 40 ? toolset.description.substring(0, 40) + "..." : toolset.description; name += `\n ${theme.muted(shortDesc)}`; } else if (toolset.apps && toolset.apps.length > 0) { name += `\n ${theme.muted(`Used by: ${toolset.apps.join(", ")}`)}`; } return { name, value: toolset, short: toolset.name, }; } /** * Display the toolsets list and handle selection */ export async function showToolsetsList(toolsets, options) { // Clear screen and show header console.clear(); output.displayHeader(`🧰 Toolsets (${toolsets.length} total)`); output.displaySpaceBuffer(1); // Show summary const inUse = toolsets.filter((ts) => ts.apps && ts.apps.length > 0).length; const autoGenerated = toolsets.filter((ts) => ts.autoGenerated).length; output.info(`Total: ${theme.value(toolsets.length.toString())} | In Use: ${theme.value(inUse.toString())} | Auto-generated: ${theme.value(autoGenerated.toString())}`); output.displaySpaceBuffer(1); if (toolsets.length === 0) { output.warn("No toolsets configured."); output.info("Toolsets help organize MCP tools by application context."); output.displaySpaceBuffer(2); const { action } = await inquirer.prompt([ { type: "list", name: "action", message: "What would you like to do?", choices: [ { name: "[Back]", value: "back", }, ], }, ]); return { action }; } // Create choices const choices = []; // Sort toolsets: manual first, then auto-generated const manualToolsets = toolsets.filter((ts) => !ts.autoGenerated); const autoToolsets = toolsets.filter((ts) => ts.autoGenerated); if (manualToolsets.length > 0) { choices.push(new inquirer.Separator("── Manual Toolsets ──")); for (const toolset of manualToolsets) { choices.push(formatToolsetChoice(toolset)); } } if (autoToolsets.length > 0) { choices.push(new inquirer.Separator("── Auto-generated Toolsets ──")); for (const toolset of autoToolsets) { choices.push(formatToolsetChoice(toolset)); } } choices.push(new inquirer.Separator(), { name: "[Back]", value: { action: "back" }, }); // Show menu const { selection } = await inquirer.prompt([ { type: "list", name: "selection", message: "Select a toolset for details:", choices, pageSize: Math.min(30, toolsets.length + 8), // Increased page size }, ]); // Handle selection if (selection.action === "back") { return { action: "back" }; } else { // Toolset selected return { action: "navigate", nextView: ViewType.TOOLSET_DETAIL, data: selection, itemName: selection.name, }; } } /** * Copy text to clipboard (cross-platform) */ function copyToClipboard(text) { try { const platform = process.platform; if (platform === "darwin") { execSync("pbcopy", { input: text }); } else if (platform === "win32") { execSync("clip", { input: text }); } else { // Linux try { execSync("xclip -selection clipboard", { input: text }); } catch { // Try xsel if xclip is not available execSync("xsel --clipboard --input", { input: text }); } } return true; } catch { return false; } } /** * Display toolset detail view with enhanced tool listing */ export async function showToolsetDetail(toolset, _allToolsets) { // Clear screen and show header console.clear(); output.displayHeader(`Toolset: ${toolset.name}`); output.displaySpaceBuffer(1); // Display toolset details if (toolset.description) { output.info(`Description: ${theme.muted(toolset.description)}`); } output.info(`Tools: ${theme.value(toolset.toolCount.toString())}`); if (toolset.autoGenerated) { output.info(`Type: ${theme.warning("Auto-generated")}`); } else { output.info(`Type: ${theme.success("Manual")}`); } output.displaySpaceBuffer(1); // Display tool details if available if (toolset.serverGroups && Object.keys(toolset.serverGroups).length > 0) { output.displaySubHeader("🔧 Tool Details:"); output.displaySpaceBuffer(1); const serverEntries = Object.entries(toolset.serverGroups); serverEntries.forEach(([serverName, tools], serverIndex) => { const isLastServer = serverIndex === serverEntries.length - 1; const serverPrefix = isLastServer ? "└──" : "├──"; const toolPrefix = isLastServer ? " " : "│ "; output.info(`${serverPrefix} ${theme.primary(serverName.charAt(0).toUpperCase() + serverName.slice(1))} Operations (${tools.length} tool${tools.length !== 1 ? "s" : ""}) - from '${theme.warning(serverName)}' server`); tools.forEach((tool, toolIndex) => { const isLastTool = toolIndex === tools.length - 1; const currentToolPrefix = isLastTool ? "└──" : "├──"; let toolDisplay = `${toolPrefix}${currentToolPrefix} ${theme.value(tool.toolName)}`; if (tool.description) { toolDisplay += ` - ${theme.muted(tool.description)}`; } output.info(toolDisplay); }); if (!isLastServer) { output.info("│"); } }); } else if (toolset.toolDetails && toolset.toolDetails.length > 0) { // Fallback display if serverGroups not available output.displaySubHeader("🔧 Tools:"); output.displaySpaceBuffer(1); toolset.toolDetails.forEach((tool, index) => { const isLast = index === toolset.toolDetails.length - 1; const prefix = isLast ? "└──" : "├──"; let toolDisplay = `${prefix} ${theme.value(tool.toolName)} (${theme.warning(tool.serverName)})`; if (tool.description) { toolDisplay += ` - ${theme.muted(tool.description)}`; } output.info(toolDisplay); }); } // Display applications using this toolset if (toolset.apps && toolset.apps.length > 0) { output.displaySpaceBuffer(1); output.displaySubHeader("📱 Used by applications:"); for (const app of toolset.apps) { output.info(` â€ĸ ${theme.primary(app)}`); } } output.displaySpaceBuffer(2); // Show helpful information output.displayHelpContext("â„šī¸ Toolsets organize MCP tools into logical groups."); output.displayHelpContext(" They can be created manually or auto-generated from app configurations."); output.displaySpaceBuffer(2); // Create action choices const choices = []; // Add tool navigation if tools are available if (toolset.toolDetails && toolset.toolDetails.length > 0) { choices.push({ name: "🔍 View Tool Details", value: { action: "view_tools" }, }); } choices.push({ name: "📋 Copy Tool List", value: { action: "copy_tools" }, }, new inquirer.Separator(), { name: "[Back]", value: { action: "back" }, }); // Show actions menu const { selection } = await inquirer.prompt([ { type: "list", name: "selection", message: "Actions:", choices, pageSize: 10, }, ]); // Handle actions if (selection.action === "copy_tools") { if (toolset.toolDetails && toolset.toolDetails.length > 0) { const toolList = toolset.toolDetails .map((tool) => tool.namespacedName) .join("\n"); if (copyToClipboard(toolList)) { output.success("✅ Tool list copied to clipboard!"); } else { output.error("❌ Failed to copy to clipboard"); } } else { output.warn("No tools to copy"); } await new Promise((resolve) => setTimeout(resolve, 1500)); return { action: "stay" }; } else if (selection.action === "view_tools") { // Navigate to tool selection menu return { action: "navigate", nextView: ViewType.TOOL_DETAIL, data: toolset, itemName: "Tool Details", }; } return { action: selection.action }; } /** * Display individual tool detail view */ export async function showToolDetail(toolset, _allToolsets) { // Clear screen and show header console.clear(); output.displayHeader(`Tools in ${toolset.name}`); output.displaySpaceBuffer(1); if (!toolset.toolDetails || toolset.toolDetails.length === 0) { output.warn("No tool details available for this toolset."); output.displaySpaceBuffer(2); const { action } = await inquirer.prompt([ { type: "list", name: "action", message: "Actions:", choices: [ { name: "[Back]", value: "back", }, ], }, ]); return { action }; } // Group tools by server for better organization const serverGroups = toolset.serverGroups || {}; const choices = []; // Add tools grouped by server Object.entries(serverGroups).forEach(([serverName, tools]) => { choices.push(new inquirer.Separator(`── ${serverName.toUpperCase()} Server (${tools.length} tools) ──`)); tools.forEach((tool) => { let name = `🔧 ${theme.primary(tool.toolName)}`; if (tool.description) { const shortDesc = tool.description.length > 50 ? tool.description.substring(0, 50) + "..." : tool.description; name += `\n ${theme.muted(shortDesc)}`; } name += `\n ${theme.warning(`Server: ${tool.serverName} | ID: ${tool.namespacedName}`)}`; choices.push({ name, value: tool, short: tool.toolName, }); }); choices.push(new inquirer.Separator()); }); // Add navigation options choices.push({ name: "[Back to Toolset]", value: { action: "back" }, }); // Show tool selection menu const { selection } = await inquirer.prompt([ { type: "list", name: "selection", message: "Select a tool for detailed information:", choices, pageSize: Math.min(25, choices.length), }, ]); // Handle selection if (selection.action === "back") { return { action: "back" }; } else { // Show individual tool details return await showIndividualToolDetail(selection, toolset); } } /** * Display individual tool information */ async function showIndividualToolDetail(tool, _toolset) { // Clear screen and show header console.clear(); output.displayHeader(`Tool: ${tool.toolName}`); output.displaySpaceBuffer(1); // Display tool details output.info(`Server: ${theme.primary(tool.serverName)} (stdio)`); output.info(`Full Name: ${theme.value(tool.namespacedName)}`); if (tool.description) { output.info(`Description: ${theme.muted(tool.description)}`); } if (tool.parameters && tool.parameters.length > 0) { output.info(`Parameters: ${theme.warning(tool.parameters.join(", "))}`); } output.displaySpaceBuffer(1); // Show usage examples output.displaySubHeader("📝 Usage Information:"); output.info(`â€ĸ This tool is provided by the '${theme.warning(tool.serverName)}' MCP server`); output.info(`â€ĸ It can be called using the namespaced name: ${theme.value(tool.namespacedName)}`); if (tool.description) { output.info(`â€ĸ Purpose: ${theme.muted(tool.description)}`); } output.displaySpaceBuffer(2); // Create action choices const choices = [ { name: "📋 Copy Tool Name", value: { action: "copy_name" }, }, { name: "🔗 View Server Details", value: { action: "view_server" }, }, new inquirer.Separator(), { name: "[Back to Tool List]", value: { action: "back" }, }, ]; // Show actions menu const { selection } = await inquirer.prompt([ { type: "list", name: "selection", message: "Actions:", choices, pageSize: 10, }, ]); // Handle actions if (selection.action === "copy_name") { if (copyToClipboard(tool.namespacedName)) { output.success(`✅ Copied '${tool.namespacedName}' to clipboard!`); } else { output.error("❌ Failed to copy to clipboard"); } await new Promise((resolve) => setTimeout(resolve, 1500)); return { action: "stay" }; } else if (selection.action === "view_server") { // Navigate to server details - this would need to be implemented // For now, just show a message output.info(`Navigate to server '${tool.serverName}' details would go here.`); await new Promise((resolve) => setTimeout(resolve, 2000)); return { action: "stay" }; } return { action: selection.action }; } //# sourceMappingURL=toolsets-menu.js.map