Skip to main content
Glama
dacebt

Prompt Cleaner MCP Server

by dacebt

cleaner

Normalize and sanitize raw user prompts by structuring content, redacting sensitive information, and preserving intent for safer processing.

Instructions

Pre-reasoning prompt normalizer and PII redactor. Use when: you receive raw/free-form user text and need it cleaned before planning, tool selection, or code execution. Does: normalize tone, structure the ask, and redact secrets; preserves user intent. Safe: read-only, idempotent, no side effects (good default to run automatically). Input: { prompt, mode?, temperature? } — defaults mode='general', temperature=0.2; mode='code' only for code-related prompts. Output: JSON { retouched, notes?, openQuestions?, risks?, redactions? }. Keywords: clean, sanitize, normalize, redact, structure, preprocess, guardrails

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

  • Core handler function `retouchPrompt` implementing the tool logic: loads system prompt from prompts/cleaner.md, calls LLM with retry logic for JSON output, extracts JSON, validates with RetouchOutput schema, redacts secrets, logs metrics.
    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");
    }
  • MCP tool definition for 'cleaner' including description and JSON inputSchema defining parameters: prompt (required), mode (enum code/general), temperature (number 0-2).
      name: "cleaner",
      description: [
        "Pre-reasoning prompt normalizer and PII redactor.",
        "Use when: you receive raw/free-form user text and need it cleaned before planning, tool selection, or code execution.",
        "Does: normalize tone, structure the ask, and redact secrets; preserves user intent.",
        "Safe: read-only, idempotent, no side effects (good default to run automatically).",
        "Input: { prompt, mode?, temperature? } — defaults mode='general', temperature=0.2; mode='code' only for code-related prompts.",
        "Output: JSON { retouched, notes?, openQuestions?, risks?, redactions? }.",
        "Keywords: clean, sanitize, normalize, redact, structure, preprocess, guardrails",
      ].join("\n"),
      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"],
      },
    },
  • src/server.ts:24-39 (registration)
    MCP server registration of tool handlers: ListToolsRequestHandler calls listTools() which includes 'cleaner', CallToolRequestHandler dispatches to callTool(name, args).
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      logger.info("tools.list", {});
      return { tools: listTools() };
    });
    
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const name = request.params.name;
      const args = request.params.arguments ?? {};
      const requestId = (args as any)?.requestId || randomUUID();
      const withRid = { ...(args as any), requestId };
      logger.info("tools.call.start", { name, request_id: requestId });
      const res: any = await callTool(name, withRid);
      logger.info("tools.call.done", { name, request_id: requestId });
      // Return MCP-spec content unchanged (including json type when present)
      return res;
    });
  • Dispatch handler in callTool function for 'cleaner' and aliases: parses input with RetouchInput, calls retouchPrompt, parses output, logs, returns JSON content.
    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);
    }
  • Zod schemas for input (RetouchInput) and output (RetouchOutput) validation used in handler parsing.
    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(),
    });
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key traits: 'read-only, idempotent, no side effects', which clarifies safety and operational characteristics. However, it lacks details on rate limits, error handling, or specific PII types redacted, leaving some behavioral aspects unspecified.

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 well-structured with clear sections (purpose, usage, behavior, input, output, keywords) and front-loaded key information. Most sentences earn their place, but the keyword list at the end is somewhat redundant with earlier content, slightly reducing efficiency without adding new insights.

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

Completeness4/5

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

Given the tool's moderate complexity, no annotations, and no output schema, the description does a good job covering purpose, usage, behavior, and parameters. It explains the output format ('JSON { retouched, notes?, openQuestions?, risks?, redactions? }'), compensating for the lack of output schema. However, it could provide more detail on error cases or specific redaction rules for completeness.

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 description coverage is 100%, so the baseline is 3. The description adds value by explaining parameter defaults ('defaults mode='general', temperature=0.2') and usage context for 'mode' ('mode='code' only for code-related prompts'), which enhances understanding beyond the schema's enum and descriptions. It doesn't fully elaborate on 'temperature' effects, keeping it from a perfect score.

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?

The description clearly states the tool's purpose with specific verbs ('normalize', 'redact', 'structure') and resources ('raw/free-form user text'), distinguishing it from siblings like 'normalize-prompt' and 'sanitize-text' by emphasizing pre-reasoning processing and PII redaction. It explicitly mentions preserving user intent, which adds nuance beyond basic normalization.

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?

The description provides explicit guidance on when to use this tool ('when you receive raw/free-form user text and need it cleaned before planning, tool selection, or code execution') and distinguishes it from alternatives by specifying mode usage ('mode='code' only for code-related prompts'). It also positions it as a 'good default to run automatically', offering clear context for application.

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