fix(webdav): address PR #764 review round 2
Addresses the four points raised in the automated review on PR #764: 1. Scope guards on the four search tools (search_files, find_by_name, find_by_type, list_favorites) so an excluded `scope` raises ToolError instead of silently returning an empty result. Previously an LLM could probe the asymmetry between list_directory (raises) and the search tools (silent) to infer that an excluded directory exists. The 4 search tools now mirror the early-guard pattern from list_directory and avoid an unnecessary upstream query for known- excluded scopes. 2. Concurrent per-tag resolution in get_excluded_file_paths via anyio.create_task_group(). Previously the 2N network calls (1 PROPFIND + 1 REPORT per tag) ran serially. Per-tag fail-open behaviour is preserved by extracting _resolve_one_tag, which swallows its own exceptions so a single tag failure does not abort the surrounding task group. 3. WebDAVClient.get_tag_by_name and get_files_by_tag now route through _make_request, inheriting the @retry_on_429 decorator. Previously they bypassed it; with tag exclusion invoked on every WebDAV tool call, a transient 429 from the systemtags endpoint was hitting the fail-open path instead of being transparently retried. 4. Test coverage: 6 new tests in test_webdav_tools_exclusion.py (4 scope-guard, 2 missing filter tests for find_by_type and list_favorites) and 2 new tests in test_tag_exclusion.py (a concurrency proof using an event-barrier that would deadlock under sequential execution, and a fail-open-under-task-group test with order-independent side_effect callables). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a6c188abbb
commit
d179ca8c8b
@@ -392,6 +392,13 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
"""
|
||||
client = await get_client(ctx)
|
||||
|
||||
# Resolve once and use for both the scope guard and the result filter.
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if scope and is_path_excluded(scope, excluded):
|
||||
raise ToolError(
|
||||
f"Access denied: scope {scope!r} is tagged with an excluded tag"
|
||||
)
|
||||
|
||||
# Build where conditions based on filters
|
||||
conditions = []
|
||||
|
||||
@@ -462,8 +469,7 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
# Filter out tagged-excluded paths.
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
# Filter out tagged-excluded paths from the result set.
|
||||
if excluded:
|
||||
results = [
|
||||
r for r in results if not is_path_excluded(r.get("path", ""), excluded)
|
||||
@@ -511,10 +517,14 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
SearchFilesResponse with list of matching files
|
||||
"""
|
||||
client = await get_client(ctx)
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if scope and is_path_excluded(scope, excluded):
|
||||
raise ToolError(
|
||||
f"Access denied: scope {scope!r} is tagged with an excluded tag"
|
||||
)
|
||||
results = await client.webdav.find_by_name(
|
||||
pattern=pattern, scope=scope, limit=limit
|
||||
)
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if excluded:
|
||||
results = [
|
||||
r for r in results if not is_path_excluded(r.get("path", ""), excluded)
|
||||
@@ -550,10 +560,14 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
SearchFilesResponse with list of matching files
|
||||
"""
|
||||
client = await get_client(ctx)
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if scope and is_path_excluded(scope, excluded):
|
||||
raise ToolError(
|
||||
f"Access denied: scope {scope!r} is tagged with an excluded tag"
|
||||
)
|
||||
results = await client.webdav.find_by_type(
|
||||
mime_type=mime_type, scope=scope, limit=limit
|
||||
)
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if excluded:
|
||||
results = [
|
||||
r for r in results if not is_path_excluded(r.get("path", ""), excluded)
|
||||
@@ -588,8 +602,12 @@ def configure_webdav_tools(mcp: FastMCP):
|
||||
SearchFilesResponse with list of favorite files
|
||||
"""
|
||||
client = await get_client(ctx)
|
||||
results = await client.webdav.list_favorites(scope=scope, limit=limit)
|
||||
excluded = await get_excluded_file_paths(client.webdav)
|
||||
if scope and is_path_excluded(scope, excluded):
|
||||
raise ToolError(
|
||||
f"Access denied: scope {scope!r} is tagged with an excluded tag"
|
||||
)
|
||||
results = await client.webdav.list_favorites(scope=scope, limit=limit)
|
||||
if excluded:
|
||||
results = [
|
||||
r for r in results if not is_path_excluded(r.get("path", ""), excluded)
|
||||
|
||||
Reference in New Issue
Block a user