fairquery-llm-data-guard
OfficialUse this MCP server to find and redact PII and secrets in text/documents before sending them to an LLM, then restore originals afterward.
Detect and redact PII (emails, phones, SSNs, cards, IBANs, IPs, optional names/addresses) and secrets (AWS/GitHub/Slack/Stripe/OpenAI keys, JWTs, private keys, high-entropy tokens) in text or public txt/md/json/csv/HTML/PDF URLs.
Choose modes:
detect(report only),redact(return sanitizedoutput_text), orrestore(put originals back using a placeholder map).Pick redaction style: reversible
placeholder,mask, or saltedhash.Restore placeholders locally with
restore_placeholders(free, no token, no network).List detectors with
list_detectorsto see entity types, redaction styles, and pricing events (free, local).Run the paid Actor via
guard_textwithwaitForFinish=truefor immediate rows orfalsefor background runs.Poll background runs with
get_run_results(free) to page through results until finished.Tune detection: opt-in names/addresses/entropy scan, filter
entityTypes, setallowList, choosedefaultPhoneRegion, cap documents/chars, and control placeholder consistency.Control cost: pay per document and per finding; error rows are free; use
waitForFinish=falsefor large batches and poll later.
Detects and redacts leaked GitHub tokens in logs, code snippets and config files.
Detects and redacts leaked GitLab tokens in logs, code snippets and config files.
Detects and redacts leaked Google API keys in text to prevent credential disclosure.
Detects and redacts leaked npm tokens in text to prevent credential disclosure.
Detects and redacts leaked OpenAI API keys in text before it is sent to an LLM.
Detects and redacts leaked SendGrid API keys in text to prevent credential disclosure.
Detects and redacts leaked Slack tokens and webhooks in text to prevent credential disclosure.
Detects and redacts leaked Stripe API keys in text to prevent credential disclosure.
Detects and redacts leaked Twilio API keys in text to prevent credential disclosure.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@fairquery-llm-data-guardredact emails, phone numbers, and API keys from this text before sending to the LLM"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
LLM Data Guard: PII & Secret Redactor
Finds and redacts PII and secrets in text, HTML and PDF before they reach an LLM, for $1.50 per 1,000 documents plus $0.20 per 1,000 findings. Error rows are free. Example: 1,000 support tickets with 2 findings each on average cost 1,000 × $0.0015 + 2,000 × $0.0002 = $1.90, plus Apify's $0.00005 Actor-start fee.
Fairquery Apify Actor that finds and redacts personal data (PII) and secrets in text, HTML and PDF documents before they are sent to an LLM, a vector store or a third-party API. Each document gets a risk level, a list of findings (type, offsets, masked preview) and, in redact mode, a clean output_text you can pass straight to your model. Placeholders are reversible, so you can put the original values back into the model's answer.
No data retention. The Actor never logs document content and writes nothing except the result rows in the run's default dataset and a counts-only
RUN_SUMMARY. By default it also deletes the run'sINPUTrecord when it finishes (purgeInputAfterRun). Raw values are not stored in findings unless you turn onincludeFindingValues. You control the dataset; delete it (or the run) once you have read it.
When to use this Actor (LLM / tool selection)
Use it when an agent or pipeline needs to:
Strip emails, phone numbers, SSNs, card numbers, IBANs and IP addresses from prompts, chat logs, support tickets or CRM notes before LLM calls
Catch leaked credentials (AWS, GitHub, Slack, Stripe, OpenAI, Anthropic, Google, private keys, JWTs,
password=...) in logs, code snippets and config filesPseudonymise a dataset for RAG / fine-tuning with consistent placeholders (
[EMAIL_1],[PERSON_2])Restore real values in the LLM's answer (
mode: "restore")Audit a batch of documents for sensitive-data risk (
mode: "detect")
Covers OWASP LLM Top 10 risk LLM02 Sensitive Information Disclosure at the input side. It is a pattern-and-validation engine, not a guarantee: always keep defense in depth.
Related MCP server: classifinder-mcp
What it detects
Category | Types | Validation |
PII |
| Luhn + issuer prefix for cards, mod-97 + country length for IBANs, libphonenumber for phones, SSN area/group rules |
PII (opt-in) |
| Open-source NER (compromise, English) and street-address patterns |
Secrets |
| Gitleaks-style rules |
Secrets |
| Shannon-entropy threshold ( |
Secrets (opt-in) |
| Any 20+ char random-looking token |
Overlapping matches are resolved (secrets > cards/IBAN > SSN > email > IP > phone > address > names), so a card number is never also reported as a phone.
Redaction styles
| Example | Reversible |
|
| Yes, with |
|
| No |
|
| No; stable across runs if you set |
Input
Field | Type | Default | Description |
|
| – (sample texts are prefilled in the Console form) | One document per entry |
|
|
| Documents with your own IDs |
|
|
| Public txt/md/json/csv/HTML/PDF URLs (max 10 MB each) |
|
|
| |
|
|
| |
| bool |
| Adds |
| bool |
| Same value → same placeholder across documents |
| object |
| For |
| secret string | random per run | For |
| bool |
| |
| bool |
| Opt-in detectors |
| number |
| Bits/char for generic secrets |
| string |
| For numbers without |
|
| all | Only report these types |
|
|
| Values never flagged |
| bool |
| Put raw values into findings |
| int |
| Safety caps |
| bool |
| Delete the |
{
"texts": ["Hi, I'm Jane (jane.doe@acme.com, +1 415-555-0132). Card 4111 1111 1111 1111.", "AWS key AKIAIOSFODNN7EXAMPLE"],
"mode": "redact",
"redactionStyle": "placeholder",
"returnPlaceholderMap": true
}Output
One row per document (type: "document") plus machine-readable type: "error" rows.
{
"schema_version": "1.0.0", "type": "document", "id": "text-1", "source": "text", "chars": 78, "mode": "redact",
"finding_count": 3, "risk_level": "high",
"counts_by_type": { "EMAIL": 1, "PHONE": 1, "CREDIT_CARD": 1 },
"findings": [
{ "type": "EMAIL", "category": "pii", "rule": "email", "confidence": "high", "start": 14, "end": 31, "length": 17, "preview": "j***@a***.com", "replacement": "[EMAIL_1]" }
],
"output_text": "Hi, I'm Jane ([EMAIL_1], [PHONE_1]). Card [CREDIT_CARD_1].",
"placeholder_map": { "[EMAIL_1]": "jane.doe@acme.com", "[PHONE_1]": "+1 415-555-0132", "[CREDIT_CARD_1]": "4111 1111 1111 1111" }
}risk_level: high = any secret or card/SSN/IBAN/NI number; medium = other confident PII; low = only low-confidence (NER/entropy) hits; none.
Restore after the LLM answers:
{ "mode": "restore", "texts": ["Dear [EMAIL_1], your card ending [CREDIT_CARD_1]…"], "placeholderMap": { "[EMAIL_1]": "jane.doe@acme.com" } }Error codes: INPUT_EMPTY, MAX_DOCUMENTS_EXCEEDED, PLACEHOLDER_MAP_MISSING, DOCUMENT_FETCH_FAILED (error rows are never charged).
RUN_SUMMARY (key-value store) holds counts per type, charged events and settings only, never text.
Pricing
Event | Price | When |
| $0.0015 | Each document scanned (or restored) |
| $0.0002 | Each PII or secret finding reported |
| $0.00005 | Once per run, per GB of memory (Apify default event) |
Error rows (INPUT_EMPTY, MAX_DOCUMENTS_EXCEEDED, PLACEHOLDER_MAP_MISSING, DOCUMENT_FETCH_FAILED) are never charged.
Worked example: 1,000 support tickets, 2,000 findings in total: 1,000 × $0.0015 + 2,000 × $0.0002 = $1.50 + $0.40 = $1.90, plus $0.00005. A clean document with no findings costs $0.0015.
To cap spend, set a maximum cost per run in the run options; the Actor stops cleanly at the limit. entityTypes limits which types are reported (and charged as findings); maxDocuments caps documents per run. Prices are from the Store pricing tab (October 2026).
Memory / runtime
Runs at a fixed 512 MB (default and maximum; enough for NER), no browser, no proxy. Regex detectors process roughly 1–5 MB of text per second; NER (detectNames) is slower, so split very big batches into several runs. PDFs are parsed with unpdf (text layer only, no OCR).
MCP / Apify tools
Call from agents via Apify MCP with Actor fairquery/llm-data-guard-pii-secrets:
{ "actor": "fairquery/llm-data-guard-pii-secrets", "input": { "texts": ["..."], "mode": "redact" } }Local stdio MCP server
The mcp/ folder is a stdio MCP server (TypeScript, @modelcontextprotocol/sdk) with four tools, all with annotations and output schemas:
Tool | What it does | Cost |
| Runs this Actor and returns the dataset rows. With | Needs |
| Status and paged rows of a run started with | Free, needs |
| Lists detectable types, redaction styles and events | Free, local, no token |
| Puts original values back into placeholder text (e.g. an LLM answer) from a | Free, local, no token |
Typical round trip: list_detectors (optional) -> guard_text (redact, returnPlaceholderMap: true) -> [get_run_results if you used waitForFinish: false] -> send the redacted text to your LLM -> restore_placeholders on the answer. Only guard_text is billed; list_detectors and restore_placeholders run entirely on your machine.
cd mcp && npm install && npm run build
node scripts/smoke-test.mjs # initialize + tools/list + free tool, no token
APIFY_TOKEN=... node scripts/smoke-test.mjs --call # real call{ "mcpServers": { "fairquery-llm-data-guard": { "command": "node", "args": ["/path/to/llm-data-guard-pii-secrets/mcp/dist/index.js"], "env": { "APIFY_TOKEN": "<your Apify token>" } } } }Docker: the root Dockerfile builds the MCP server image (docker build -t fairquery-llm-data-guard-mcp .); it is also the Dockerfile for Glama. The Actor image is .actor/Dockerfile. glama.json lists fairquery as maintainer.
Works well after
Website Content Crawler: scrub crawled pages before you index them in a vector store or send them to an LLM.
Exports of support tickets, chat logs or CRM notes: run them through
mode: "redact"first, call your model, then put the real values back withmode: "restore".
Troubleshooting
Symptom | Cause and fix |
Run ends with only an | No |
| The URL was unreachable, over 10 MB or not a supported type. These rows are free. Scanned PDFs have no text layer, and there is no OCR. |
| Pass the |
Too many findings (false positives) | Add known-safe values to |
Names or addresses not detected |
|
FAQ
Is document content stored?
Content is never logged. Results go only to your run's dataset, RUN_SUMMARY holds counts only, and the INPUT record is deleted at the end by default (purgeInputAfterRun). Delete the dataset when you no longer need it.
Can I get the original values back after the LLM answers?
Yes. Use redactionStyle: "placeholder" with returnPlaceholderMap: true, then run mode: "restore" with that map.
Does it catch everything?
No. It is a pattern-and-validation engine (checksums, issuer prefixes, Gitleaks-style rules). Unusual formats, non-English names and images can slip through. Use it as one layer of defence.
How much does it cost?
$0.0015 per document plus $0.0002 per finding, and a $0.00005 start fee. Error rows are free.
Can I scan text without sending it to Apify?
Yes. The Fairquery MCP server (npx -y @fairquery/agent-trust-mcp) has free pii_scan_text and pii_redact_text tools that run locally. This Actor adds names and addresses, URLs, PDFs and restore mode.
Fairquery family
Other Fairquery tools on the Apify Store. Each one returns structured rows with machine-readable error codes, and error rows are never charged:
Email Validator for Agents (MX, Disposable, Typo): syntax, MX, disposable, role and typo checks without sending mail; $0.79 per 1,000 emails
Website Contact & Company Enricher: domain in, public business emails, phones, socials and company details out; $3 per 1,000 domains
Website Tech, SEO & llms.txt Check for Agents: tech stack, SEO score, AI-crawler rules and llms.txt per site; $1.50 per 1,000 sites
Sanctions & Watchlist Screener (OFAC, EU, UK, UN): fuzzy name screening against official sanctions lists; $3 per 1,000 names + $0.007 per run
Dependency Vulnerability Check for Agents: packages and lockfiles against OSV.dev; $0.50 per 1,000 packages + $1.50 per 1,000 vulnerabilities
EDGAR Company Facts & Filings for Agents: SEC filings and XBRL facts by ticker, CIK or name; from $1.50 per 1,000 companies
Company Jobs Feed: Greenhouse, Lever & Ashby: live jobs from employers' own ATS boards with age and repost signals; $0.99 per 1,000 jobs + $0.0005 per board
Competitor Change Tracker: Pricing, Terms, Careers & Changelog: watch competitors' public pricing, terms, careers and changelog pages for real changes with plain-English summaries and webhook alerts; $0.00049 per page checked + $0.0049 per change
AI Exposure Scanner: Public Secrets, Files & AI-Crawler Policy: passive, scored scan of your own domains and public GitHub repos for leaked secrets (masked), exposed files, AI-crawler policy, security headers and SPF/DMARC; $0.039 per domain + $0.0039 per repo + $0.0019 per finding
MCP server
@fairquery/agent-trust-mcp: one MCP server for Claude Desktop, Cursor and VS Code (npx -y @fairquery/agent-trust-mcp). It has 8 free tools that need no token, plus paid tools that call the PII, email, sanctions and OSV Actors with your own Apify token.
All Fairquery Actors: https://apify.com/fairquery
Kits & templates
Optional guides from Fairquery for wiring this Actor into agents and automations (the Actor works fine without them):
AI Agent Data Toolkit Quick-Start (PDF): MCP setup for Claude Desktop and Cursor, API calls, recipes, cost estimates.
n8n + Zapier Workflow Template Pack: 10 importable n8n workflows for the four original Fairquery Actors (SEC EDGAR, website/tech, email, OSV); it does not include workflows for this Actor yet, but the patterns adapt easily.
Agent Prompt Pack for Research & Lead Ops: 69 prompts for company research, lead cleaning, SEO and security reviews.
Agent Starter Kit: 10 copy-paste MCP + n8n recipes ($5): 10 copy-paste MCP + n8n recipes, with ready-made Claude/Cursor configs and 3 starter workflows.
Disclaimer
Pattern-based detection finds most but not all sensitive data: unusual formats, non-English names, images and scanned PDFs (no OCR) can slip through, and some matches are false positives. Use it as one layer of a data-protection program, not as a compliance certification. Only process data you are allowed to process.
Links
Apify Store: https://apify.com/fairquery/llm-data-guard-pii-secrets
GitHub: https://github.com/fairquery/llm-data-guard-pii-secrets
Contact: info@getfairquery.com
License: MIT
Development
Local development, tests and deployment notes are in DEVELOPMENT.md. The Apify Store README is .actor/README.md.
Available Tools
4 toolsget_run_resultsPoll a background guard_text run (free read)ARead-onlyIdempotentInspect
WHAT: Read-only follow-up to guard_text(waitForFinish=false): returns the run status and one page of its result rows. It scans nothing itself. WHEN TO USE: only after guard_text returned run.run_id (large batches, many documentUrls or PDFs); poll until run.finished is true and page with nextOffset. Rows already written are returned while the run is RUNNING. WHEN NOT TO USE: to scan or redact (guard_text; a synchronous guard_text call already returns its rows, so there is nothing to poll). To restore placeholders in the rows' output_text after the LLM step, restore_placeholders (free). Cannot read runs of other Actors. BEHAVIOR: GET requests to your Apify account only (actor, run, dataset); never starts, aborts or re-bills a run. Typically <1 s; poll every 10-30 s. To stop a run early, abort it in the Apify console (run.console_url); this server has no abort tool. COST: free; needs the APIFY_TOKEN that started the run. The run was billed once, per document and finding, when it ran. RETURNS: run.status is READY | RUNNING | SUCCEEDED | FAILED | ABORTED | TIMED-OUT; finished=true for the last four, after which total no longer changes (FAILED/TIMED-OUT runs still return the rows they wrote). Rows are exactly the ones guard_text returns, including placeholder_map with ORIGINAL values if requested. Errors: APIFY_TOKEN_MISSING, RUN_NOT_FOUND, RUN_NOT_FOR_THIS_ACTOR, APIFY_TOKEN_INVALID, APIFY_RUN_LOOKUP_FAILED / APIFY_DATASET_READ_FAILED (retryable). PARAMETERS: runId comes from guard_text run.run_id. Pages are in write order; total counts rows written so far, so keep calling with nextOffset until it is null and finished is true. EXAMPLE: {"runId":"a1B2c3D4e5F6g7H8i"} -> {"run":{"status":"SUCCEEDED","finished":true},"count":1,"total":1,"items":[{"type":"document","risk_level":"high",...}]}
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Optional page size, integer 1-1000, default 100. Example: 250 | |
| runId | Yes | Required. Apify run ID (letters and digits) from guard_text run.run_id. Example: "a1B2c3D4e5F6g7H8i" | |
| offset | No | Optional integer >= 0, default 0; use nextOffset from the previous page. Example: 100 |
Output Schema
| Name | Required | Description |
|---|---|---|
| run | Yes | run_id, status, finished, dataset_id, started_at, finished_at, console_url |
| count | Yes | Rows in this page |
| items | Yes | Dataset rows: type=document or type=error |
| total | Yes | Rows written by the run so far |
| nextOffset | Yes | Offset of the next page, or null |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent/openWorld annotations: discloses that it only issues GETs and 'never starts, aborts or re-bills a run', latency (<1 s) and poll cadence (10-30 s), that there is no abort tool and aborts happen in the Apify console, cost/auth requirements, terminal status semantics, and named error codes including retryability. This is exactly the extra context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with labeled sections (WHAT/WHEN TO USE/WHEN NOT TO USE/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE) that make it scannable, and every block earns its place. It is nonetheless long and verbose relative to a 3-parameter read tool, with some redundancy between WHAT, RETURNS and PARAMETERS.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema existing, the description still supplies the status enumeration, the finished-semantics for terminal states, partial-results-while-RUNNING behavior, the full error set, and a worked example. Nothing an agent needs to poll correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: runId's provenance ('comes from guard_text run.run_id'), page ordering ('in write order'), and the pagination contract ('keep calling with nextOffset until it is null and finished is true'). It doesn't restate limit/offset types, which the schema already covers.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read-only follow-up to guard_text... returns the run status and one page of its result rows') and immediately scopes what it does not do ('It scans nothing itself'). An agent can distinguish it from guard_text, restore_placeholders and list_detectors without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit WHEN TO USE ('only after guard_text returned run.run_id... poll until run.finished is true') and WHEN NOT TO USE with named alternatives ('to scan or redact (guard_text...)... To restore placeholders... restore_placeholders'). It also states a hard boundary ('Cannot read runs of other Actors'), leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
guard_textDetect / redact PII and secrets before LLM use (paid run)ARead-onlyIdempotentInspect
WHAT: The only tool in this server that inspects content. Scans text documents (or public document URLs: txt/md/json/csv/HTML/PDF) for PII (emails, phones, US SSNs, Luhn-valid cards, IBANs, UK NINOs, IPs, optional person names/addresses) and secrets (AWS/GitHub/Slack/Stripe/OpenAI/... keys, JWTs, private keys, URL credentials, high-entropy key=value). mode="detect" reports findings; mode="redact" (default) also returns sanitized text and, on request, the placeholder map that restore_placeholders needs later. WHEN TO USE: before sending prompts, tickets, logs or files to an LLM or third party; to audit text for leaked credentials. Step 2 of the round trip (list_detectors -> guard_text -> [get_run_results] -> LLM -> restore_placeholders). WHEN NOT TO USE: to put originals back into an LLM answer, use restore_placeholders (free, local; mode="restore" here gives the same output but starts a paid run and is only kept for Actor pipelines outside MCP). To see entity types, list_detectors (free). To fetch rows of a run you already started, get_run_results (free; calling guard_text again starts and bills a new run). Not for images/scanned PDFs (text layer only, no OCR) or email deliverability. COST: paid. Runs Apify Actor fairquery/llm-data-guard-pii-secrets with your APIFY_TOKEN; pay-per-event: $0.0015 per document processed + $0.0002 per finding (error rows free). Example: 10 documents with 20 findings ~ $0.02. RETURNS: findings[] hold masked previews only, never raw values; placeholder_map (ORIGINAL values) only with returnPlaceholderMap=true. With waitForFinish=false: items is empty and run.run_id is the input for get_run_results. EXAMPLE: {"texts":["Mail jane@acme.com, key AKIAIOSFODNN7EXAMPLE"],"mode":"redact","redactionStyle":"placeholder"} -> output_text with the email replaced by [EMAIL_1] and the key by a placeholder; findings lists EMAIL and AWS_ACCESS_KEY. PARAMETERS: texts and/or documentUrls: at least one is required although the schema marks both optional (neither -> a free INPUT_EMPTY error row); both may be combined, and every text or URL is one billed document (max 1000 per run; extras get a free MAX_DOCUMENTS_EXCEEDED row). mode decides which other fields apply: "detect" ignores redactionStyle / returnPlaceholderMap; "redact" uses them; "restore" ignores all detector options and only replaces placeholders found in texts with values from placeholderMap (a placeholder missing from the map stays unchanged; restore rows have 0 findings, so they cost the per-document fee only). returnPlaceholderMap only works with mode="redact" + redactionStyle="placeholder" and is what restore_placeholders consumes; placeholders are consistent across all documents of one call ([EMAIL_1] is the same address everywhere) but numbering restarts on every call, so keep each call's map. detectNames / detectAddresses must be true before PERSON / LOCATION / STREET_ADDRESS can be found or used in entityTypes. entityTypes filters findings after detection (unknown names match nothing, no error) and is the main lever to cut per-finding cost. waitForFinish=false returns at once with run.run_id while the run continues in your Apify account (same price, billed once); use it for big batches or many URLs/PDFs that may not finish within this call's 280 s wait, then poll get_run_results with that run_id.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Optional, one of "detect" | "redact" | "restore", default "redact". "detect" = report findings only; "redact" = also return output_text with values replaced; "restore" = replace placeholders in texts with originals from placeholderMap (paid run; restore_placeholders does the same for free). Example: "detect" | |
| texts | No | Optional if documentUrls is given (at least one of the two is required). One string per document (prompt, chat message, ticket, log, file content), <= 1,000,000 chars each (longer is truncated); one billed document each. Example: ["Hi, I am Jane (jane.doe@acme.com)"] | |
| allowList | No | Optional, default []. Exact values that are never flagged (whole value, case-insensitive; not substrings). Example: ["support@yourcompany.com"] | |
| detectNames | No | Optional boolean, default false. true detects person names (type PERSON) with English NER (lower precision, slightly slower; each finding billed). | |
| entityTypes | No | Optional, default omitted/[] = all enabled detectors. Entity type names (case-insensitive) from list_detectors, without the "(opt-in)" notes. Example: ["EMAIL","CREDIT_CARD","AWS_ACCESS_KEY"] | |
| entropyScan | No | Optional boolean, default false. true also flags unlabeled random-looking tokens of 20+ chars as HIGH_ENTROPY_STRING (catches unknown key formats; may flag hashes/IDs). | |
| documentUrls | No | Optional if texts is given. Absolute public http(s) URLs of txt, md, json, csv, HTML or PDF files (max 10 MB each; PDFs text layer only, no OCR), fetched by the Actor; unreachable URLs give free DOCUMENT_FETCH_FAILED rows. Example: ["https://example.com/report.pdf"] | |
| waitForFinish | No | Optional boolean, default true. true waits up to 280 s and returns the rows; false starts the run and returns immediately with run.run_id (poll get_run_results). Same price either way. Example: false | |
| placeholderMap | No | Optional, default {}; used only with mode="restore". Object mapping placeholder -> original value, as returned in placeholder_map by a previous redact call. Example: {"[EMAIL_1]":"jane@acme.com"} | |
| redactionStyle | No | Optional, one of "placeholder" | "mask" | "hash", default "placeholder"; used in mode="redact" only. "placeholder" = [EMAIL_1], reversible via placeholder map; "mask" = j***@a***.com / **** 1111; "hash" = [EMAIL:3f2a9c0d1b7e] salted sha256 prefix (random salt per run). | |
| detectAddresses | No | Optional boolean, default false. true detects street addresses (STREET_ADDRESS, English formats) and NER place names (LOCATION). | |
| defaultPhoneRegion | No | Optional, default "US". ISO 3166-1 alpha-2 country code (exactly 2 letters, case-insensitive) used to parse phone numbers written without a +country prefix. Examples: "US", "DE", "GB" | |
| returnPlaceholderMap | No | Optional boolean, default false. true (mode="redact" + redactionStyle="placeholder" only) adds placeholder_map {"[EMAIL_1]":"jane@acme.com"} to each row; pass it to restore_placeholders after the LLM step. Contains the ORIGINAL sensitive values. |
Output Schema
| Name | Required | Description |
|---|---|---|
| run | No | Only with waitForFinish=false: run_id, status, dataset_id, console_url |
| count | Yes | Number of rows in items |
| items | Yes | Dataset rows: type=document (findings, risk_level, output_text) or type=error |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint/idempotentHint/destructiveHint=false, and the description adds the operational context that matters: it is a paid Apify Actor run with per-document and per-finding pricing, error rows are free, a 1000-document cap, a 280s wait, and exactly what each call bills. It also discloses that placeholder_map carries ORIGINAL sensitive values and that numbering restarts per call, which is the kind of safety-relevant behavior structured fields cannot express. (Minor tension: 'random salt per run' in hash mode means repeated calls differ, but this concerns return values rather than state mutation, so it does not amount to a contradiction of idempotentHint.)
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Headers (WHAT/WHEN TO USE/WHEN NOT TO USE/COST/RETURNS/EXAMPLE/PARAMETERS) make a long body navigable and front-load the identity of the tool. The length is largely justified by 13 parameters and a paid pipeline, but there is some repetition (the restore 'same output for free' caveat and billing behavior are restated more than once), which keeps it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter, nested-object, paid tool with an async path, the description covers everything an agent needs: cost model, batching limits, the waitForFinish=false polling handoff to get_run_results, error-row semantics, and the round-trip contract with restore_placeholders. An output schema exists, yet the RETURNS note still flags the critical distinction between masked findings and the original-value placeholder map.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema description coverage is 100%, the description adds the cross-parameter logic the schema cannot: at least one of texts/documentUrls is required despite both being optional, mode gates which other fields apply, returnPlaceholderMap only functions with redact+placeholder, detectNames/detectAddresses must be true before PERSON/LOCATION/STREET_ADDRESS can appear in entityTypes, and entityTypes filters post-detection (unknown names silently match nothing). This meaningfully exceeds the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT section names a specific verb (scans/detects/redacts) and resource (text documents and public URLs) and enumerates the entity classes it covers. It explicitly positions itself against siblings ('the only tool in this server that inspects content') and routes to list_detectors, restore_placeholders, and get_run_results by name, so an agent can distinguish it without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Dedicated WHEN TO USE and WHEN NOT TO USE blocks name the exact scenarios (before sending prompts/tickets/logs to an LLM) and the precise alternatives for each edge case: restore_placeholders for putting originals back, list_detectors for entity types, get_run_results to avoid re-billing. It even calls out non-goals (no OCR, not for email deliverability).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_detectorsList detectable types and redaction styles (free, local)ARead-onlyIdempotentInspect
WHAT: Reference data, not a scan: the PII and secret entity types guard_text can detect, its redaction styles and its billable events. Inspects no text. WHEN TO USE: optional step 1, before guard_text, to choose entityTypes / redactionStyle, to estimate guard_text cost, or to explain coverage to a user. WHEN NOT TO USE: to check whether a given text contains PII or secrets (guard_text; this catalog cannot tell you that). Not needed before every guard_text call: guard_text runs all default detectors when entityTypes is omitted. To undo a redaction, restore_placeholders. BEHAVIOR: the catalog ships with this server version and is not fetched from the Actor, so a newer Actor build can support types that an older server version does not list. COST: free; no APIFY_TOKEN. RETURNS: pii / secret entries are valid guard_text entityTypes once the parenthesised note is dropped ("CREDIT_CARD (Luhn)" -> "CREDIT_CARD"); "opt-in" entries need guard_text detectNames / detectAddresses / entropyScan; redaction_styles keys are the allowed redactionStyle values (only "placeholder" can be undone with restore_placeholders). PARAMETERS: none; call with {}. EXAMPLE: {} -> {"pii":["EMAIL","PHONE",...],"secret":["AWS_ACCESS_KEY",...],...}
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| pii | Yes | PII entity types (opt-in ones marked) |
| secret | Yes | Secret/credential entity types |
| pricing_events | Yes | Billable Apify event -> unit |
| redaction_styles | Yes | Redaction style -> example output |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), yet the description adds substantial non-obvious context: the catalog is version-pinned to the server rather than fetched from the Actor, so newer Actor builds may support unlisted types; it is free and needs no APIFY_TOKEN. This is exactly the kind of behavioral detail annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Heavy but front-loaded and labelled (WHAT / WHEN / WHEN NOT / BEHAVIOR / COST / RETURNS / PARAMETERS / EXAMPLE), so an agent can scan to the needed section. The RETURNS block is somewhat long for a tool that has an output schema, though its guidance on stripping parenthesised notes and the 'opt-in' flags is genuinely actionable rather than padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only catalog with a full output schema, the description supplies everything else an agent needs: cost, token requirements, version-skew caveat, and how to translate returned entries into valid guard_text arguments. Nothing required to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4; schema coverage is 100% and the description confirms 'none; call with {}'. It adds no per-parameter meaning because none exists, but the call convention is stated unambiguously.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource — the catalog of PII/secret entity types and redaction styles — and immediately disambiguates it from scanning ('Reference data, not a scan... Inspects no text'). The distinction from guard_text is explicit, so an agent can route correctly without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Dedicated WHEN TO USE and WHEN NOT TO USE sections name the concrete triggers (choose entityTypes/redactionStyle, estimate cost, explain coverage) and the anti-triggers (checking text → guard_text; undoing redaction → restore_placeholders; not needed before every guard_text call since defaults apply). Alternatives are named, not implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
restore_placeholdersRestore placeholders with original values (free, local)ARead-onlyIdempotentInspect
WHAT: The last step of the round trip and the default way to restore: puts original values back into texts (typically an LLM answer) that contain placeholders such as [EMAIL_1], using the placeholder_map from a guard_text redact call. Does no detection. WHEN TO USE: after the LLM answered a prompt you redacted with guard_text (mode="redact", redactionStyle="placeholder", returnPlaceholderMap=true), before showing the answer to the user. Prefer it over guard_text mode="restore": same output_text, but free and local. WHEN NOT TO USE: to find or redact PII/secrets (guard_text) or list entity types (list_detectors). Masked and hashed values cannot be restored; only placeholder style is reversible. BEHAVIOR: no network call; the map never leaves your machine; milliseconds. Up to 1000 texts and 5,000,000 chars in total per call. COST: free; no APIFY_TOKEN. RETURNS: replacements counts substituted placeholders; unknown_placeholders lists [TYPE_N] tokens the map does not cover (left as is, e.g. invented by the LLM). Errors: INPUT_TOO_LARGE. The output holds the ORIGINAL sensitive values: show it to the user, do not send it back to the LLM. PARAMETERS: placeholderMap must be the map of the redact call that produced the placeholders (numbering restarts on every call); only [UPPERCASE_TYPE_N] keys are used (exact, case-sensitive, one pass). A placeholder the LLM rewrote (e.g. "EMAIL_1" without brackets) is not restored. EXAMPLE: {"texts":["I will email [EMAIL_1] today."],"placeholderMap":{"[EMAIL_1]":"jane@acme.com"}} -> output_text "I will email jane@acme.com today.", replacements 1.
| Name | Required | Description | Default |
|---|---|---|---|
| texts | Yes | Required. 1-1000 texts containing placeholders, 5,000,000 chars in total. Example: ["I will email [EMAIL_1] today."] | |
| placeholderMap | Yes | Required. Object placeholder -> original value, exactly as returned in placeholder_map by a guard_text redact call. Example: {"[EMAIL_1]":"jane@acme.com"} |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | Number of rows in items |
| items | Yes | type=restored_document rows (doc_index, chars, replacements, unknown_placeholders, output_text) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond annotations: discloses no network call, the map never leaves the machine, millisecond latency, size limits (1000 texts / 5,000,000 chars), cost (free, no APIFY_TOKEN), what cannot be restored (masked/hashed), and a critical safety caveat that output contains original sensitive values and must not be sent back to the LLM. Annotations already say readOnly/idempotent/non-destructive; description adds local-only execution and data-handling warnings.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded WHAT/WHEN TO USE/WHEN NOT TO USE/BEHAVIOR/COST/RETURNS/PARAMETERS/EXAMPLE headings make it scannable. Despite length, each section carries distinct, actionable content with no filler; the example is compact and illustrative.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers everything an agent needs: correct sequencing, alternatives, local-only guarantee, limits, cost, return fields (replacements, unknown_placeholders), the INPUT_TOO_LARGE error, and the sensitive-output handling rule. With an output schema present, the description still adds the 'do not send back to LLM' caveat that the schema cannot express.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the description still adds meaning: placeholderMap must come from the exact redact call that produced the placeholders, numbering restarts per call, only [UPPERCASE_TYPE_N] keys within brackets are used, matching is exact, case-sensitive, and single-pass, and a rewritten placeholder without brackets is not restored. These are behavioral semantics not captured by the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The WHAT section states a specific verb (restore/put back), resource (original values into texts with placeholders), and positions it as 'the last step of the round trip and the default way to restore'. It explicitly names the counterpart guard_text and its mode, so an agent can distinguish it from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit WHEN TO USE (after guard_text redact with placeholder style, before showing user), WHEN NOT TO USE (finding/redacting PII -> guard_text, listing types -> list_detectors), and a preference rule (prefer over guard_text mode=restore because free and local). This is textbook when/when-not/alternatives guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v1.1.1- Changed
guard_text2 fields changed- changed
Input schema / properties / mode / descriptionPrevious value: -"Optional, one of \"detect\" | \"redact\" | \"restore\", default \"redact\". \"detect\" = report findings only; \"redact\" = also return output_text with values replaced; \"restore\" = replace placeholders in texts with originals from placeholderMap. Example: \"detect\""New value: +"Optional, one of \"detect\" | \"redact\" | \"restore\", default \"redact\". \"detect\" = report findings only; \"redact\" = also return output_text with values replaced; \"restore\" = replace placeholders in texts with originals from placeholderMap (paid run; restore_placeholders does the same for free). Example: \"detect\"" - changed
Input schema / properties / returnPlaceholderMap / descriptionPrevious value: -"Optional boolean, default false. true (mode=\"redact\" + redactionStyle=\"placeholder\" only) adds placeholder_map {\"[EMAIL_1]\":\"jane@acme.com\"} to each row, needed for restore mode. Contains the ORIGINAL sensitive values."New value: +"Optional boolean, default false. true (mode=\"redact\" + redactionStyle=\"placeholder\" only) adds placeholder_map {\"[EMAIL_1]\":\"jane@acme.com\"} to each row; pass it to restore_placeholders after the LLM step. Contains the ORIGINAL sensitive values."
3 tool updates
v1.1.0- Added
get_run_results - Changed
guard_text2 fields changed- added
Input schema / properties / waitForFinishAdded value: +{ + "description": "Optional boolean, default true. true waits up to 280 s and returns the rows; false starts the run and returns immediately with run.run_id (poll get_run_results). Same price either way. Example: false", + "type": "boolean" +} - added
Output schema / properties / runAdded value: +{ + "additionalProperties": {}, + "description": "Only with waitForFinish=false: run_id, status, dataset_id, console_url", + "type": "object" +}
- Added
restore_placeholders
1 tool update
- Changed
guard_text12 fields changed- changed
Input schema / properties / allowList / descriptionPrevious value: -"Exact values that are never flagged, e.g. [\"support@yourcompany.com\"]. Default []."New value: +"Optional, default []. Exact values that are never flagged (whole value, case-insensitive; not substrings). Example: [\"support@yourcompany.com\"]" - changed
Input schema / properties / defaultPhoneRegion / descriptionPrevious value: -"ISO 3166-1 alpha-2 country code used to parse phone numbers written without +country prefix, e.g. \"US\" (default), \"DE\", \"GB\"."New value: +"Optional, default \"US\". ISO 3166-1 alpha-2 country code (exactly 2 letters, case-insensitive) used to parse phone numbers written without a +country prefix. Examples: \"US\", \"DE\", \"GB\"" - changed
Input schema / properties / detectAddresses / descriptionPrevious value: -"Default false. Detect street addresses (English formats) and NER place names."New value: +"Optional boolean, default false. true detects street addresses (STREET_ADDRESS, English formats) and NER place names (LOCATION)." - changed
Input schema / properties / detectNames / descriptionPrevious value: -"Default false. Detect person names with English NER (lower precision, slightly slower)."New value: +"Optional boolean, default false. true detects person names (type PERSON) with English NER (lower precision, slightly slower; each finding billed)." - changed
Input schema / properties / documentUrls / descriptionPrevious value: -"Public http(s) URLs of txt, md, json, csv, HTML or PDF files (max 10 MB each; PDFs text layer only). Example: [\"https://example.com/report.pdf\"]. Optional if texts is given."New value: +"Optional if texts is given. Absolute public http(s) URLs of txt, md, json, csv, HTML or PDF files (max 10 MB each; PDFs text layer only, no OCR), fetched by the Actor; unreachable URLs give free DOCUMENT_FETCH_FAILED rows. Example: [\"https://example.com/report.pdf\"]" - changed
Input schema / properties / entityTypes / descriptionPrevious value: -"Only report these entity types, e.g. [\"EMAIL\",\"CREDIT_CARD\",\"AWS_ACCESS_KEY\"]. Omit/empty = all enabled detectors. Valid names: see list_detectors."New value: +"Optional, default omitted/[] = all enabled detectors. Entity type names (case-insensitive) from list_detectors, without the \"(opt-in)\" notes. Example: [\"EMAIL\",\"CREDIT_CARD\",\"AWS_ACCESS_KEY\"]" - changed
Input schema / properties / entropyScan / descriptionPrevious value: -"Default false. Also flag unlabeled random-looking tokens of 20+ chars (catches unknown key formats; may flag hashes/IDs)."New value: +"Optional boolean, default false. true also flags unlabeled random-looking tokens of 20+ chars as HIGH_ENTROPY_STRING (catches unknown key formats; may flag hashes/IDs)." - changed
Input schema / properties / mode / descriptionPrevious value: -"\"detect\" = report findings only; \"redact\" (default) = also return output_text with values replaced; \"restore\" = replace placeholders in texts with originals from placeholderMap."New value: +"Optional, one of \"detect\" | \"redact\" | \"restore\", default \"redact\". \"detect\" = report findings only; \"redact\" = also return output_text with values replaced; \"restore\" = replace placeholders in texts with originals from placeholderMap. Example: \"detect\"" - changed
Input schema / properties / placeholderMap / descriptionPrevious value: -"Only for mode=\"restore\": map of placeholder -> original value returned by a previous redact call, e.g. {\"[EMAIL_1]\":\"jane@acme.com\"}. Default {}."New value: +"Optional, default {}; used only with mode=\"restore\". Object mapping placeholder -> original value, as returned in placeholder_map by a previous redact call. Example: {\"[EMAIL_1]\":\"jane@acme.com\"}" - changed
Input schema / properties / redactionStyle / descriptionPrevious value: -"Used in redact mode. \"placeholder\" (default) = [EMAIL_1], reversible via placeholder map; \"mask\" = j***@a***.com / **** 1111; \"hash\" = [EMAIL:3f2a9c0d1b7e] salted sha256 prefix."New value: +"Optional, one of \"placeholder\" | \"mask\" | \"hash\", default \"placeholder\"; used in mode=\"redact\" only. \"placeholder\" = [EMAIL_1], reversible via placeholder map; \"mask\" = j***@a***.com / **** 1111; \"hash\" = [EMAIL:3f2a9c0d1b7e] salted sha256 prefix (random salt per run)." - changed
Input schema / properties / returnPlaceholderMap / descriptionPrevious value: -"Default false. If true, each row includes placeholder_map {\"[EMAIL_1]\":\"jane@acme.com\"} needed for restore mode. Contains the original sensitive values."New value: +"Optional boolean, default false. true (mode=\"redact\" + redactionStyle=\"placeholder\" only) adds placeholder_map {\"[EMAIL_1]\":\"jane@acme.com\"} to each row, needed for restore mode. Contains the ORIGINAL sensitive values." - changed
Input schema / properties / texts / descriptionPrevious value: -"Documents to scan, one string per document (prompt, chat message, ticket, log line, file content). Example: [\"Hi, I am Jane (jane.doe@acme.com)\"]. Optional if documentUrls is given."New value: +"Optional if documentUrls is given (at least one of the two is required). One string per document (prompt, chat message, ticket, log, file content), <= 1,000,000 chars each (longer is truncated); one billed document each. Example: [\"Hi, I am Jane (jane.doe@acme.com)\"]"
2 tool updates
v0.1.0- First observed
guard_text - First observed
list_detectors
TDQS
Scored across 4 tools
Each tool has a distinct role (scan, poll, list reference types, restore), and descriptions explicitly tell agents when to use one over another. The only real overlap is guard_text mode="restore" versus restore_placeholders, but the description flags this and steers agents to the free tool.
All four names follow a clean verb_noun snake_case pattern (guard_text, get_run_results, list_detectors, restore_placeholders). No casing or verb-style deviations.
Four tools is on the lean side but each maps to a distinct stage of the redact/restore round trip, so nothing feels redundant. It is well-scoped for the domain, though there is little slack.
The lifecycle (discover types -> detect/redact -> poll async run -> restore) is fully covered, including an async path and a free local restore. Minor gaps exist: no abort/run-management tool (explicitly deferred to the Apify console) and no OCR/image handling.
Maintenance
Related MCP Connectors
Detect and redact PII and secrets before text reaches an LLM, with reversible placeholders.
Find and redact leaked secrets, wallet keys and PII before an AI agent sends text.
Detects and redacts PII (emails, phones, SSNs, names, addresses) from text. $0.02/call via x402.
Redact personal data from text before your agent sends it to a model or stores it: names, contact details, ID numbers, financial and health identifiers, card numbers, plus any exact terms you add. Nothing stored, no third-party AI. Tools - redact_text: returns the redacted text and a count of what was removed, by type. Never the removed values. Options: restrict to particular data types, add exact terms to remove, choose the replacement style (a type label such as <PERSON> by default) and set a confidence threshold. - list_entity_types: lists the detectable data types. Free. Data handling - Nothing you send is stored. Text is processed in memory and discarded when the call returns. - No third-party AI. Text is never sent to OpenAI, Anthropic, Google or any other outside model. - Tool arguments are never logged. Only counts are kept: credits used, a timestamp and how many of each type were found. PDFs, including scanned pages, are handled by the REST API with the same key and credits. They aren't offered as an MCP tool because a file doesn't fit in a tool call. Pricing: 1 credit per call plus 1 per 1,000 characters. 10 free credits on sign-up, no card. Credit packs from $5; purchased credits never expire. Failed calls and repeated identical calls are free. Setup and API key: https://eleyed.com/mcp?utm_source=glama&utm_medium=directory&utm_campaign=listing
Related MCP Servers
- FlicenseAqualityBmaintenancePII sanitization layer for autonomous AI agent pipelines. Detects and redacts emails, phone numbers, national IDs, private keys, and financial data before text reaches LLMs. Supports EN, ES (LATAM), PT (BR/PT), DE, JA.12-

classifinder-mcpofficial
AlicenseAqualityCmaintenanceEnables AI agents to scan text for leaked secrets and prompt injection markers, and redact them before reaching an LLM.21MIT- AlicenseAqualityDmaintenanceScans prompts for PII and masks or redacts sensitive data locally before sending to an LLM, supporting multiple anonymization modes.1MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to redact PII from text, summarize redacted content, and manage custom redaction patterns across multiple languages.-