Skip to main content
Glama

hypertool-mcp

toolsets-menu.tsโ€ข14.2 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 { ToolsetInfo, ToolDetail } from "../show.js"; import { ViewType, MenuChoice, InteractiveOptions } from "./types.js"; /** * Format toolset choice for inquirer list */ function formatToolsetChoice(toolset: ToolsetInfo): MenuChoice { 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: ToolsetInfo[], options: InteractiveOptions ): Promise<{ action: string; nextView?: ViewType; data?: unknown; itemName?: string; }> { // 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: MenuChoice[] = []; // 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 โ”€โ”€") as any); for (const toolset of manualToolsets) { choices.push(formatToolsetChoice(toolset)); } } if (autoToolsets.length > 0) { choices.push( new inquirer.Separator("โ”€โ”€ Auto-generated Toolsets โ”€โ”€") as any ); for (const toolset of autoToolsets) { choices.push(formatToolsetChoice(toolset)); } } choices.push(new inquirer.Separator() as any, { 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: string): boolean { 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: ToolsetInfo, _allToolsets: ToolsetInfo[] ): Promise<{ action: string; nextView?: ViewType; data?: unknown; itemName?: string; }> { // 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: MenuChoice[] = []; // 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() as any, { 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: ToolsetInfo, _allToolsets: ToolsetInfo[] ): Promise<{ action: string; nextView?: ViewType; data?: unknown; itemName?: string; }> { // 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: MenuChoice[] = []; // Add tools grouped by server Object.entries(serverGroups).forEach(([serverName, tools]) => { choices.push( new inquirer.Separator( `โ”€โ”€ ${serverName.toUpperCase()} Server (${tools.length} tools) โ”€โ”€` ) as any ); 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() as any); }); // 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 as ToolDetail, toolset); } } /** * Display individual tool information */ async function showIndividualToolDetail( tool: ToolDetail, _toolset: ToolsetInfo ): Promise<{ action: string; nextView?: ViewType; data?: unknown; itemName?: string; }> { // 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: MenuChoice[] = [ { name: "๐Ÿ“‹ Copy Tool Name", value: { action: "copy_name" }, }, { name: "๐Ÿ”— View Server Details", value: { action: "view_server" }, }, new inquirer.Separator() as any, { 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 }; }

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