Skip to main content
Glama
dacebt

Prompt Cleaner MCP Server

by dacebt

normalize-prompt

Standardizes and refines prompts by removing sensitive data, restructuring content, and clarifying formatting for consistent LLM processing.

Instructions

Alias of cleaner. Keywords: normalize, restructure, clarify, tighten, format, preflight. Same input/output schema as 'cleaner'.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
promptYesRaw user prompt
modeNoRetouching mode; default 'general'. Use 'code' only for code-related prompts.
temperatureNoSampling temperature (0-2); default 0.2

Implementation Reference

  • src/tools.ts:56-74 (registration)
    Registration of the 'normalize-prompt' tool in the listTools() function, including its description and input schema (identical to 'cleaner').
    {
      name: "normalize-prompt",
      description:
        "Alias of cleaner. Keywords: normalize, restructure, clarify, tighten, format, preflight. Same input/output schema as 'cleaner'.",
      inputSchema: {
        type: "object",
        properties: {
          prompt: { type: "string", description: "Raw user prompt" },
          mode: {
            type: "string",
            enum: ["code", "general"],
            description:
              "Retouching mode; default 'general'. Use 'code' only for code-related prompts.",
          },
          temperature: { type: "number", description: "Sampling temperature (0-2); default 0.2" },
        },
        required: ["prompt"],
      },
    },
  • Dispatch handler in callTool() for 'normalize-prompt' (shared with 'cleaner' and 'sanitize-text'), which validates input using RetouchInput schema, calls retouchPrompt, validates output, logs, and returns JSON.
    case "cleaner":
    case "sanitize-text":
    case "normalize-prompt": {
      const parsed = RetouchInput.parse(args);
      const result = await retouchPrompt(parsed);
      const safe = RetouchOutput.parse(result);
      logger.info("retouch.prompt", {
        elapsed_ms: Date.now() - start,
        input_len: parsed.prompt.length,
        preview: logger.preview(parsed.prompt),
        request_id: parsed.requestId,
      });
      return jsonContent(safe);
    }
  • Core implementation of prompt normalization in retouchPrompt(): loads system prompt from file, calls LLM (chatCompletions) with retry logic for JSON output, extracts/parses JSON, applies redaction, validates with RetouchOutput schema, and logs extensively.
    export async function retouchPrompt(input: RetouchInputT): Promise<RetouchOutputT> {
      const start = Date.now();
      const system = await loadCleanerSystemPrompt();
      const baseTemperature = input.temperature ?? 0;
    
      const userBody = `MODE: ${input.mode || "general"}\nRAW_PROMPT:\n${input.prompt}`;
      const sys = system;
    
      class CleanerNonJsonError extends Error {
        constructor(message = "Cleaner returned non-JSON") {
          super(message);
          this.name = "CleanerNonJsonError";
        }
      }
    
      // Retry loop for content-level non-JSON responses
      const maxAttempts = Math.max(1, 1 + (config.contentMaxRetries ?? 0));
      let lastErr: Error | undefined;
      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        // Escalate on retries: enforce temperature 0 and stricter system instructions
        const strictSuffix =
          attempt > 1
            ? "\n\nSTRICT OUTPUT MODE: Respond with EXACTLY ONE JSON object and nothing else. No prose. No code fences. No prefix/suffix."
            : "";
        const sysAttempt = sys + strictSuffix;
        const tempAttempt = attempt > 1 ? 0 : baseTemperature;
    
        const response = await chatCompletions(
          {
            model: config.model,
            temperature: tempAttempt,
            max_tokens: 600,
            messages: [
              { role: "system", content: sysAttempt },
              { role: "user", content: userBody },
            ],
          },
          { requestId: input.requestId },
        );
    
        const content = response.choices?.[0]?.message?.content ?? "";
        const initial = redactSecrets(content);
        const redactedText = initial.text;
        try {
          const obj = extractFirstJsonObject(redactedText);
          const parsed = RetouchOutput.safeParse(obj);
          if (!parsed.success) {
            throw new Error("shape-error");
          }
    
          const { value, redactions } = ensureNoSecretsInObject(parsed.data);
          const totalRedactions = initial.count + redactions;
          const result: RetouchOutputT = {
            ...value,
            redactions:
              totalRedactions > 0 ? Array(totalRedactions).fill("[REDACTED]") : value.redactions,
          };
    
          logger.info("retouch.prompt", {
            elapsed_ms: Date.now() - start,
            input_len: input.prompt.length,
            preview: logger.preview(input.prompt),
            request_id: input.requestId,
            attempts: attempt,
            outcome: "ok",
          });
    
          return result;
        } catch (e: any) {
          lastErr = new CleanerNonJsonError("Cleaner returned non-JSON");
          if (attempt < maxAttempts) {
            const base = config.backoffMs ?? 250;
            const jitter = config.backoffJitter ?? 0.2;
            const exp = Math.pow(2, attempt - 1);
            const rand = 1 + (Math.random() * 2 - 1) * jitter; // 1 +/- jitter
            const delay = Math.max(0, Math.floor(base * exp * rand));
            logger.warn("retouch.retry", {
              request_id: input.requestId,
              attempt,
              delay_ms: delay,
              reason: "non-json",
            });
            await new Promise((r) => setTimeout(r, delay));
            continue;
          }
        }
      }
      logger.info("retouch.prompt", {
        elapsed_ms: Date.now() - start,
        input_len: input.prompt.length,
        preview: logger.preview(input.prompt),
        request_id: input.requestId,
        attempts: maxAttempts,
        outcome: "error",
        reason: "non-json",
      });
      throw lastErr ?? new CleanerNonJsonError("Cleaner returned non-JSON");
    }
  • Zod schemas for RetouchInput and RetouchOutput used for validation in the normalize-prompt (cleaner) tool handler.
    export const RetouchInput = z.object({
      prompt: z.string().min(1),
      mode: z.enum(["code", "general"]).optional(),
      temperature: z.number().min(0).max(2).optional(),
      requestId: z.string().uuid().optional(),
    });
    
    export const RetouchOutput = z.object({
      retouched: z.string().min(1),
      notes: z.array(z.string()).optional(),
      openQuestions: z.array(z.string()).optional(),
      risks: z.array(z.string()).optional(),
      redactions: z.array(z.literal("[REDACTED]")).optional(),
    });
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It only states it's an alias with the same input/output schema as 'cleaner', but doesn't disclose behavioral traits such as whether it's read-only, destructive, has rate limits, or requires authentication. This leaves significant gaps in understanding how the tool behaves beyond basic functionality.

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

