* fix(mcp): make key.access_group_ids grants additive over team ceiling
A key whose unified access_group_ids grant a private MCP server was
having that grant intersected against its team's MCP ceiling, so a key
in a team scoped to other servers (or with no own scope) lost the
granted server entirely. Resolve access_group_ids once as ungated
additive grants and union them on top of the key/team ceiling instead
of folding them into the key scope that gets intersected.
* test(mcp): align key access-group tests with additive-grant model
The previous commit moved key.access_group_ids resolution out of the
intersected key ceiling (_get_allowed_mcp_servers_for_key) and into the
ungated additive grant path (_get_key_access_group_mcp_server_extras),
unioned on top of the team ceiling. Five tests from #28890/#29195 still
asserted the old gated / in-key-scope contract and failed:
- _get_allowed_mcp_servers_for_key now returns the object_permission
ceiling only and never resolves access_group_ids; two tests now assert
the group resolver is not called from that path (with and without an
object_permission present).
- The extras path is ungated, so a group whose assigned_team_ids /
assigned_key_ids exclude the caller still contributes its servers.
- The end-to-end test asserts the grant surfaces via the extras path
rather than the base key path.
- Dropped test_key_access_group_ids_empty_returns_no_extras; the empty
case is already covered by the extras family's no-groups test.
* feat(auth): gate member access-group assignment on keys behind opt-in
Non-admin team members could attach access_group_ids to keys they create
or update, letting them self-grant resources (MCP servers/models) the team
admin never intended. Add an opt-in KEY_ACCESS_GROUP_ASSIGNMENT team-member
permission (default-deny) enforced at /key/generate and /key/update; proxy
and team admins bypass. Surfaces automatically as a checkbox in the team
Member Permissions UI.
* fix(auth): gate access-group assignment on /key/regenerate too
RegenerateKeyRequest inherits access_group_ids and prepare_key_update_data
persists it, so a non-admin key owner could self-grant access groups by
regenerating. Apply the same opt-in member gate using the existing key's
team.
* test(auth): cover member access-group gate and additive MCP grants
Add unit tests for enforce_member_can_assign_access_groups (deny without
opt-in, allow with opt-in, and proxy-admin / team-admin / non-team-key
bypasses) and for _get_key_access_group_mcp_server_extras (no-auth and
no-resolved-servers return empty, resolved ids are expanded, errors
degrade to no grants).