Skip to main content
Glama

suggest_remediations

Extract prioritized remediation suggestions from Tactual analysis results, ranked by severity, to identify the most critical fixes first.

Instructions

Extract the top unique remediation suggestions from a Tactual analysis result, ranked by severity. Returns a JSON array of {targetId, severity, score, fix, penalties}.

Read-only, no side effects. Most useful with large JSON results where you want a prioritized shortlist of what to fix first. For SARIF results, the findings already contain fix suggestions inline — this tool is redundant in that case. Input must be a JSON string from analyze_url (format='json').

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
analysisYesAnalysis result as JSON string
maxSuggestionsNoMaximum number of suggestions to return

Implementation Reference

  • Core handler: runs the suggest_remediations logic — extracts findings via extractFindings, deduplicates by fix string, sorts by score (ascending, so worst first), and returns up to maxSuggestions results.
    export function runSuggestRemediations(
      opts: SuggestRemediationsOptions,
    ): Suggestion[] {
      let findings;
      try {
        findings = extractFindings(opts.analysis);
      } catch (err) {
        throw new SuggestRemediationsError(
          "bad-input",
          `Error parsing analysis: ${err instanceof Error ? err.message : String(err)}`,
        );
      }
    
      const max = opts.maxSuggestions ?? 10;
      const sorted = [...findings].sort((a, b) => a.overall - b.overall);
    
      const suggestions: Suggestion[] = [];
      const seenFixes = new Set<string>();
    
      for (const finding of sorted) {
        for (const fix of finding.suggestedFixes) {
          if (seenFixes.has(fix)) continue;
          seenFixes.add(fix);
          suggestions.push({
            targetId: finding.targetId,
            severity: finding.severity,
            score: finding.overall,
            fix,
            penalties: finding.penalties,
          });
          if (suggestions.length >= max) return suggestions;
        }
      }
      return suggestions;
    }
  • Suggestion interface (targetId, severity, score, fix, penalties) and SuggestRemediationsOptions interface (analysis, maxSuggestions).
    export interface Suggestion {
      targetId: string;
      severity: string;
      score: number;
      fix: string;
      penalties: string[];
    }
    
    export interface SuggestRemediationsOptions {
      analysis: unknown;
      maxSuggestions?: number;
    }
  • MCP registration: registers the 'suggest_remediations' tool on the McpServer with Zod inputSchema describing 'analysis' (string) and 'maxSuggestions' (number, default 10). The handler calls runSuggestRemediations and returns formatted JSON text.
    export function registerSuggestRemediations(server: McpServer): void {
      server.registerTool(
        "suggest_remediations",
        {
          description:
            "Extract the top unique remediation suggestions from a Tactual analysis result, " +
            "ranked by severity. Returns a JSON array of {targetId, severity, score, fix, penalties}.\n\n" +
            "Read-only, no side effects. Most useful with large JSON results where you want a prioritized " +
            "shortlist of what to fix first. For SARIF results, the findings already contain fix " +
            "suggestions inline — this tool is redundant in that case. " +
            "Input must be a JSON string from analyze_url (format='json').",
          inputSchema: {
            analysis: z.string().describe("Analysis result as JSON string"),
            maxSuggestions: z.number().default(10).describe("Maximum number of suggestions to return"),
          },
        },
        async ({ analysis, maxSuggestions }) => {
          try {
            const suggestions = runSuggestRemediations({
              analysis: JSON.parse(analysis),
              maxSuggestions,
            });
            return {
              content: [
                { type: "text" as const, text: JSON.stringify(suggestions, null, 2) },
              ],
            };
          } catch (err) {
            const text =
              err instanceof SuggestRemediationsError
                ? err.message
                : `Error: ${err instanceof Error ? err.message : String(err)}`;
            return { content: [{ type: "text" as const, text }], isError: true };
          }
        },
      );
  • CLI registration: registers a 'suggest-remediations' command on Commander, reads a file path for analysis JSON, parses it, and calls runSuggestRemediations.
    export function registerSuggestRemediations(program: Command): void {
      program
        .command("suggest-remediations")
        .description("Extract top remediation suggestions from an analysis result")
        .argument("<file>", "Path to analysis JSON file")
        .option(
          "-n, --max-suggestions <n>",
          "Maximum suggestions to show",
          "10",
        )
        // Legacy alias; parity plan renames this `maxSuggestions` everywhere.
        .option("--max <n>", "(deprecated - use --max-suggestions)")
        .action(
          async (file: string, opts: { maxSuggestions?: string; max?: string }) => {
            const fs = await import("fs/promises");
            try {
              const data = JSON.parse(await fs.readFile(file, "utf-8"));
              const max = parseInt(opts.maxSuggestions ?? opts.max ?? "10", 10);
              const suggestions = runSuggestRemediations({
                analysis: data,
                maxSuggestions: max,
              });
    
              console.log("");
              if (suggestions.length === 0) {
                console.log("  No fix suggestions found.");
                console.log("");
                return;
              }
              for (const s of suggestions) {
                console.log(`  ${s.score}/100  ${s.targetId}`);
                console.log(`  \x1b[2m-> ${s.fix}\x1b[0m`);
                console.log("");
              }
            } catch (err) {
              if (err instanceof SuggestRemediationsError) {
                console.error(err.message);
              } else {
                console.error(`Error: ${err}`);
              }
              process.exit(1);
            }
          },
        );
    }
  • extractFindings helper: normalizes analysis results from raw AnalysisResult, SummarizedResult (worstFindings), or SARIF format into NormalizedFinding[] used by the pipeline handler.
    export function extractFindings(data: unknown): NormalizedFinding[] {
      const obj = data as Record<string, unknown>;
    
      if (Array.isArray(obj.findings)) {
        return (obj.findings as Array<Record<string, unknown>>).map((f) => ({
          targetId: String(f.targetId ?? ""),
          overall: getOverallScore(f),
          severity: String(f.severity ?? "unknown"),
          penalties: (f.penalties as string[] | undefined) ?? [],
          suggestedFixes: (f.suggestedFixes as string[] | undefined) ?? [],
        }));
      }
    
      if (Array.isArray(obj.worstFindings)) {
        return (obj.worstFindings as Array<Record<string, unknown>>).map((f) => ({
          targetId: String(f.targetId ?? ""),
          overall: getOverallScore(f),
          severity: String(f.severity ?? "unknown"),
          penalties: (f.penalties as string[] | undefined) ?? [],
          suggestedFixes: (f.suggestedFixes as string[] | undefined) ?? [],
        }));
      }
    
      if (Array.isArray(obj.runs)) {
        const runs = obj.runs as Array<Record<string, unknown>>;
        const results = (runs[0]?.results ?? []) as Array<Record<string, unknown>>;
        return results
          .filter((r) => {
            const props = r.properties as Record<string, unknown> | undefined;
            return !props?.truncated;
          })
          .map((r) => {
            const props = (r.properties ?? {}) as Record<string, unknown>;
            const scores = (props.scores ?? {}) as Record<string, unknown>;
            const locs = (r.locations ?? []) as Array<Record<string, unknown>>;
            const logLocs = (locs[0]?.logicalLocations ?? []) as Array<Record<string, unknown>>;
            const targetId = String(logLocs[0]?.name ?? "");
            const level = String(r.level ?? "note");
            const severity =
              level === "error" ? "high" : level === "warning" ? "moderate" : "acceptable";
            const msgText = String((r.message as Record<string, unknown>)?.text ?? "");
            const penalties: string[] = [];
            const suggestedFixes: string[] = [];
            const issuesMatch = msgText.match(/Issues:\s*(.+?)(?:\.\s*Fixes:|$)/);
            if (issuesMatch) penalties.push(...issuesMatch[1].split(/;\s*/).filter(Boolean));
            const fixesMatch = msgText.match(/Fixes:\s*(.+)/);
            if (fixesMatch) suggestedFixes.push(...fixesMatch[1].split(/;\s*/).filter(Boolean));
    
            return {
              targetId,
              overall: Number(scores.overall ?? 0),
              severity,
              penalties,
              suggestedFixes,
            };
          });
      }
    
      throw new Error(
        'Input must contain "findings" (raw), "worstFindings" (summarized), or "runs" (SARIF) array. ' +
          "Pass the full analysis result object, not a sub-field.",
      );
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Declares read-only and no side effects. Describes output format. Warns about redundancy for SARIF. Lacks error handling details, but given no annotations, it provides sufficient behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four concise sentences, each adding value. Front-loaded with core purpose. No redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, input/output format, usage guidelines, and exclusion criteria. Sufficient for an agent to correctly invoke the tool without additional context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers both parameters with descriptions. Description adds extra semantics for the 'analysis' parameter (must be from analyze_url in JSON format). The maxSuggestions parameter is clear from schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it extracts top unique remediation suggestions from a Tactual analysis result, ranked by severity, and returns a JSON array. Differentiates from sibling tools by specifying it is a post-processing step and redundant for SARIF results.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use (with large JSON results needing prioritized shortlist) and when-not-to-use (SARIF results already have suggestions). Also specifies input must come from analyze_url with format='json'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/tactual-dev/tactual'

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