Skip to content

[CHORE]: Centralize Layer-1 visibility filter across main.py REST endpoints #4451

@jonpspri

Description

@jonpspri

Background

mcpgateway/main.py contains roughly 22 near-identical inline copies of the Layer-1 admin-bypass + public-only-secure-default derivation across REST endpoint handlers (list_tools, list_resources, list_prompts, list_a2a_agents, list_tags, get_entities_by_tag, several JSON-RPC dispatcher branches, etc.). Each call site looks roughly like:

user_email, token_teams, is_admin = get_rpc_filter_context(request, user)
if is_admin and token_teams is None:
    user_email = None
    token_teams = None  # Admin unrestricted
elif token_teams is None:
    token_teams = []  # Non-admin without teams = public-only (secure default)

mcpgateway/auth_context.py already provides get_scoped_resource_access_context(request, user) that encapsulates exactly this rule (and get_request_identity for the auditor-identity portion).

Why this matters

What was attempted

PR #4080 commit a1acd3691 ("refactor(main): centralize Layer-1 visibility filter via auth_context helpers") replaced the 22 inline copies with calls to get_scoped_resource_access_context / get_request_identity. That commit has been reverted because:

  1. Scope creep: the refactor was not the cause of the original SSO admin bug (issue SSO (Entra ID) users cannot edit gateways/servers in admin UI — 'invalid team name' error despite ["*"] effective permissions #4070), so it inflated PR fix(sso): SSO users with platform_admin role can now access teams in admin UI #4080 unnecessarily.
  2. Test churn: 14+ tests had to switch their @patch targets, several broke in subtle ways during rebases.
  3. Latent behavior change: get_scoped_resource_access_context correctly enables Layer-1 admin bypass for basic-auth/dev-mode admins (which the inline rule missed). This is technically a bug-fix bundled with the refactor — separating the two would be cleaner.

Acceptance criteria

  • All 22 inline Layer-1 derivations in main.py replaced with calls to get_scoped_resource_access_context(request, user) (and get_request_identity where the audit-identity values are also needed).
  • 4 known exceptions retained: a2a get_agent and invoke_agent (preserve user_email for federation user_id construction), _get_internal_a2a_scope_context (custom return contract), and the internal MCP tools/invoke handler that captures run_owner_email / run_owner_team_ids before applying the rule.
  • Tests updated to mock get_scoped_resource_access_context (post-rule 2-tuple) instead of the lower-level get_rpc_filter_context (pre-rule 3-tuple).
  • The basic-auth/dev-mode admin behavior change is documented in the commit message and covered by a regression test.

Out of scope for this issue

  • Anything in streamablehttp_transport.py — see the companion issue.
  • Anything in routers/teams.py (already migrated).

References

Metadata

Metadata

Labels

choreLinting, formatting, dependency hygiene, or project maintenance chorespythonPython / backend development (FastAPI)triageIssues / Features awaiting triage

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions