data-filter-mcp
Summary: data-filter-mcp is a local MCP server that registers AST-validated Python filter functions in memory and runs them against local JSON, YAML, and TXT files to return or persist transformed text.
Register filters (
register_filter): submit Python source defining exactly onedef filter_item(data):; the server validates it against a restricted subset and returns afilter_id, expiry timestamp, TTL, and policy version.Run filters inline (
run_filter): load a local json/yaml/txt file (auto-detected or overridden viafile_type), pass the parsed document to the filter, and get back the exactresult_textalong with resolved path, type, and expiry.Convert/write files (
convert_file): apply a filter to a source file and save the returned string as UTF-8 to a destination path, creating missing parent directories and reportingbytes_writtenandoverwritten.Work with multiple input types: JSON → parsed value, YAML → parsed value, TXT → list of lines.
Use preloaded standard-library modules without importing:
json,yaml(safe only),re,math,statistics,datetime,decimal,collections,itertools,functools,operator,textwrap,html,base64,hashlib,ipaddress,unicodedata,difflib, plus curated builtins and safe string/dict/list methods.Express rich logic: lambdas (e.g.
sorted(..., key=lambda ...)),next(generator),assertstatements, and catchingAssertionError.Restrict filesystem access with one or more
--workdirflags (absolute existing directories);run_filterreads only inside them, andconvert_filerequires at least one workdir and writes only inside it.Control overwrites:
convert_filerefuses to replace an existing destination unlessoverwrite: true.Benefit from automatic expiry: filters live only in memory and expire per server TTL, cleaned up on a configured interval.
Enjoy safety guarantees: no imports, no filesystem/network/process/env access, no dynamic execution (
eval/exec/__import__), no unsafe modules (os,subprocess,pickle,yaml.load,lru_cache,attrgetter, etc.).Deploy easily: run via
uvx data-filter-mcpwith CLI flags (TTL, cleanup interval, workdirs), viapython server.py,python -m data_filter_mcp.server, or the installed.venv/bin/data-filter-mcpentry point, and configure it in an MCP client'smcpServersblock.
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., "@data-filter-mcpFilter data.json where active is True"
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.
data-filter-mcp
Local MCP server that registers restricted Python filters and runs them against local json, yaml, and txt files.
What it does
register_filteraccepts Python source code with exactly one top-level function:def filter_item(data):run_filterloads a local file, passes the loaded document intofilter_item(data), and returns the text fromresult_textconvert_fileloads a local file, passes it intofilter_item(data), and writes the returned text to another local fileRegistered filters live only in memory and expire automatically based on server TTL settings
What filter code may use
Allowed modules are preloaded into the filter namespace. Redundant import json,
import math, re, and import datetime as dt statements are accepted at module
level and inside functions. They are removed before validation and execution
(aliases remain available); no actual imports are executed. Imports of other
modules, dotted module names, and all from ... import ... forms remain rejected.
All other validation rules still apply, including the single top-level function
requirement. Imports are unnecessary; prefer using the preloaded canonical names.
Filter bodies are AST-validated against a whitelist. In addition to a curated set of builtins (len, sorted, max, min, next, range, enumerate, zip, sum, any, all, conversions, etc.) and safe string/dict/list methods, filters may also use a curated set of standard-library modules. Modules are exposed by their canonical names (math, datetime, hashlib, etc.). Filesystem, process, network, and unsafe serialization modules (os, pathlib, shutil, subprocess, socket, urllib, pickle, etc.) are intentionally not available.
Filters support next(generator) and next(generator, default), as well as assert statements (with optional messages) and catching AssertionError. Assertions are disabled when the server runs with Python optimization (-O or -OO); do not use optimized mode when relying on assertions for validation.
lambdaexpressions — typically askey=arguments, e.g.sorted(data, key=lambda item: item.get("score")). Lambda bodies are validated by the same rules as the rest of the filter.json—json.loads,json.dumps.yaml—yaml.safe_load,yaml.safe_dump. The unsafeyaml.load/yaml.dumpare intentionally not exposed.re—re.match,re.search,re.fullmatch,re.findall,re.sub,re.subn,re.compile,re.escape, plusMatch/Patternmethods (group,groups,groupdict,start,end,span).math— numeric helpers such asmath.ceil,math.floor,math.sqrt,math.log,math.exp,math.pow,math.factorial,math.gcd,math.lcm,math.isfinite,math.isclose.statistics— aggregates such asstatistics.mean,statistics.median,statistics.stdev,statistics.variance,statistics.quantiles.datetime—datetime.datetime.fromisoformat,datetime.datetime.now,datetime.timedelta,datetime.timezone.utc, and instance methods such asisoformat,strftime,timestamp,weekday,total_seconds. General instance attribute reads such asdt.yearanddt.monthare not supported by the current policy.decimal—decimal.Decimal(...),quantize,normalize,to_eng_string,to_integral_value.collections—collections.Counter,collections.defaultdict,collections.OrderedDict,collections.deque, plus methods such asmost_common,elements,popleft,appendleft,rotate.itertools—chain,chain.from_iterable,islice,takewhile,dropwhile,groupby,starmap,accumulate,combinations,permutations,product,filterfalse.functools—reduce,partial,cmp_to_key,wraps. Caching decorators such aslru_cacheandcacheare intentionally not exposed because they can retain process-local state across filter calls.operator—itemgetter,methodcaller, and arithmetic/comparison helpers such asadd,mul,lt,eq,gt.attrgetteris intentionally not exposed.textwrap—fill,wrap,shorten,indent,dedent.html—html.escape,html.unescape.base64—b64encode,b64decode,urlsafe_b64encode,urlsafe_b64decode,b32encode,b32decode,b16encode,b16decode.hashlib—hashlib.sha256,hashlib.sha1,hashlib.md5,hashlib.blake2b,hashlib.new, plus hash object methods such ashexdigest,digest,update.ipaddress—ip_address,ip_network,ip_interface,IPv4Network,IPv6Network, plus methods such assupernet,subnets,hosts,overlaps,subnet_of,supernet_of. General instance attribute reads such asaddr.is_privateandaddr.compressedare not supported by the current policy.unicodedata—category,name,lookup,numeric,digit,decimal,bidirectional,combining,mirrored.difflib—get_close_matches,ndiff,unified_diff,context_diff,SequenceMatcher.
Note: re.compile runs against patterns supplied by filter code, so a pathological pattern can stall the server (ReDoS). Some helpers such as difflib.SequenceMatcher can also be CPU-heavy on large inputs. Treat filter source as trusted-but-restricted.
Related MCP server: jq-mcp
Run with uvx
After publishing to PyPI, start the server with:
uvx data-filter-mcp --filter-ttl-seconds 3600 --cleanup-interval-seconds 60Show the available CLI flags with:
uvx data-filter-mcp --helpRestricting file access with --workdir
By default the server can read any file on the local filesystem. Use one or
more --workdir flags to restrict file reads to specific directories:
uvx data-filter-mcp \
--filter-ttl-seconds 3600 \
--cleanup-interval-seconds 60 \
--workdir /Users/me/project \
--workdir /tmp/dataRules:
Each
--workdirvalue must be an absolute path to an existing directory.run_filterwill only accept files located inside the allowed directories.If no
--workdirflags are provided, no restrictions are applied (backward compatible).convert_filealways requires at least one--workdirbecause it writes to disk.convert_filerequires the destination path to be inside an allowed workdir.convert_filecreates missing destination parent directories automatically.convert_filerefuses to replace an existing destination file unlessoverwriteistrue.
Writing transformed files with convert_file
Use convert_file when the filtered output should be persisted instead of returned
inline to the model. The tool accepts:
filter_id— an identifier returned byregister_filtersource_file_path— absolute path to the json/yaml/txt file to loaddestination_file_path— absolute path where the returned text should be savedfile_type— optional source file type override (json,yaml, ortxt)overwrite— optional boolean, defaultfalse
Example flow:
def filter_item(data):
return "\n".join(data["items"])Then call convert_file with a source such as /tmp/data/items.json and a
destination such as /tmp/data/out/items.txt. The result is written as UTF-8
text. The returned metadata includes the resolved source and destination paths,
the effective source file type, bytes_written, and whether an existing file was
overwritten.
Example MCP client configuration:
{
"mcpServers": {
"data-filter": {
"command": "uvx",
"args": [
"data-filter-mcp",
"--filter-ttl-seconds",
"3600",
"--cleanup-interval-seconds",
"60",
"--workdir",
"/Users/me/project",
"--workdir",
"/tmp/data"
]
}
}
}Run locally
python server.py --filter-ttl-seconds 3600 --cleanup-interval-seconds 60
python -m data_filter_mcp.server --filter-ttl-seconds 3600 --cleanup-interval-seconds 60
.venv/bin/data-filter-mcp --filter-ttl-seconds 3600 --cleanup-interval-seconds 60Available Tools
3 toolsconvert_fileA
Apply a registered filter to a source file and save the text output.
Use this tool after register_filter when you want to transform a local json, yaml, or txt file and persist the returned string as UTF-8 text. The destination path must be inside a configured --workdir; unlike run_filter, convert_file refuses to write when no --workdir is configured.
Missing destination parent directories are created automatically. Existing destination files are rejected unless overwrite is true.
Args: filter_id: Identifier returned earlier by register_filter. source_file_path: Absolute path to the source file to load. destination_file_path: Absolute path where result text is saved. file_type: Optional explicit source file type override. overwrite: Whether to replace an existing destination file.
Returns: A structured object describing the written file and filter metadata.
Raises: ValueError: If paths are invalid, workdir is missing, the filter is unknown or expired, destination exists without overwrite, or the filter returns a non-string result. FileNotFoundError: If the source file does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| file_type | No | Optional explicit file type override for the source file. If omitted, detected from the source extension. | |
| filter_id | Yes | Identifier previously returned by register_filter. | |
| overwrite | No | If false (default), fail when destination exists. If true, overwrite the existing destination file. | |
| source_file_path | Yes | Absolute path to the source file. Must be inside an allowed --workdir if any are configured. | |
| destination_file_path | Yes | Absolute path to the destination file. Must be inside an allowed --workdir. Missing parent directories are created automatically. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file_type | Yes | Effective loader type used for the source file. One of: json, yaml, txt. |
| filter_id | Yes | Identifier of the registered filter that produced this file. |
| expires_at | Yes | UTC timestamp in ISO 8601 format when this filter expires. |
| overwritten | Yes | Whether an existing destination file was replaced. |
| bytes_written | Yes | Number of UTF-8 bytes written to the destination file. |
| source_file_path | Yes | Resolved absolute path of the processed source file. |
| destination_file_path | Yes | Resolved absolute path where the result text was written. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It thoroughly discloses all behavioral traits: writing behavior, workdir requirement, automatic directory creation, overwrite rejection (unless overwrite is true), and specific error conditions (ValueError, FileNotFoundError). No contradictory information.
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?
The description is front-loaded with a clear one-sentence summary, followed by usage context, then parameter explanations, and finally returns/raises. It is structured and relatively concise, though a bit lengthy. Every sentence serves a purpose.
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?
Given the tool's complexity (5 parameters, 3 required), high schema coverage (100%), and presence of output schema, the description is complete. It covers purpose, usage, parameter details, return values, and error conditions. No gaps for the agent to make incorrect decisions.
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 baseline is 3. The description adds value beyond schema: explains that file_type is an optional override, source_file_path must be inside allowed workdir, missing parent directories for destination are created automatically, and overwrite defaults to false. This enhances the agent's understanding.
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 description clearly states the action: 'Apply a registered filter to a source file and save the text output.' It specifies the resources (source file, destination file, filter) and explicitly contrasts with sibling tools by indicating it is used after register_filter and that run_filter is the alternative for non-persistent transformation.
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?
The description provides explicit guidance: use after register_filter, for transforming local json/yaml/txt files and persisting output. It also specifies when not to use: 'refuses to write when no --workdir is configured.' It mentions automatic parent directory creation and overwrite behavior, giving clear context for decision-making.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
register_filterA
Validate and register a restricted Python filter for later execution on a local file.
Use this tool first when you want to run custom filtering or transformation logic against a local document. The submitted source code must define exactly one top-level function with this exact signature:
def filter_item(data):The server loads the target file before execution and passes the loaded document into filter_item(data).
Input document types:
JSON files -> parsed JSON value such as dict, list, string, number, boolean, or null
YAML files -> parsed YAML value such as dict, list, string, number, boolean, or null
TXT files -> list of text lines
The function must return a text result (str). The returned text may contain any format you want, such as plain text, YAML, CSV-like text, or a custom report.
Preloaded modules (no imports needed; redundant import statements for these modules are accepted, including aliases):
json, yaml, re
math, statistics, datetime, decimal
collections, itertools, functools, operator
textwrap, html, base64, hashlib, ipaddress, unicodedata, difflib
Safety rules:
The code is validated against a restricted Python subset
next() (including a default value), assert statements, and catching AssertionError are supported
Imports of other modules and all from-imports are rejected
Network access, dynamic execution, and unsafe attribute access are rejected
Registered filters are stored in memory only and expire automatically after a server-side TTL
Forbidden:
Using non-standard libraries or modules
Accessing the filesystem, network, or environment variables
Defining multiple top-level functions, classes, or module-level code other than redundant imports
Using dynamic features like eval, exec, or import
Args: code: Python source code that defines exactly one function named filter_item(data).
Returns: A structured object containing the new filter identifier, expiration timestamp, TTL in seconds, and validation policy version.
Raises: ValueError: If the code is invalid, unsafe, or does not match the required function signature.
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | Python source code that defines exactly one top-level function named filter_item(data). The function receives the loaded document and must return a text result. |
Output Schema
| Name | Required | Description |
|---|---|---|
| filter_id | Yes | Unique filter identifier to pass into run_filter. |
| expires_at | Yes | UTC timestamp in ISO 8601 format when the filter expires. |
| ttl_seconds | Yes | Server-side lifetime of the registered filter in seconds. |
| policy_version | Yes | Validation policy version used for the submitted filter code. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly: in-memory-only storage with a server-side TTL, the exact validation policy, forbidden operations, the accepted preloaded module list, and the ValueError failure mode. Nothing material about behavior is left undisclosed.
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 purpose and usage before the long structural rules, and every block (input types, modules, safety, forbidden) is scannable. It is longer than strictly necessary — the redundant-import allowance is restated in the Forbidden section — but the length is justified by a constrained DSL.
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 tool that accepts arbitrary restricted code, the description covers signature, input contract, allowed stdlib surface, safety rules, TTL lifetime, and error semantics. An output schema exists, yet the description also summarizes the return object, leaving no gap.
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 already 100%, yet the description still adds real meaning: the exact required signature 'def filter_item(data)', what the argument receives for JSON/YAML/TXT inputs, and the required str return type. This is well beyond what the schema records.
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 pair and resource ('Validate and register a restricted Python filter') plus the downstream purpose ('for later execution on a local file'). An agent can distinguish this from run_filter purely from the description.
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?
Explicitly says 'Use this tool first when you want to run custom filtering or transformation logic against a local document', which clearly implies the ordering relative to run_filter. It never names run_filter directly, so the routing is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_filterA
Run a previously registered filter on a local file and return its text output.
Use this tool after register_filter. The server resolves the registered filter, loads the file from the local filesystem, converts it into an in-memory document, calls filter_item(data), and returns the exact text produced by the filter.
Supported file types:
json
yaml
txt
If file_type is omitted, the server tries to detect the type from the file extension.
File loading behavior:
json -> parsed JSON value
yaml -> parsed YAML value
txt -> list of lines
Args: filter_id: Identifier returned earlier by register_filter. file_path: Path to the local file that should be loaded and passed into the filter. file_type: Optional explicit file type override. Use this when extension-based detection is missing or ambiguous.
Returns: A structured object containing the filter identifier, resolved file path, effective file type, filter expiration time, and result_text.
Raises: ValueError: If the filter does not exist, has expired, returns a non-string result, or the file type is unsupported. FileNotFoundError: If the file does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Path to the local file that should be loaded and passed into filter_item(data). | |
| file_type | No | Optional explicit file type override. If omitted, the server detects the type from the file extension. | |
| filter_id | Yes | Identifier previously returned by register_filter. |
Output Schema
| Name | Required | Description |
|---|---|---|
| file_path | Yes | Resolved absolute path of the processed local file. |
| file_type | Yes | Effective loader type used for the file. One of: json, yaml, txt. |
| filter_id | Yes | Identifier of the registered filter that produced this result. |
| expires_at | Yes | UTC timestamp in ISO 8601 format when this filter expires. |
| result_text | Yes | Exact text returned by filter_item(data). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses behavior: it loads a file, converts to in-memory document, calls filter_item, and returns text. It details file type handling, loading behavior per type, errors (ValueError, FileNotFoundError), and the return structure. This is comprehensive and transparent.
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?
The description is well-structured with clear sections (purpose, usage, supported types, behavior, args, returns, raises). Every sentence adds value, though it is slightly verbose. It is front-loaded with purpose, making it easy to scan.
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?
Given the presence of an output schema (not shown but noted), the description appropriately does not repeat return values. It covers prerequisites (register_filter), file types, loading behavior, errors, and the output structure. It is fully complete for an agent to use the tool correctly.
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 baseline is 3. The description adds value by explaining the file loading behavior per type (e.g., 'json -> parsed JSON value'), which enriches understanding of the file_type and file_path parameters beyond the schema. This extra context justifies a 4.
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 description clearly states the tool's purpose: 'Run a previously registered filter on a local file and return its text output.' It specifies the action (run), the resource (filter), and the context (on a local file). It also distinguishes from sibling tools by mentioning its dependency on register_filter and the distinct behavior from convert_file.
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?
The description explicitly says 'Use this tool after register_filter,' guiding the agent on the correct sequence. It implies the prerequisite without explicitly excluding alternatives, but the context is clear. It does not mention when not to use or compare to convert_file, but the guideline is sufficient.
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.
3 tool updates
v0.1.0- First observed
convert_file - First observed
register_filter - First observed
run_filter
TDQS
Scored across 3 tools
register_filter clearly creates and validates a new filter, while run_filter and convert_file both execute an existing filter. Their output behaviors (return text vs write a file) are well described, but the shared execution step creates some potential for confusion.
All three tools follow a consistent snake_case verb_noun pattern: register_filter, run_filter, convert_file. The naming is predictable and easy to scan.
Three tools cover the minimal register/run/persist workflow for a custom filter service. The set is tightly scoped, and no tool feels redundant or out of place.
Core lifecycle operations are present: register a filter, execute it read-only, and execute it with file persistence. Missing list/delete/get-filter operations are minor gaps, partly mitigated by server-side TTL, but an agent cannot explicitly inspect or revoke filters.
Maintenance
Related MCP Connectors
An MCP server that provides bazaarvoic JOLT transformation capabilities.
MCP server for the FFmpeg Micro video transcoding API — create, monitor, download transcodes.
Host your MCP tool over streamable HTTP in one command.
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
Related MCP Servers
- FlicenseBqualityDmaintenanceA config-driven, zero-dependency MCP server with plugin architecture that enables filesystem operations, shell commands, HTTP requests, and utilities through simple JSON configuration.31-
- AlicenseAqualityDmaintenanceAn MCP server that exposes a single tool, jq, for running jq filters against JSON files on disk.16 npmMIT
- AlicenseNot gradedqualityAmaintenanceMCP server for reading, querying, and filtering local JSON files with extended JSONPath syntax, supporting sorting, aggregations, and complex conditions.15 npm2MIT
- AlicenseAqualityDmaintenanceA secure MCP server for converting documents between Markdown, DOCX, HTML, PDF, and TXT formats within a sandboxed working directory.3MIT