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(), });

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