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
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | Raw user prompt | |
| mode | No | Retouching mode; default 'general'. Use 'code' only for code-related prompts. | |
| temperature | No | Sampling temperature (0-2); default 0.2 |
Implementation Reference
- src/cleaner.ts:83-180 (handler)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"); }
- src/tools.ts:12-36 (schema)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; });
- src/tools.ts:92-105 (handler)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); }
- src/shapes.ts:5-18 (schema)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(), });