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