Skip to main content
Glama
REMnux

REMnux MCP Server

Official
by REMnux

suggest_tools

Identify file types and get recommended REMnux malware analysis tools to plan investigation strategies before execution.

Instructions

Detect file type and return recommended REMnux analysis tools without executing them. Use this to plan an analysis strategy, then run individual tools with run_tool. Returns tool names, descriptions, depth tiers, and expert analysis hints.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
fileYesFilename relative to samples directory, or absolute path in local mode
depthNoFilter recommendations by depth tier: 'quick' (triage only), 'standard' (default), 'deep' (all tools)standard

Implementation Reference

  • Main handler function handleSuggestTools that implements the suggest_tools tool. It validates file paths, detects file type using the 'file' command, matches to a category, retrieves recommended tools from the registry, checks tool availability, queries the catalog for additional tools, and returns a formatted response with tool recommendations and expert analysis hints.
    export async function handleSuggestTools( deps: HandlerDeps, args: SuggestToolsArgs, ) { const startTime = Date.now(); try { const { connector, config } = deps; const depth = (args.depth ?? "standard") as DepthTier; // Validate file path (skip unless --sandbox) if (!config.noSandbox) { const validation = validateFilePath(args.file, config.samplesDir); if (!validation.safe) { return formatError("suggest_tools", new REMnuxError( validation.error || "Invalid file path", "INVALID_PATH", "validation", "Use a relative path within the samples directory", ), startTime); } } const filePath = (config.mode === "local" && args.file.startsWith("/")) ? args.file : `${config.samplesDir}/${args.file}`; // Detect file type let fileOutput: string; try { const result = await connector.execute(["file", filePath], { timeout: 30000 }); fileOutput = result.stdout?.trim() || ""; if (!fileOutput) { return formatError("suggest_tools", new REMnuxError( "Could not determine file type (empty `file` output)", "EMPTY_OUTPUT", "tool_failure", "Check that the file exists and is readable", ), startTime); } } catch (error) { const msg = `Error running file command: ${error instanceof Error ? error.message : "Unknown error"}`; return formatError("suggest_tools", new REMnuxError( msg, "EMPTY_OUTPUT", "tool_failure", "Check that the file exists and is readable", ), startTime); } // Match category and get tools from registry const category = matchFileType(fileOutput, args.file); const primaryTag = CATEGORY_TAG_MAP[category.name] ?? "fallback"; const tools = toolRegistry.byTagAndTier(primaryTag, depth); // Check tool availability (batch all unique commands in one shell call) const uniqueCommands = [...new Set(tools.map((t) => t.command))]; const availableCommands = new Set<string>(); if (uniqueCommands.length > 0) { try { // Single shell call: check all commands at once const checks = uniqueCommands.map((c) => `which ${c} >/dev/null 2>&1 && echo "${c}"`).join("; "); const check = await connector.executeShell(checks, { timeout: 10000, cwd: config.samplesDir, }); for (const line of (check.stdout || "").split("\n")) { const cmd = line.trim(); if (cmd) availableCommands.add(cmd); } } catch { // On failure, assume all available (graceful degradation) for (const c of uniqueCommands) availableCommands.add(c); } } const recommended = tools.map((t) => ({ name: t.name, description: t.description, tier: t.tier, tags: t.tags ?? [], ...(availableCommands.has(t.command) ? {} : { available: false as const }), })); // Query catalog for additional tools not in the registry let additionalTools: Array<{ command: string; name: string; description: string; website: string }> = []; try { const normalize = (c: string) => c.replace(/\.py$/, ''); const registryCommands = new Set(tools.map((t) => normalize(t.command))); // Check if a catalog tool is already covered by the registry const isCovered = (catalogCmd: string): boolean => { // Direct match (with .py normalization) if (registryCommands.has(normalize(catalogCmd))) return true; // Alias match (catalog package name → registry CLI commands) // Alias match: catalog package name → registry CLI commands. // Uses some() deliberately: if ANY tool from the package is recommended, // the catalog package entry is hidden to avoid confusing overlap. const aliases = CATALOG_ALIASES[catalogCmd]; return aliases !== undefined && aliases.some((a) => registryCommands.has(normalize(a))); }; const seen = new Set<string>(); additionalTools = toolCatalog.forMcpCategory(category.name) .filter((ct) => !isCovered(ct.command)) .filter((ct) => { if (seen.has(ct.command)) return false; seen.add(ct.command); return true; }) .map((ct) => ({ command: ct.command, name: ct.name, description: ct.description, website: ct.website, })); } catch (err) { console.error("WARNING: Catalog query failed for additional_tools:", err); } return formatResponse("suggest_tools", { file: args.file, detected_type: fileOutput, matched_category: category.name, depth, recommended_tools: recommended, ...(recommended.length === 0 && { warning: `No tools registered for category "${category.name}" at depth "${depth}". Try depth "deep" or use run_tool directly.`, }), analysis_hints: generateHints(category.name, fileOutput), ...(additionalTools.length > 0 && { additional_tools: additionalTools, additional_tools_note: "Additional tools available on REMnux for this file type. " + "Not auto-run by analyze_file. Use run_tool to invoke manually.", }), }, startTime); } catch (error) { return formatError("suggest_tools", toREMnuxError(error, deps.config.mode), startTime); } }
  • Schema definition for suggest_tools input validation. Defines 'file' (required) and 'depth' (optional, defaults to 'standard') parameters using zod validation.
    export const suggestToolsSchema = z.object({ file: z.string().describe("Filename relative to samples directory, or absolute path in local mode"), depth: z.enum(["quick", "standard", "deep"]).optional().default("standard").describe( "Filter recommendations by depth tier: 'quick' (triage only), 'standard' (default), 'deep' (all tools)" ), }); export type SuggestToolsArgs = z.input<typeof suggestToolsSchema>;
  • src/index.ts:187-195 (registration)
    MCP tool registration for suggest_tools. Registers the tool with its name, description, schema, and handler function using server.tool().
    // Tool: suggest_tools - Get tool recommendations for a file server.tool( "suggest_tools", "Detect file type and return recommended REMnux analysis tools without executing them. " + "Use this to plan an analysis strategy, then run individual tools with run_tool. " + "Returns tool names, descriptions, depth tiers, and expert analysis hints.", suggestToolsSchema.shape, (args) => handleSuggestTools(deps, args) );
  • Helper function generateHints that creates dynamic analysis guidance by combining base hints with property-specific observations (packer detection, .NET detection, compiler detection) extracted from file command output.
    /** Generate dynamic hints by augmenting base hints with property-specific guidance. */ function generateHints(category: string, fileOutput: string): string { const base = BASE_HINTS[category] ?? BASE_HINTS.Unknown; const props = extractFileProperties(fileOutput); const extras: string[] = []; if (props.packed) { extras.push( `Packer detected: ${props.packed}. ` + "capa and floss results may be limited on packed samples. " + (props.packed === "UPX" ? "UPX can be unpacked with the upx-decompress tool — recommend unpacking then re-analyzing." : "No standard unpacker available; static code analysis will be limited. " + "Focus on: certificate/signature artifacts (disitool.py for structure, pestr for embedded text including Unicode), " + "metadata masquerading (exiftool, peframe), and string patterns for C2/IOCs."), ); } if (props.isDotNet) { extras.push("Detected .NET assembly — ilspycmd decompilation recommended for source-level analysis. monodis --presources lists embedded resources (payloads, config data)."); } if (props.isDll) { extras.push("DLL detected — check exports with `pedump --exports` for entry point analysis."); } if (props.compiler === "AutoIt") { extras.push( "AutoIt compiled executable detected. " + "autoit-ripper extracts and decompiles the embedded script to .au3 source. " + "Review the decompiled script for C2 URLs, obfuscated strings, and DllCall APIs." ); } else if (props.compiler) { extras.push(`Unusual compiler: ${props.compiler}. This may indicate specialized tooling or uncommon origin.`); } if (extras.length === 0) return base; return base + "\n\nAdditional notes: " + extras.join(" "); }
  • Helper function extractFileProperties that parses 'file' command output to detect packers (UPX, ASPack, etc.), .NET assemblies, DLLs, and compilers (PureBasic, MASM, Delphi, AutoIt) for enhanced recommendations.
    function extractFileProperties(fileOutput: string): FileProperties { const lower = fileOutput.toLowerCase(); const props: FileProperties = {}; if (/upx/i.test(fileOutput)) props.packed = "UPX"; else if (/aspack/i.test(fileOutput)) props.packed = "ASPack"; else if (/pecompact/i.test(fileOutput)) props.packed = "PECompact"; else if (/themida/i.test(fileOutput)) props.packed = "Themida"; if (lower.includes(".net") || lower.includes("mono/") || lower.includes("msil")) { props.isDotNet = true; } if (lower.includes("(dll)") || lower.includes("dll ")) { props.isDll = true; } if (/purebasic/i.test(fileOutput)) props.compiler = "PureBasic"; else if (/masm/i.test(fileOutput)) props.compiler = "MASM"; else if (/delphi/i.test(fileOutput)) props.compiler = "Delphi"; else if (/autoit/i.test(fileOutput)) props.compiler = "AutoIt"; return props; }

Latest Blog Posts

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/REMnux/remnux-mcp-server'

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