Remove 'or running SQL' from the write-operations bullet so that SQL
execution is not grouped under can_write. execute_sql is controlled by
the separate execute_sql_query permission on SQLLab, which is already
called out in its own bullet below.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore "Available tools:" section header in app.py instructions so
test_get_default_instructions_declares_data_boundary can find it
- Revert fail-open change in _tool_allowed_for_current_user: tool-search
should stay fail-closed (hide protected tools) when no user is resolved;
only RBACToolVisibilityMiddleware.on_list_tools is fail-open for the
no-auth-configured case
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth.py: collapse check_tool_permission signature to one line (ruff-format)
- auth.py: extract _log_user_resolution_failure() helper to reduce
_setup_user_context cyclomatic complexity from 11 to 10 (ruff C901)
- test_middleware.py: shorten docstring to stay within 88-char limit (ruff E501)
Thread 1 (app.py): Restructure the permission preamble to unambiguously
separate write-access operations from SQL Lab access. Previously the
preamble listed "saving SQL queries" inside the write-operations clause
which could be read as including execute_sql. Now each permission type
is its own bullet with explicit tool names.
Thread 2 (server.py): Make _tool_allowed_for_current_user consistent with
RBACToolVisibilityMiddleware: "No authenticated user found" ValueError now
returns True (fail-open, show the tool) instead of False. Other ValueErrors
and PermissionError remain fail-closed. Previously tool-search mode would
hide all tools when no auth was configured, while tools/list showed all.
Thread 3 (middleware.py): Replace _setup_user_context() with a direct call
to get_user_from_request() in on_list_tools. _setup_user_context carries
per-call execution overhead (retry loop, session management, error logging)
that is inappropriate and noisy at list time. The middleware now controls
all logging for list-time auth failures directly.
Also updates all RBACToolVisibilityMiddleware tests to patch
get_user_from_request instead of _setup_user_context, matching the
refactored implementation.
- app.py: clarify execute_sql requires SQL Lab access (not write access)
in both the instructions preamble and Permission Awareness section
- auth.py: add log_denial param to check_tool_permission() to suppress
noisy WARNING logs during tools/list scanning; downgrade "No authenticated
user found" from ERROR to DEBUG in _setup_user_context
- middleware.py: fail completely closed (return []) on credential failures
instead of returning tools with no class_permission_name, which could
include protect=True tools requiring auth; remove _public_tools_only helper
- server.py: catch PermissionError (invalid API key) in addition to
ValueError in _tool_allowed_for_current_user
- tests: add tests for fail-closed branches (PermissionError, bad ValueError,
and no-auth-configured ValueError in RBACToolVisibilityMiddleware)
- Fail closed (return only public tools) when credentials are invalid
(PermissionError from bad API key, ValueError from unknown dev username);
fail open only when no auth source is configured at all
- Extract _get_app_context_manager() to module level in auth.py so
RBACToolVisibilityMiddleware reuses the same context-selection logic as
mcp_auth_hook, preventing external g.user from being shadowed
- Add RBACToolVisibilityMiddleware to __main__.py stdio entry point via
build_middleware_list() to keep all transports in sync
- Fix stale patch targets in test_tool_search_transform.py: update
superset.mcp_service.server.user_can_view_data_model_metadata →
superset.mcp_service.privacy.user_can_view_data_model_metadata
- Qualify write tool listings in instructions with "(requires write access)"
and add a permissions preamble so read-only users are not confused by
tools they cannot call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 1: MCPPermissionDeniedError falls through to GlobalErrorHandlerMiddleware's
generic "Internal error" branch (500-style response) because it doesn't subclass
PermissionError. Fixed by adding it to _USER_ERROR_TYPES and an explicit elif
branch in _handle_error() that converts it to a clean ToolError.
Phase 2: Add RBACToolVisibilityMiddleware that intercepts tools/list and removes
tools the calling user lacks permission to execute. Add
is_tool_visible_to_current_user() to auth.py as the single source of truth for
tool visibility, shared by both the new middleware and the existing tool-search
transform. Register the middleware inside StructuredContentStripperMiddleware so
it filters full tool objects before outputSchema stripping. Fail open: if user
resolution fails, all tools are returned (call-time RBAC still enforces).
Also update server instructions to note write tools require write permissions.