feat(governance): scope ir.model to the API-key role so list_models works - #7
Open
rutgerhofste wants to merge 1 commit into
Open
feat(governance): scope ir.model to the API-key role so list_models works#7rutgerhofste wants to merge 1 commit into
rutgerhofste wants to merge 1 commit into
Conversation
…orks The MCP server's `list_models` tool enumerates models by reading `ir.model`. A scoped read-only role holds none of the technical-model groups, so the read is denied and `list_models` returns an empty list -- a poor first impression for anyone connecting an AI agent with a scoped key. Naively granting `ir.model` read is worse: `list_models` does no per-model filtering, so it would dump every model in the database, almost none of which the key can actually read. Fix, entirely inside the governance layer and only for role-bound requests: - Inject `ir.model` into the role's readable set (read-only) so the ACL check on `ir.model` passes. - Add a global record rule that scopes the visible `ir.model` rows to exactly the models the role may read (via `res.users._mcp_ir_model_domain`), excluding the introspection models themselves so they are not noise in the list. Off-role requests (UI, unscoped keys, sudo) get an always-true domain and are unaffected. Result: `list_models` mirrors the role -- the relevant business models, with clean names, and nothing the key cannot read. Tests: new test_scoped_model_list drives it end to end via the role thread-local (readable set, row scoping, off-role no-op, read-only grant). Full suite green (44 tests); module fresh-installs cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The MCP Pro server's
list_modelstool enumerates models by readingir.model. A scoped read-only role holds none of the technical-model groups, so that read is denied andlist_modelsreturns an empty list. Anyone connecting an AI agent with a scoped key and asking "what can I access here?" gets nothing back.Found while testing the Bean Forge demo:
list_modelscame back empty for the read-only demo key, even thoughsearch_recordson the business models worked fine.Naively granting
ir.modelread is worse, not better:list_modelsdoes no per-model permission filtering, so it would dump every model in the database (~440 rows), almost none of which the key can actually read.What
Fixed entirely inside the governance layer, and only for role-bound requests:
ir_model_access.py- injectir.modelinto the role's readable set (read only) so the ACL check onir.modelpasses for a scoped key.res_users.py-_mcp_ir_model_domain()returns the row scope: for a role-bound request, the models the role may read (minus the introspection models, which would just be noise in the list); off-role it returns an always-true domain.security/mcp_scoped_model_list.xml- a globalir.ruleonir.modelwhosedomain_forcecalls that helper. Global so it is evaluated for everyir.modelread, but it resolves to no-op for UI users, unscoped keys and sudo.Net effect:
list_modelsmirrors the role - the relevant business models with clean names, and nothing the key cannot read. No change for any non-role request.This is the general version of the manual patch applied to the Bean Forge demo instance (a hand-written
ir.ruleonir.model); with this PR that hack is no longer needed and every scoped-key customer benefits.Testing
tests/test_scoped_model_list.pydrives it end to end via the role thread-local:ir.modelbecomes readable for the role, rows are scoped to the role's models (and the introspection model itself is hidden), off-role reads are untouched, and write is never granted.Notes
ir.modelis only ever added to thereadset, never write/create/unlink.ir.modelonly. Field-level introspection (ir.model.fields) is intentionally left out to keep the change minimal; add it separately if a tool needs it.🤖 Generated with Claude Code