-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Feat/tool permission #8617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feat/tool permission #8617
Changes from all commits
019fab5
2b38a01
319d313
9807a6e
0372ae6
a59eb4f
dfa8c0f
98c0b94
7a102b3
fd9aa96
3e11e43
c3faa33
731c701
4840137
9e9898e
ab59954
2e47e91
4e62a2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -958,6 +958,29 @@ def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None: | |||||||||||||||||||||||||||||||
| req.func_tool = new_tool_set | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _filter_tools_by_role(event: AstrMessageEvent, req: ProviderRequest) -> None: | ||||||||||||||||||||||||||||||||
| """从 req.func_tool 中移除当前用户无权调用的工具。 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| require_admin=True 的工具对非管理员用户不可见, | ||||||||||||||||||||||||||||||||
| LLM 不会感知到这些工具的存在。 | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if not req.func_tool: | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| if getattr(event, "role", "member") == "admin": | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| new_tool_set = ToolSet() | ||||||||||||||||||||||||||||||||
| for tool in req.func_tool.tools: | ||||||||||||||||||||||||||||||||
| if getattr(tool, "require_admin", False): | ||||||||||||||||||||||||||||||||
| logger.debug( | ||||||||||||||||||||||||||||||||
| "Hiding tool '%s' from non-admin user (sender_id=%s).", | ||||||||||||||||||||||||||||||||
| tool.name, | ||||||||||||||||||||||||||||||||
| event.get_sender_id(), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
|
Comment on lines
+973
to
+979
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
|
||||||||||||||||||||||||||||||||
| new_tool_set.add_tool(tool) | ||||||||||||||||||||||||||||||||
| req.func_tool = new_tool_set | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| async def _handle_webchat( | ||||||||||||||||||||||||||||||||
| event: AstrMessageEvent, req: ProviderRequest, prov: Provider | ||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||
|
|
@@ -1446,6 +1469,7 @@ async def build_main_agent( | |||||||||||||||||||||||||||||||
| req.session_id = event.unified_msg_origin | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _plugin_tool_fix(event, req) | ||||||||||||||||||||||||||||||||
| _filter_tools_by_role(event, req) | ||||||||||||||||||||||||||||||||
| await _apply_web_search_tools(event, req, plugin_context) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if config.llm_safety_mode: | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -516,6 +516,8 @@ async def init_mcp_clients( | |
| if raise_on_all_failed: | ||
| raise MCPAllServicesFailedError(msg) | ||
| logger.error(msg) | ||
|
|
||
| self._restore_tool_permissions() | ||
|
lingyun14beta marked this conversation as resolved.
|
||
| return summary | ||
|
|
||
| async def _start_mcp_server( | ||
|
|
@@ -678,6 +680,7 @@ async def _init_mcp_client(self, name: str, config: dict) -> MCPClient: | |
| self.func_list.append(func_tool) | ||
|
|
||
| logger.info(f"Connected to MCP server {name}, Tools: {tool_names}") | ||
| self._restore_tool_permissions() | ||
| return mcp_client | ||
|
|
||
| async def _terminate_mcp_client(self, name: str) -> None: | ||
|
|
@@ -887,6 +890,48 @@ def activate_llm_tool(self, name: str, star_map: dict) -> bool: | |
| return True | ||
| return False | ||
|
|
||
| def set_tool_require_admin(self, name: str, require: bool) -> bool: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new tool permission functionality (setting, restoring, and checking References
|
||
| """设置某工具是否仅管理员可调用,并持久化。 | ||
|
|
||
| Returns: | ||
| 如果没找到工具,会返回 False | ||
| """ | ||
| func_tool = self.get_func(name) | ||
| if func_tool is None: | ||
| return False | ||
|
|
||
| func_tool.require_admin = require | ||
|
|
||
| require_admin_map: dict = sp.get( | ||
| "tool_require_admin_map", | ||
| {}, | ||
| scope="global", | ||
| scope_id="global", | ||
| ) | ||
| require_admin_map[name] = require | ||
| sp.put( | ||
| "tool_require_admin_map", | ||
| require_admin_map, | ||
| scope="global", | ||
| scope_id="global", | ||
| ) | ||
| return True | ||
|
lingyun14beta marked this conversation as resolved.
|
||
|
|
||
| def _restore_tool_permissions(self) -> None: | ||
| """从持久化存储恢复 require_admin 状态到已注册的工具。 | ||
|
|
||
| 应在 MCP 客户端初始化完成后、插件工具注册完成后调用。 | ||
| """ | ||
| require_admin_map: dict = sp.get( | ||
| "tool_require_admin_map", | ||
| {}, | ||
| scope="global", | ||
| scope_id="global", | ||
| ) | ||
| for tool in self.func_list: | ||
| if tool.name in require_admin_map: | ||
| tool.require_admin = require_admin_map[tool.name] | ||
|
|
||
| @property | ||
| def mcp_config_path(self): | ||
| data_dir = get_astrbot_data_path() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,7 @@ def __init__( | |
| "/tools/mcp/test": ("POST", self.test_mcp_connection), | ||
| "/tools/list": ("GET", self.get_tool_list), | ||
| "/tools/toggle-tool": ("POST", self.toggle_tool), | ||
| "/tools/set-permission": ("POST", self.set_tool_permission), | ||
| "/tools/mcp/sync-provider": ("POST", self.sync_provider), | ||
| } | ||
| self.register_routes() | ||
|
|
@@ -498,6 +499,7 @@ async def get_tool_list(self): | |
| "description": tool.description, | ||
| "parameters": tool.parameters, | ||
| "active": tool.active, | ||
| "require_admin": getattr(tool, "require_admin", False), | ||
| "origin": origin, | ||
| "origin_name": origin_name, | ||
| "readonly": readonly, | ||
|
|
@@ -551,6 +553,42 @@ async def toggle_tool(self): | |
| logger.error(traceback.format_exc()) | ||
| return Response().error(f"Failed to operate tool: {e!s}").__dict__ | ||
|
|
||
| async def set_tool_permission(self): | ||
| """Set require_admin flag for a specified tool.""" | ||
| try: | ||
| data = await request.json | ||
| tool_name = data.get("name") | ||
| require_admin = data.get("require_admin") | ||
|
|
||
| if not tool_name or require_admin is None: | ||
| return ( | ||
| Response() | ||
| .error("Missing required parameters: name or require_admin") | ||
| .__dict__ | ||
| ) | ||
|
|
||
| if self.tool_mgr.is_builtin_tool(tool_name): | ||
| return ( | ||
| Response() | ||
| .error( | ||
| f"Tool {tool_name} is a builtin tool and its permission cannot be changed." | ||
| ) | ||
| .__dict__ | ||
| ) | ||
|
|
||
| ok = self.tool_mgr.set_tool_require_admin(tool_name, bool(require_admin)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 issue (security): Avoid using bool() coercion on
|
||
| if ok: | ||
| return Response().ok(None, "Permission updated.").__dict__ | ||
| return ( | ||
| Response() | ||
| .error(f"Tool {tool_name} does not exist or the operation failed.") | ||
| .__dict__ | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.error(traceback.format_exc()) | ||
| return Response().error(f"Failed to set tool permission: {e!s}").__dict__ | ||
|
|
||
| async def sync_provider(self): | ||
| """Sync MCP provider configuration.""" | ||
| try: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.