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.