data-filter-mcp 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/PKG-INFO +1 -1
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/_version.py +3 -3
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/server.py +1 -1
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/validator.py +20 -2
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/PKG-INFO +1 -1
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/tests/test_validator.py +40 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/.github/workflows/ci.yml +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/.github/workflows/release.yml +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/.gitignore +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/LICENSE +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/README.md +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/__init__.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/__init__.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/base.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/factory.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/json_loader.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/txt_loader.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/loaders/yaml_loader.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/models.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp/registry.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/SOURCES.txt +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/dependency_links.txt +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/entry_points.txt +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/requires.txt +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/top_level.txt +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/pyproject.toml +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/server.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/setup.cfg +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/tests/test_loaders.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/tests/test_mcp_tools.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/tests/test_registry.py +0 -0
- {data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/tests/test_server_flow.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 6)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga0ff2aab6'
|
|
@@ -239,7 +239,7 @@ def create_mcp_server(service: FilterService | None = None) -> FastMCP:
|
|
|
239
239
|
The function must return a text result (str). The returned text may contain any
|
|
240
240
|
format you want, such as plain text, YAML, CSV-like text, or a custom report.
|
|
241
241
|
|
|
242
|
-
Preloaded standard-library modules:
|
|
242
|
+
Preloaded standard-library modules (don't try to import them in your functions):
|
|
243
243
|
- json, yaml, re
|
|
244
244
|
- math, statistics, datetime, decimal
|
|
245
245
|
- collections, itertools, functools, operator
|
|
@@ -56,6 +56,10 @@ SAFE_ATTRIBUTE_READS = {
|
|
|
56
56
|
("itertools", "chain"),
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
SAFE_DUNDER_ATTRIBUTES = {
|
|
60
|
+
"__name__",
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
SAFE_BUILTINS: dict[str, Any] = {
|
|
60
64
|
"all": all,
|
|
61
65
|
"any": any,
|
|
@@ -71,10 +75,14 @@ SAFE_BUILTINS: dict[str, Any] = {
|
|
|
71
75
|
"range": range,
|
|
72
76
|
"reversed": reversed,
|
|
73
77
|
"sorted": sorted,
|
|
78
|
+
"set": set,
|
|
74
79
|
"str": str,
|
|
75
80
|
"sum": sum,
|
|
76
81
|
"tuple": tuple,
|
|
82
|
+
"type": type,
|
|
77
83
|
"zip": zip,
|
|
84
|
+
"isinstance": isinstance,
|
|
85
|
+
"Exception": Exception,
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
SAFE_METHODS = {
|
|
@@ -280,6 +288,7 @@ SAFE_METHODS = {
|
|
|
280
288
|
"sha384",
|
|
281
289
|
"sha512",
|
|
282
290
|
"shorten",
|
|
291
|
+
"sort",
|
|
283
292
|
"span",
|
|
284
293
|
"split",
|
|
285
294
|
"splitlines",
|
|
@@ -355,6 +364,8 @@ ALLOWED_NODE_TYPES = {
|
|
|
355
364
|
ast.Constant,
|
|
356
365
|
ast.List,
|
|
357
366
|
ast.Tuple,
|
|
367
|
+
ast.Try,
|
|
368
|
+
ast.ExceptHandler,
|
|
358
369
|
ast.Set,
|
|
359
370
|
ast.Dict,
|
|
360
371
|
ast.Subscript,
|
|
@@ -472,14 +483,21 @@ class FilterValidator(ast.NodeVisitor):
|
|
|
472
483
|
self.generic_visit(node)
|
|
473
484
|
|
|
474
485
|
def visit_Attribute(self, node: ast.Attribute) -> None:
|
|
475
|
-
|
|
486
|
+
is_safe_dunder = node.attr in SAFE_DUNDER_ATTRIBUTES
|
|
487
|
+
if node.attr.startswith("_") and not is_safe_dunder:
|
|
476
488
|
raise FilterValidationError(f"Attribute access is not allowed: {node.attr}")
|
|
477
489
|
|
|
478
490
|
parent = self._parents.get(id(node))
|
|
479
491
|
if self._is_safe_attribute_read(node):
|
|
480
492
|
return
|
|
481
493
|
|
|
482
|
-
|
|
494
|
+
is_call_target = isinstance(parent, ast.Call) and parent.func is node
|
|
495
|
+
|
|
496
|
+
if is_safe_dunder and not is_call_target:
|
|
497
|
+
self.visit(node.value)
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
if not is_call_target:
|
|
483
501
|
raise FilterValidationError(
|
|
484
502
|
"Attribute access is restricted to approved method calls"
|
|
485
503
|
)
|
|
@@ -144,6 +144,46 @@ def test_compile_filter_rejects_attribute_reads() -> None:
|
|
|
144
144
|
compile_filter(code)
|
|
145
145
|
|
|
146
146
|
|
|
147
|
+
def test_compile_filter_allows_dunder_name_attribute() -> None:
|
|
148
|
+
code = textwrap.dedent(
|
|
149
|
+
"""
|
|
150
|
+
def filter_item(data):
|
|
151
|
+
return type(data).__name__
|
|
152
|
+
"""
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
filter_fn = compile_filter(code)
|
|
156
|
+
|
|
157
|
+
assert filter_fn([1, 2, 3]) == "list"
|
|
158
|
+
assert filter_fn({"k": 1}) == "dict"
|
|
159
|
+
assert filter_fn("hello") == "str"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_compile_filter_allows_dunder_name_in_lambda() -> None:
|
|
163
|
+
code = textwrap.dedent(
|
|
164
|
+
"""
|
|
165
|
+
def filter_item(data):
|
|
166
|
+
return ",".join(sorted([type(item).__name__ for item in data]))
|
|
167
|
+
"""
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
filter_fn = compile_filter(code)
|
|
171
|
+
|
|
172
|
+
assert filter_fn([1, "x", 2.0]) == "float,int,str"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_compile_filter_rejects_calling_dunder_name() -> None:
|
|
176
|
+
code = textwrap.dedent(
|
|
177
|
+
"""
|
|
178
|
+
def filter_item(data):
|
|
179
|
+
return data.__name__()
|
|
180
|
+
"""
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
with pytest.raises(FilterValidationError, match="Method is not allowed: __name__"):
|
|
184
|
+
compile_filter(code)
|
|
185
|
+
|
|
186
|
+
|
|
147
187
|
def test_compile_filter_rejects_string_format_methods() -> None:
|
|
148
188
|
code = textwrap.dedent(
|
|
149
189
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{data_filter_mcp-0.2.4 → data_filter_mcp-0.2.6}/data_filter_mcp.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|