From e4bf5254bfa6af1dc24ae419491882af3ce17506 Mon Sep 17 00:00:00 2001 From: ZachDreamZ Date: Sat, 4 Jul 2026 13:14:18 +0800 Subject: [PATCH 1/2] fix: _ai_get_date output_schema uses invalid JSON Schema type 'date' The JSON Schema spec does not define 'date' as a valid type. Use 'string' with format 'date' instead, which is the correct way to represent a date in JSON Schema. (cherry picked from commit 3efa79aba063082b7100da0cc6e0bdb58f658cd8) --- ai_tool/models/ai_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai_tool/models/ai_tool.py b/ai_tool/models/ai_tool.py index 68cd76d3..c6f1b2b8 100644 --- a/ai_tool/models/ai_tool.py +++ b/ai_tool/models/ai_tool.py @@ -47,7 +47,7 @@ def _get_tool_definition(self): @aitool( input_schema={}, output_schema={ - "date": {"type": "date"}, + "date": {"type": "string", "format": "date"}, }, ) def _ai_get_date(self): From 5f347381cb5049d213620c10d9b363d5706723bd Mon Sep 17 00:00:00 2001 From: nobuQuartile Date: Tue, 21 Jul 2026 00:26:43 +0000 Subject: [PATCH 2/2] [FIX] ai_tool: resolve tool model metadata with sudo Building and dispatching a tool reads ir.model via model_id, which a plain internal user (base.group_user) cannot access. Resolve the model name with sudo() while keeping the actual tool execution under the caller's own permissions, so non-admin MCP keys can list and call tools. Add a regression test exercising a non-admin user through _get_tool_definition and _execute_tool. (cherry picked from commit 16beaa75cebf403940309d55147aa1fa2ed33195) --- ai_tool/models/ai_tool.py | 20 ++++++++++++-------- ai_tool/tests/test_ai_tool.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ai_tool/models/ai_tool.py b/ai_tool/models/ai_tool.py index c6f1b2b8..b76c8331 100644 --- a/ai_tool/models/ai_tool.py +++ b/ai_tool/models/ai_tool.py @@ -36,7 +36,10 @@ class AiTool(models.Model): ) def _get_tool_definition(self): - func = getattr(self.env[self.model_id.model], self.function_name) + # model_id points to ir.model, which non-admin users cannot read. + # The model name is only metadata, so resolve it as sudo. + model = self.sudo().model_id.model + func = getattr(self.env[model], self.function_name) return { "name": self.name, "description": self.description, @@ -76,19 +79,20 @@ def _ai_post_message_parse_body(self, message): return plaintext2html(message) def _execute_tool(self, *args, record=None, **kwargs): + # model_id points to ir.model, which non-admin users cannot read. + # Resolve the model name as sudo (metadata only); the call below + # still runs with the caller's own rights via self.env[model]. + model = self.sudo().model_id.model if self.kind == "generic": - return getattr(self.env[self.model_id.model], self.function_name)( - *args, **kwargs - ) + return getattr(self.env[model], self.function_name)(*args, **kwargs) if not record: raise ValueError("Record must be provided for non-generic tools") if self.kind == "generic_model": - return getattr(self.env[self.model_id.model], self.function_name)( + return getattr(self.env[model], self.function_name)( *args, record=record, **kwargs ) - elif record._name != self.model_id.model: + elif record._name != model: raise ValueError( - f"Record model {record._name} does not match tool " - f"model {self.model_id.model}" + f"Record model {record._name} does not match tool model {model}" ) return getattr(record, self.function_name)(*args, **kwargs) or {} diff --git a/ai_tool/tests/test_ai_tool.py b/ai_tool/tests/test_ai_tool.py index c9090aaf..86c1d991 100644 --- a/ai_tool/tests/test_ai_tool.py +++ b/ai_tool/tests/test_ai_tool.py @@ -47,3 +47,21 @@ def test_post_message_tool_record_different_model(self): tool.kind = "record" with self.assertRaises(ValueError): tool._execute_tool(message="Hello World", record=self.partner) + + def test_tool_non_admin_user(self): + """A plain internal user (no ir.model access) must be able to build a + tool definition and execute a generic tool: resolving the tool's model + is metadata and must not require Administration/Access Rights.""" + user = self.env["res.users"].create( + { + "name": "Ai Tool Plain User", + "login": "ai_tool_plain_user", + "groups_id": [(6, 0, [self.env.ref("base.group_user").id])], + } + ) + self.assertFalse(user.has_group("base.group_system")) + tool = self.env.ref("ai_tool.current_date").with_user(user) + definition = tool._get_tool_definition() + self.assertEqual(definition["name"], "get_date") + result = tool._execute_tool() + self.assertIn("date", result)