Skip to content

feat(governance): scope ir.model to the API-key role so list_models works - #7

Open
rutgerhofste wants to merge 1 commit into
19.0from
feat/scoped-list-models
Open

feat(governance): scope ir.model to the API-key role so list_models works#7
rutgerhofste wants to merge 1 commit into
19.0from
feat/scoped-list-models

Conversation

@rutgerhofste

Copy link
Copy Markdown
Member

Why

The MCP Pro server's list_models tool enumerates models by reading ir.model. A scoped read-only role holds none of the technical-model groups, so that read is denied and list_models returns 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_models came back empty for the read-only demo key, even though search_records on the business models worked fine.

Naively granting ir.model read is worse, not better: list_models does 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 - inject ir.model into the role's readable set (read only) so the ACL check on ir.model passes 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 global ir.rule on ir.model whose domain_force calls that helper. Global so it is evaluated for every ir.model read, but it resolves to no-op for UI users, unscoped keys and sudo.

Net effect: list_models mirrors 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.rule on ir.model); with this PR that hack is no longer needed and every scoped-key customer benefits.

Testing

  • New tests/test_scoped_model_list.py drives it end to end via the role thread-local: ir.model becomes 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.
  • Full module suite green (44 tests). Module fresh-installs cleanly (reviewer gate).
  • Runtime behaviour already confirmed live on the Bean Forge demo before generalising it here.

Notes

  • Read-only by construction: ir.model is only ever added to the read set, never write/create/unlink.
  • Scope is ir.model only. 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

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant