Skip to main content
Glama
data-filter-mcp

data-filter-mcp

by alxark

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_filter accepts Python source code with exactly one top-level function: def filter_item(data):

  • run_filter loads a local file, passes the loaded document into filter_item(data), and returns the text from result_text

  • convert_file loads a local file, passes it into filter_item(data), and writes the returned text to another local file

  • Registered 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.

  • lambda expressions — typically as key= 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 unsafe yaml.load / yaml.dump are intentionally not exposed.

  • re — re.match, re.search, re.fullmatch, re.findall, re.sub, re.subn, re.compile, re.escape, plus Match / Pattern methods (group, groups, groupdict, start, end, span).

  • math — numeric helpers such as math.ceil, math.floor, math.sqrt, math.log, math.exp, math.pow, math.factorial, math.gcd, math.lcm, math.isfinite, math.isclose.

  • statistics — aggregates such as statistics.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 as isoformat, strftime, timestamp, weekday, total_seconds. General instance attribute reads such as dt.year and dt.month are 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 as most_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 as lru_cache and cache are intentionally not exposed because they can retain process-local state across filter calls.

  • operator — itemgetter, methodcaller, and arithmetic/comparison helpers such as add, mul, lt, eq, gt. attrgetter is 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 as hexdigest, digest, update.

  • ipaddress — ip_address, ip_network, ip_interface, IPv4Network, IPv6Network, plus methods such as supernet, subnets, hosts, overlaps, subnet_of, supernet_of. General instance attribute reads such as addr.is_private and addr.compressed are 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 60

Show the available CLI flags with:

uvx data-filter-mcp --help

Restricting 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/data

Rules:

  • Each --workdir value must be an absolute path to an existing directory.

  • run_filter will only accept files located inside the allowed directories.

  • If no --workdir flags are provided, no restrictions are applied (backward compatible).

  • convert_file always requires at least one --workdir because it writes to disk.

  • convert_file requires the destination path to be inside an allowed workdir.

  • convert_file creates missing destination parent directories automatically.

  • convert_file refuses to replace an existing destination file unless overwrite is true.

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 by register_filter

  • source_file_path — absolute path to the json/yaml/txt file to load

  • destination_file_path — absolute path where the returned text should be saved

  • file_type — optional source file type override (json, yaml, or txt)

  • overwrite — optional boolean, default false

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 over HTTP

Use the MCP Streamable HTTP transport instead of the default stdio transport:

uvx data-filter-mcp --transport streamable-http --host 127.0.0.1 --port 8000 --workdir /tmp/data

The workdir must already exist. Connect an MCP client using Streamable HTTP to http://127.0.0.1:8000/mcp. --host and --port default to 127.0.0.1 and 8000; these flags only affect HTTP mode. For clients supporting this config format:

{
  "mcpServers": {
    "data-filter": {
      "type": "http",
      "url": "http://127.0.0.1:8000/mcp"
    }
  }
}

Security: HTTP mode has no authentication. Anyone who can reach the port can register and execute filters, read files, and write files within configured workdirs. Without --workdir, reads are unrestricted. All clients share the same in-memory filter registry; HTTP sessions do not isolate filters. Keep the default loopback bind address unless access is protected by an authenticated reverse proxy or equivalent controls. The SDK automatically enables DNS-rebinding protection when bound to 127.0.0.1, localhost or ::1; binding to 0.0.0.0 does not enable it.

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 60

Available Tools

3 tools
convert_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.

Inputs: filter_id (required), source_file_path (required), destination_file_path (required), file_type, +1 more. Returns: file_type, filter_id, expires_at, overwritten, +3 more

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.

Inputs: code (required). Returns: filter_id, expires_at, ttl_seconds, policy_version

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.

Inputs: file_path (required), filter_id (required), file_type. Returns: file_path, file_type, filter_id, expires_at, +1 more

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.6/5.0

    Scored across 3 tools

    Disambiguation4/5

    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.

    Naming Consistency5/5

    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.

    Tool Count5/5

    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.

    Completeness4/5

    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

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      A config-driven, zero-dependency MCP server with plugin architecture that enables filesystem operations, shell commands, HTTP requests, and utilities through simple JSON configuration.
      3
      1
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server that exposes a single tool, jq, for running jq filters against JSON files on disk.
      1
      9 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for reading, querying, and filtering local JSON files with extended JSONPath syntax, supporting sorting, aggregations, and complex conditions.
      174 npm
      2
      MIT