Skip to main content
Glama
dacebt

Prompt Cleaner MCP Server

by dacebt

cleaner

Clean and structure raw user prompts by normalizing tone, removing sensitive data, and preserving intent. Outputs JSON with retouched text, redactions, notes, and risks for safer pre-processing and planning.

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
modeNoRetouching mode; default 'general'. Use 'code' only for code-related prompts.
promptYesRaw user prompt
temperatureNoSampling temperature (0-2); default 0.2

Implementation Reference

  • Primary handler function that implements the 'cleaner' tool logic: calls LLM with system prompt to retouch input, parses JSON output with robust extraction and retries, applies PII redaction, and structures the result.
    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 input (RetouchInput) and output (RetouchOutput) validation used by the cleaner tool.
    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(), }); export type HealthOutputT = z.infer<typeof HealthOutput>; export type RetouchInputT = z.infer<typeof RetouchInput>; export type RetouchOutputT = z.infer<typeof RetouchOutput>;
  • src/tools.ts:11-36 (registration)
    Registration of the 'cleaner' tool in listTools(), including MCP-compatible description and JSON input schema.
    { 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"], }, },
  • Dispatch handler in callTool() that invokes the retouchPrompt for 'cleaner' and its aliases, with input/output parsing.
    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); }
  • Helper function to robustly extract the first valid JSON object from LLM response text, handling fences and partial outputs.
    function extractFirstJsonObject(text: string): any { // Remove common code fences (```json ... ``` or ``` ... ```) const unfenced = text .replace(/```[a-zA-Z]*\n?/g, "") .replace(/```/g, "") .trim(); // Fast path: try parsing the whole string try { return JSON.parse(unfenced); } catch {} // Scan for the first balanced JSON object while ignoring braces inside strings const s = unfenced; let start = -1; let depth = 0; let inString = false; let escape = false; for (let i = 0; i < s.length; i++) { const ch = s[i]; if (inString) { if (escape) { escape = false; continue; } if (ch === "\\") { escape = true; continue; } if (ch === '"') { inString = false; } continue; } if (ch === '"') { inString = true; continue; } if (ch === "{") { if (depth === 0) start = i; depth++; continue; } if (ch === "}") { if (depth > 0) { depth--; if (depth === 0 && start !== -1) { const candidate = s.slice(start, i + 1); try { return JSON.parse(candidate); } catch { // keep scanning; there may be another valid object ahead start = -1; } } } } } throw new Error("Cleaner returned non-JSON"); }

Other Tools

Related 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