Conciseness4/5

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

The description is brief and to the point, consisting of two sentences that efficiently convey key information (alias relationship and keywords). However, it could be more front-loaded with a clearer purpose statement, but it avoids unnecessary verbosity.

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

Completeness2/5

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

Given no annotations, no output schema, and a vague purpose, the description is incomplete. It doesn't adequately explain what the tool does, when to use it, or its behavioral aspects, making it insufficient for an agent to fully understand the tool's role and operation in context.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema fully documents the parameters. The description adds no additional meaning beyond stating it has the 'same input/output schema as cleaner', which doesn't explain parameter semantics further. This meets the baseline of 3 since the schema handles the heavy lifting.

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

Purpose3/5

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

The description states this is an 'alias of cleaner' and lists keywords like 'normalize, restructure, clarify, tighten, format, preflight', which gives a vague sense of purpose but lacks a specific verb+resource statement. It doesn't clearly explain what the tool actually does beyond being related to 'cleaner', making it somewhat ambiguous rather than tautological.

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

Usage Guidelines2/5

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

The description mentions it's an alias of 'cleaner' and lists keywords, but provides no explicit guidance on when to use this tool versus alternatives like 'cleaner' or 'sanitize-text'. There's no context on use cases, prerequisites, or exclusions, leaving the agent with minimal direction.

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/dacebt/prompt-cleaner-mcp'

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